From fdf83070cdddd7650891e2dd8d4648849874517f Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 12 Jun 2017 21:11:42 +0200 Subject: [PATCH] Add Title::isValid method Make the notion of Title objects representing invalid titles explicit. Bug: T165149 Change-Id: I89aaabdff9614fe63bd1244784a1d677dbc26f9e --- includes/Title.php | 60 ++++++++++++++++++++++++++-- tests/phpunit/includes/TitleTest.php | 22 ++++++++++ 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/includes/Title.php b/includes/Title.php index c97a42bde5..8cfeb882a0 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -253,6 +253,9 @@ class Title implements LinkTarget { * Create a new Title from text, such as what one would find in a link. De- * codes any HTML entities in the text. * + * Title objects returned by this method are guaranteed to be valid, and + * thus return true from the isValid() method. + * * @param string|int|null $text The link text; spaces, prefixes, and an * initial ':' indicating the main namespace are accepted. * @param int $defaultNamespace The namespace to use if none is specified @@ -284,6 +287,9 @@ class Title implements LinkTarget { * * The exception subclasses encode detailed information about why the title is invalid. * + * Title objects returned by this method are guaranteed to be valid, and + * thus return true from the isValid() method. + * * @see Title::newFromText * * @since 1.25 @@ -500,10 +506,19 @@ class Title implements LinkTarget { /** * Create a new Title from a namespace index and a DB key. - * It's assumed that $ns and $title are *valid*, for instance when - * they came directly from the database or a special page name. - * For convenience, spaces are converted to underscores so that - * eg user_text fields can be used directly. + * + * It's assumed that $ns and $title are safe, for instance when + * they came directly from the database or a special page name, + * not from user input. + * + * No validation is applied. For convenience, spaces are normalized + * to underscores, so that e.g. user_text fields can be used directly. + * + * @note This method may return Title objects that are "invalid" + * according to the isValid() method. This is usually caused by + * configuration changes: e.g. a namespace that was once defined is + * no longer configured, or a character that was once allowed in + * titles is now forbidden. * * @param int $ns The namespace of the article * @param string $title The unprefixed database key form @@ -529,6 +544,10 @@ class Title implements LinkTarget { * The parameters will be checked for validity, which is a bit slower * than makeTitle() but safer for user-provided data. * + * Title objects returned by makeTitleSafe() are guaranteed to be valid, + * that is, they return true from the isValid() method. If no valid Title + * can be constructed from the input, this method returns null. + * * @param int $ns The namespace of the article * @param string $title Database key form * @param string $fragment The link fragment (after the "#") @@ -536,6 +555,9 @@ class Title implements LinkTarget { * @return Title|null The new object, or null on an error */ public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) { + // NOTE: ideally, this would just call makeTitle() and then isValid(), + // but presently, that means more overhead on a potential performance hotspot. + if ( !MWNamespace::exists( $ns ) ) { return null; } @@ -777,6 +799,36 @@ class Title implements LinkTarget { } } + /** + * Returns true if the title is valid, false if it is invalid. + * + * Valid titles can be round-tripped via makeTitleSafe() and newFromText(). + * Invalid titles may get returned from makeTitle(), and it may be useful to + * allow them to exist, e.g. in order to process log entries about pages in + * namespaces that belong to extensions that are no longer installed. + * + * @note This method is relatively expensive. When constructing Title + * objects that need to be valid, use an instantiator method that is guaranteed + * to return valid titles, such as makeTitleSafe() or newFromText(). + * + * @return bool + */ + public function isValid() { + $ns = $this->getNamespace(); + + if ( !MWNamespace::exists( $ns ) ) { + return false; + } + + try { + $parser = MediaWikiServices::getInstance()->getTitleParser(); + $parser->parseTitle( $this->getDBkey(), $ns ); + return true; + } catch ( MalformedTitleException $ex ) { + return false; + } + } + /** * Determine whether the object refers to a page within * this project (either this wiki or a wiki with a local diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php index 7770cbc2c3..b0febe8d5f 100644 --- a/tests/phpunit/includes/TitleTest.php +++ b/tests/phpunit/includes/TitleTest.php @@ -637,6 +637,28 @@ class TitleTest extends MediaWikiTestCase { ]; } + /** + * @covers Title::isValid + * @dataProvider provideIsValid + * @param Title $title + * @param bool $isValid + */ + public function testIsValid( Title $title, $isValid ) { + $this->assertEquals( $isValid, $title->isValid(), $title->getPrefixedText() ); + } + + public static function provideIsValid() { + return [ + [ Title::makeTitle( NS_MAIN, '' ), false ], + [ Title::makeTitle( NS_MAIN, '<>' ), false ], + [ Title::makeTitle( NS_MAIN, '|' ), false ], + [ Title::makeTitle( NS_MAIN, '#' ), false ], + [ Title::makeTitle( NS_MAIN, 'Test' ), true ], + [ Title::makeTitle( -33, 'Test' ), false ], + [ Title::makeTitle( 77663399, 'Test' ), false ], + ]; + } + /** * @covers Title::isAlwaysKnown */ -- 2.20.1