diff --git "a/include/class-mw\\CompleteTitle.php" "b/include/class-mw\\CompleteTitle.php" index 39ab558..1e610f6 100644 --- "a/include/class-mw\\CompleteTitle.php" +++ "b/include/class-mw\\CompleteTitle.php" @@ -1,168 +1,202 @@ . # MediaWiki namespace mw; /** * A MediaWiki complete title (with namespace) */ class CompleteTitle { /** * Which MediaWiki site * * @var object */ private $wiki; /** * @var object */ private $ns; /** * @var object */ private $title; /** * Constructor * - * @param $wiki object - * @param $ns object - * @param $title object + * @param $wiki object Dependency injection of the wiki + * @param $ns object Dependency injection of the namespace + * @param $title object Dependency injection of the Title object (the part after the namespace) */ public function __construct( $wiki, Ns $ns, Title $title ) { $this->wiki = $wiki; $this->ns = $ns; $this->title = $title; } /** * Get the namespace object * * @return object */ public function getNs() { return $this->ns; } /** * Get the title object * * @return object */ public function getTitle() { return $this->title; } /** - * Get the complete title, as displayed in a page + * Get the complete title, as displayed in a page, without underscores * * @return string */ public function getCompleteTitle() { $title = $this->getTitle()->get(); $ns = $this->getNs()->getName(); if( $ns ) { - $ns .= ":"; + $ns .= ':'; } return $ns . $title; } + /** + * Get the complete title, as displayed in a page, but with underscores + * + * @return string + */ + public function getCompleteTitleUnderscored() { + $title = $this->getCompleteTitle(); + return TitlePart::space2underscore( $title ); + } + + /** + * Get the {{SUBPAGENAME}} for this complete page title + * + * It reproduces the MediaWiki {{SUBPAGENAME}} behaviour, so: + * - return 'asd' from 'Discussion:The/asd' + * - return 'Discussion' from 'Discussion:Asd' + * + * See https://www.mediawiki.org/wiki/Help:Magic_words + */ + public function getSubPageName() { + return $this->getTitle()->getSubPageName(); + } + /** * Get the regex able to match this complete title * * @param $args array * ns-group-name: Name of the capturing group for the namespace * title-group-name: Name of the capturing group for the title * @return string */ public function getRegex( $args = [] ) { // default options $args = array_replace( [ 'wikilink' => true, 'ns-group-name' => null, 'title-group-name' => null, ], $args ); // namespace regex $ns = $this->getNs()->getRegex( [ 'wikilink' => $args[ 'wikilink' ], ] ); // title regex $title = $this->getTitle()->getRegex(); // @TODO: handle anchor // eventually group $ns = \regex\Generic::groupNamed( $ns, $args[ 'ns-group-name' ] ); $title = \regex\Generic::groupNamed( $title, $args[ 'title-group-name' ] ); return $ns . '[ _]*' . $title; } /** * Create a Wikilink to this title * * @param $alias string|false|null (NULL: whatever, false: no one) * @return object */ public function createWikilink( $alias = null ) { return new Wikilink( $this, $alias ); } + /** + * Get the URL to this page + * + * @return string + */ + public function getURL() { + $title = $this->getCompleteTitleUnderscored(); + $title = urlencode( $title ); + return $this->wiki->getBaseURL() . $title; + } + /** * Static constructor parsing a string * * @param $wiki object * @param $s string e.g. ' Mediawiki: test ' * @return self */ public static function createParsingTitle( $wiki, $s ) { // @TODO: parse also anchor // split namespace and title $ns_raw = ''; $tokens = explode( ':', $s, 2 ); if( count( $tokens ) === 2 ) { $ns_raw = $tokens[ 0 ]; $title_raw = $tokens[ 1 ]; } else { // no namespace? that's the main namespace! $ns_raw = ''; $title_raw = $tokens[ 0 ]; } // validate namespace $ns = $wiki->findNamespace( $ns_raw ); if( ! $ns ) { // that was the main namespace with a ':' in the title $ns = $wiki->getNamespace( 0 ); $title_raw = "$ns_raw:$title_raw"; } $title = new Title( $title_raw, $wiki ); return new self( $wiki, $ns, $title ); } } diff --git "a/include/class-mw\\Site.php" "b/include/class-mw\\Site.php" index 4882ce9..8d3c480 100644 --- "a/include/class-mw\\Site.php" +++ "b/include/class-mw\\Site.php" @@ -1,347 +1,410 @@ . # MediaWiki namespace mw; +/** + * A generic MediaWiki website + */ class Site { /** * A sort of $wgCapitalLinks * * @see https://www.mediawiki.org/wiki/Manual:$wgCapitalLinks */ const CAPITAL_LINKS = true; /** * MediaWiki API * * @var API */ private $api; /** * Site namespaces * * @var array Array of namespaces */ private $namespaces = []; /** * A sort of internal UID * * @var null|string E.g. 'enwiki' */ private $uid; + /** + * Public MediaWiki base URL + * + * e.g. 'https://www.mediawiki.org/wiki/' + * + * It's the base for every public page. + * + * @var string + */ + private $baseURL; + /** * Constructor * - * @param $url string MediaWiki API URL + * @param $api_url string MediaWiki API URL */ - public function __construct( $url ) { - $this->api = new API( $url ); + public function __construct( $api_url ) { + $this->api = new API( $api_url ); + + // just for laziness, to avoid some refactors. + // actually the user provides the API URL and not + // other stuff. So just use the API URL. + $this->guessBaseURLFromAPIURL( $api_url ); } /** * Create an API query with continuation handler * * @param $data array GET/POST data arguments * @return mw\APIQuery */ public function createQuery( $data ) { return $this->getApi()->createQuery( $data ); } /** * Make an HTTP GET request to the API * * @param $data array HTTP GET data * @return mixed API result */ public function fetch( $data ) { return $this->getApi()->fetch( $data ); } /** * Make an HTTP POST request to the API * * This method will call the API#login() method. * * @param $data array HTTP GET data * @return mixed API result */ public function post( $data ) { return $this->getApi()->post( $data ); } /** * Do an API edit request * * @param $data array API request data * @return mixed * @see https://www.mediawiki.org/wiki/API:Edit */ public function edit( $data ) { return $this->post( array_replace( $data, [ 'action' => 'edit', 'token' => $this->getToken( \mw\Tokens::CSRF ) ] ) ); } /** * Make an HTTP POST request to the API * * This method will call the API#login() method. * * @param array $data Array of ContentDisposition(s) * @return mixed API result */ public function postMultipart( $data ) { return $this->getApi()->postMultipart( $data ); } /** * Do an API upload request * * @param $data array API request data and ContentDisposition(s) * @return mixed * @see https://www.mediawiki.org/wiki/API:Upload */ public function upload( $data ) { return $this->postMultipart( array_replace( $data, [ 'action' => 'upload', 'token' => $this->getToken( \mw\Tokens::CSRF ) ] ) ); } /** * Check if I'm logged * * @return bool */ public function isLogged() { return $this->getApi()->isLogged(); } /** * Get the username used for the login (if any) * * @return string|null */ public function getUsername() { return $this->getApi()->getUsername(); } /** * Preload some tokens * * @return self */ public function preloadTokens( $tokens ) { $this->getApi()->preloadTokens( $tokens ); return $this; } /** * Get the value of a token * * @param $token string Token name * @return string Token value */ public function getToken( $token ) { return $this->getApi()->getToken( $token ); } /** * Invalidate a token * * @param $token string Token name * @return self */ public function invalidateToken( $token ) { $this->getApi()->invalidateToken( $token ); return $this; } /** * Make a login * * @see API#login() * @param $username string MediaWiki username * @param $password string MediaWiki password * @return self */ public function login( $username = null, $password = null ) { $this->getApi()->login( $username, $password ); return $this; } /** * Check if a certain namespace ID is registered * * @param int $id Namespace ID * @return bool */ public function hasNamespace( $id ) { return array_key_exists( $id, $this->namespaces ); } /** * Get the namespace related to the specified ID * * @param int $id Namespace ID * @return Ns Corresponding namespace */ public function getNamespace( $id ) { if( ! $this->hasNamespace( $id ) ) { throw new \Exception( sprintf( 'missing namespace %d', $id ) ); } return $this->namespaces[ $id ]; } /** * Set/overwrite a namespace * * @param $namespace Ns Namespace to be set/overwrited * @return self */ public function setNamespace( Ns $namespace ) { $id = $namespace->getID(); $this->namespaces[ $id ] = $namespace; return $this; } /** * Find a namespace by it's name * * @param $name string * @return object|false */ public function findNamespace( $name ) { $name = Ns::normalizeName( $name ); foreach( $this->namespaces as $ns ) { if( $ns->getName() === $name ) { return $ns; } } return false; } /** * Stupid shortcut for setting multiple namespaces * * @param $namespaces array Array of namespaces * @return self */ public function setNamespaces( $namespaces ) { foreach( $namespaces as $namespace ) { $this->setNamespace( $namespace ); } return $this; } /** * Set the UID * * @param string E.g. 'enwiki' * @return self */ public function setUID( $uid ) { $this->uid = $uid; return $this; } /** * Get internal MediaWiki API object * * @return API */ public function getApi() { return $this->api; } + /** + * Set the MediaWiki base URL + * + * Often it ends with '/wiki/' + * + * @param string $base_url + * @return self + */ + public function setBaseURL( $base_url ) { + $this->baseURL = $base_url; + return $this; + } + + /** + * Get the MediaWiki base URL + * + * Often it ends with '/wiki/' + * + * @return string + */ + public function getBaseURL() { + return $this->baseURL; + } + /** * Get the UID * * @return null|string e.g. 'enwiki' */ public function getUID() { return $this->uid; } /** * Create a Wikitext object * * @return Wikitext */ public function createWikitext( $wikitext = '' ) { return new Wikitext( $this, $wikitext ); } /** * Create a CompleteTitle object * * @param $title string Page title without namespace prefix * @param $ns int Namespace number * @return object */ public function createTitle( $title, $ns = 0 ) { return new CompleteTitle( $this, $this->getNamespace( $ns ), new Title( $title, $this ) ); } /** * Create a CompleteTitle object * * @param $s Page title with namespace prefix * @return object */ public function createTitleParsing( $s ) { return CompleteTitle::createParsingTitle( $this, $s ); } /** * Create a wikilink object * * @deprecate Unuseful, just use createTitleParsing()->createWikilink() * @return object */ public function createWikilink( CompleteTitle $title, $alias = null ) { return $title->createWikilink( $alias ); } /** * Check if in this wiki the first case is insensitive * * @TODO generalize * @return boolean */ public function hasCapitalLinks() { return static::CAPITAL_LINKS; } /** * Create from an API URL * * @param $url string MediaWiki API URL * @return self */ public static function createFromAPIURL( $url ) { return new static( $url ); } + + /** + * Guess the MediaWiki base URL from the API URL + * + * I know, I know, it's not that simple! Calm down. + * Anyway 99% of the world has the API in /w/api.php and the website at /wiki/. + * If you do not appreciate this, just call setBaseURL() manually. + * + * I do not want to start adding random arguments in the Site() constructor. + * + * I thought: a smart default plus the ability to override it should be effective. + * ...isn't it? + * + * @param string $api_url API URL + */ + protected function guessBaseURLFromAPIURL( $api_url ) { + + // I know, I know, it's not that simple + $this->setBaseURL( str_replace( '/w/api.php', '/wiki/', $api_url ) ); + } } diff --git "a/include/class-mw\\Title.php" "b/include/class-mw\\Title.php" index 3bf4606..2d957d0 100644 --- "a/include/class-mw\\Title.php" +++ "b/include/class-mw\\Title.php" @@ -1,52 +1,63 @@ . # MediaWiki namespace mw; /** * A page Title without a namespace. * * See also CompleteTitle class. */ class Title extends TitlePartCapitalized { private $site; /** * Constructor * * @param $name string * @param $site object */ public function __construct( $name, $site ) { parent::__construct( $name ); $this->site = $site; } + /** + * Get the {{SUBPAGENAME}} for this complete page title + * + * Returns 'asd' from 'The/great/asd' + * + * See https://www.mediawiki.org/wiki/Help:Magic_words + */ + public function getSubPageName() { + return basename( $this->get() ); + } + /** * Get a regex matching this title part * * @return string */ public function getRegex( $unused = null ) { return $this->site->hasCapitalLinks() ? $this->getRegexFirstCaseInsensitive() : parent::getRegex(); } }