RCFilters: Prevent duplicate filter names
authorMatthew Flaschen <mflaschen@wikimedia.org>
Thu, 16 Mar 2017 04:06:02 +0000 (00:06 -0400)
committerCatrope <roan@wikimedia.org>
Thu, 16 Mar 2017 07:19:40 +0000 (07:19 +0000)
Explicitly block two filters in the same group from having the same
name.

Before, it would be left to registerFilter, which would just cause
the second one to win.

Also, avoid a getFilter warning when the filter does not exist.
Do the same for getFilterGroup on ChangesListSpecialPage

Finally, a minor related doc fix.

Change-Id: I6b3880a5c7cc381c169bbd969cd4814559b49c91

docs/hooks.txt
includes/changes/ChangesListFilter.php
includes/changes/ChangesListFilterGroup.php
includes/specialpage/ChangesListSpecialPage.php
tests/phpunit/includes/changes/ChangesListFilterGroupTest.php
tests/phpunit/includes/changes/ChangesListFilterTest.php

index 149ee4b..1be9a03 100644 (file)
@@ -1015,10 +1015,11 @@ $opts: FormOptions for this request
 'ChangesListSpecialPageStructuredFilters': Called to allow extensions to register
 filters for pages inheriting from ChangesListSpecialPage (in core: RecentChanges,
 RecentChangesLinked, and Watchlist).  Generally, you will want to construct
-new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects.  You can
-then either add them to existing ChangesListFilterGroup objects (accessed through
-$special->getFilterGroup), or create your own.  If you create new groups, you
-must register them with $special->registerFilterGroup.
+new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects.
+
+When constructing them, you specify which group they belong to.  You can reuse
+existing groups (accessed through $special->getFilterGroup), or create your own.
+If you create new groups, you must register them with $special->registerFilterGroup.
 $special: ChangesListSpecialPage instance
 
 'ChangeTagAfterDelete': Called after a change tag has been deleted (that is,
index cd74154..22e797d 100644 (file)
@@ -113,7 +113,8 @@ abstract class ChangesListFilter {
        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.
@@ -161,6 +162,11 @@ abstract class ChangesListFilter {
                        );
                }
 
+               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'] ) ) {
index 30ab552..d2ad204 100644 (file)
@@ -315,10 +315,10 @@ abstract class ChangesListFilterGroup {
         * Get filter by name
         *
         * @param string $name Filter name
-        * @return ChangesListFilter Specified filter
+        * @return ChangesListFilter|null Specified filter, or null if it is not registered
         */
        public function getFilter( $name ) {
-               return $this->filters[$name];
+               return isset( $this->filters[$name] ) ? $this->filters[$name] : null;
        }
 
        /**
index e92f697..8f702ba 100644 (file)
@@ -663,10 +663,12 @@ abstract class ChangesListSpecialPage extends SpecialPage {
         *
         * @param string $groupName Name of group
         *
-        * @return ChangesListFilterGroup
+        * @return ChangesListFilterGroup|null Group, or null if not registered
         */
        public function getFilterGroup( $groupName ) {
-               return $this->filterGroups[$groupName];
+               return isset( $this->filterGroups[$groupName] ) ?
+                       $this->filterGroups[$groupName] :
+                       null;
        }
 
        // Currently, this intentionally only includes filters that display
index f712a2f..465a9d1 100644 (file)
@@ -51,4 +51,29 @@ class ChangesListFilterGroupTest extends MediaWikiTestCase {
                        )
                );
        }
+
+       // Get without warnings
+       public function testGetFilter() {
+               $group = new MockChangesListFilterGroup(
+                       [
+                               'type' => 'some_type',
+                               'name' => 'groupName',
+                               'isFullCoverage' => true,
+                               'priority' => 1,
+                               'filters' => [
+                                       [ 'name' => 'foo' ],
+                               ],
+                       ]
+               );
+
+               $this->assertEquals(
+                       'foo',
+                       $group->getFilter( 'foo' )->getName()
+               );
+
+               $this->assertEquals(
+                       null,
+                       $group->getFilter( 'bar' )
+               );
+       }
 }
index c212560..1d87aeb 100644 (file)
@@ -40,6 +40,30 @@ class ChangesListFilterTest extends MediaWikiTestCase {
                );
        }
 
+       // @codingStandardsIgnoreStart
+       /**
+        * @expectedException MWException
+        * @expectedExceptionMessage Two filters in a group cannot have the same name: 'somename'
+        */
+       // @codingStandardsIgnoreEnd
+       public function testDuplicateName() {
+               new MockChangesListFilter(
+                       [
+                               'group' => $this->group,
+                               'name' => 'somename',
+                               'priority' => 1,
+                       ]
+               );
+
+               new MockChangesListFilter(
+                       [
+                               'group' => $this->group,
+                               'name' => 'somename',
+                               'priority' => 2,
+                       ]
+               );
+       }
+
        /**
         * @expectedException MWException
         * @expectedExceptionMessage Supersets can only be defined for filters in the same group