*
* @param int $index Namespace index
* @return int
+ * @throws MWException if the given namespace doesn't have an associated talk namespace
+ * (e.g. NS_SPECIAL).
*/
public function getTalk( $index ) {
$this->isMethodValidFor( $index, __METHOD__ );
}
/**
+ * Get a LinkTarget referring to the talk page of $target.
+ *
+ * @see canHaveTalkPage
* @param LinkTarget $target
* @return LinkTarget Talk page for $target
- * @throws MWException if $target's namespace doesn't have talk pages (e.g., NS_SPECIAL)
+ * @throws MWException if $target doesn't have talk pages, e.g. because it's in NS_SPECIAL,
+ * because it's a relative section-only link, or it's an an interwiki link.
*/
public function getTalkPage( LinkTarget $target ) : LinkTarget {
+ if ( $target->getText() === '' ) {
+ throw new MWException( 'Can\'t determine talk page associated with relative section link' );
+ }
+
+ if ( $target->getInterwiki() !== '' ) {
+ throw new MWException( 'Can\'t determine talk page associated with interwiki link' );
+ }
+
if ( $this->isTalk( $target->getNamespace() ) ) {
return $target;
}
- return new TitleValue( $this->getTalk( $target->getNamespace() ), $target->getDbKey() );
+
+ // NOTE: getTalk throws on bad namespaces!
+ return new TitleValue( $this->getTalk( $target->getNamespace() ), $target->getDBkey() );
+ }
+
+ /**
+ * Can the title have a corresponding talk page?
+ *
+ * False for relative section-only links (with getText() === ''),
+ * interwiki links (with getInterwiki() !== ''), and pages in NS_SPECIAL.
+ *
+ * @see getTalkPage
+ *
+ * @param LinkTarget $target
+ * @return bool True if this title either is a talk page or can have a talk page associated.
+ */
+ public function canHaveTalkPage( LinkTarget $target ) {
+ if ( $target->getText() === '' || $target->getInterwiki() !== '' ) {
+ return false;
+ }
+
+ if ( $target->getNamespace() < NS_MAIN ) {
+ return false;
+ }
+
+ return true;
}
/**
if ( $this->isSubject( $target->getNamespace() ) ) {
return $target;
}
- return new TitleValue( $this->getSubject( $target->getNamespace() ), $target->getDbKey() );
+ return new TitleValue( $this->getSubject( $target->getNamespace() ), $target->getDBkey() );
}
/**
* @throws MWException if $target's namespace doesn't have talk pages (e.g., NS_SPECIAL)
*/
public function getAssociatedPage( LinkTarget $target ) : LinkTarget {
+ if ( $target->getText() === '' ) {
+ throw new MWException( 'Can\'t determine talk page associated with relative section link' );
+ }
+
+ if ( $target->getInterwiki() !== '' ) {
+ throw new MWException( 'Can\'t determine talk page associated with interwiki link' );
+ }
+
return new TitleValue(
- $this->getAssociated( $target->getNamespace() ), $target->getDbKey() );
+ $this->getAssociated( $target->getNamespace() ), $target->getDBkey() );
}
/**
return $levels;
}
- // First, get the list of groups that can edit this namespace.
- $namespaceGroups = [];
- $combine = 'array_merge';
+ // $wgNamespaceProtection can require one or more rights to edit the namespace, which
+ // may be satisfied by membership in multiple groups each giving a subset of those rights.
+ // A restriction level is redundant if, for any one of the namespace rights, all groups
+ // giving that right also give the restriction level's right. Or, conversely, a
+ // restriction level is not redundant if, for every namespace right, there's at least one
+ // group giving that right without the restriction level's right.
+ //
+ // First, for each right, get a list of groups with that right.
+ $namespaceRightGroups = [];
foreach ( (array)$this->options->get( 'NamespaceProtection' )[$index] as $right ) {
if ( $right == 'sysop' ) {
$right = 'editprotected'; // BC
$right = 'editsemiprotected'; // BC
}
if ( $right != '' ) {
- $namespaceGroups = call_user_func( $combine, $namespaceGroups,
- User::getGroupsWithPermission( $right ) );
- $combine = 'array_intersect';
+ $namespaceRightGroups[$right] = User::getGroupsWithPermission( $right );
}
}
- // Now, keep only those restriction levels where there is at least one
- // group that can edit the namespace but would be blocked by the
- // restriction.
+ // Now, go through the protection levels one by one.
$usableLevels = [ '' ];
foreach ( $this->options->get( 'RestrictionLevels' ) as $level ) {
$right = $level;
if ( $right == 'autoconfirmed' ) {
$right = 'editsemiprotected'; // BC
}
- if ( $right != '' && ( !$user || $user->isAllowed( $right ) ) &&
- array_diff( $namespaceGroups, User::getGroupsWithPermission( $right ) )
+
+ if ( $right != '' &&
+ !isset( $namespaceRightGroups[$right] ) &&
+ ( !$user || $user->isAllowed( $right ) )
) {
+ // Do any of the namespace rights imply the restriction right? (see explanation above)
+ foreach ( $namespaceRightGroups as $groups ) {
+ if ( !array_diff( $groups, User::getGroupsWithPermission( $right ) ) ) {
+ // Yes, this one does.
+ continue 2;
+ }
+ }
+ // No, keep the restriction level
$usableLevels[] = $level;
}
}