diff --git a/include/generic/Singleton.php b/include/generic/Singleton.php index 9185fc3..2cb67ca 100644 --- a/include/generic/Singleton.php +++ b/include/generic/Singleton.php @@ -1,59 +1,66 @@ . # generic stuff namespace generic; /** * A singleton gives an #instance() method */ -trait Singleton { +class Singleton { + + /** + * Array of class instances by their class name + */ + private static $instances = []; /** * Get an instance of this class * + * The instance will be created only once. + * * @return self */ public static function instance() { - static $instance; - if( ! $instance ) { - $instance = static::create(); + $instance = self::$instances[ static::class ] ?: null; + if( !$instance ) { + self::$instances[ static::class ] = static::create(); } return $instance; } /** * Function to be overrided to create an instance of this class * * @return self */ protected static function create() { return new static(); } /** * Throw an usage error * @return never */ protected static function throwSingletonUsage() { throw new \Exception( sprintf( 'wrong singleton usage, you must call %s::instance()', static::class ) ); } } diff --git a/include/mw/Site.php b/include/mw/Site.php deleted file mode 100644 index b99f428..0000000 --- a/include/mw/Site.php +++ /dev/null @@ -1,410 +0,0 @@ -. - -# 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 $api_url string MediaWiki 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 CompleteTitle - */ - 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 CompleteTitle - */ - 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/mw/StaticSite.php b/include/mw/StaticSite.php index c84c150..c8bd14d 100644 --- a/include/mw/StaticSite.php +++ b/include/mw/StaticSite.php @@ -1,64 +1,475 @@ . # MediaWiki namespace mw; /** - * The behaviours for a static site + * A generic MediaWiki website * - * They are declared in a trait on order to be recycled it e.g. in mw\StaticWikibaseSite + * This class is designed to have just one instance of this site */ -trait StaticSiteTrait { +class StaticSite { - use \generic\Singleton; + /** + * 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; + + /** + * Array of class instances by their class name + */ + private static $instances = []; + + /** + * Constructor + * + * @param $api_url string MediaWiki 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 ); + } /** * @override */ protected static function create() { $site = static::createFromAPIURL( static::getApiURL() ); $site->setUID( static::UID ); // Set default namespaces foreach( Ns::defaultCanonicalNames() as $ns_id => $ns ) { $site->setNamespace( new Ns( $ns_id, $ns ) ); } return $site; } + /** + * Get an instance of this class + * + * The instance will be created only once. + * + * @return self + */ + public static function instance() { + + // try to check if we already have one instance + if( !array_key_exists( static::class, self::$instances ) ) { + + // let's create one instance, not calling the constructor directly + // but our static method designed for this purpose + self::$instances[ static::class ] = static::create(); + } + + // yeeh! now this exists + return self::$instances[ static::class ]; + } + /** * To be overloaded. * * @return string */ protected static function getApiURL() { return static::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 ); + } -/** - * A static site is a site that must be used statically - */ -class StaticSite extends Site { + /** + * 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 ); + } - use StaticSiteTrait; + /** + * 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 CompleteTitle + */ + 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 CompleteTitle + */ + 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 ) ); + } + + /** + * Throw an usage error + * @return never + */ + protected static function throwSingletonUsage() { + throw new \Exception( sprintf( + 'wrong singleton usage, you must call %s::instance()', + static::class + ) ); + } } diff --git a/include/mw/StaticWikibaseSite.php b/include/mw/StaticWikibaseSite.php index fd09544..7d63bad 100644 --- a/include/mw/StaticWikibaseSite.php +++ b/include/mw/StaticWikibaseSite.php @@ -1,28 +1,108 @@ . # MediaWiki namespace mw; /** - * A static MediaWiki site with Wikibase enabled + * A site with wikibase enabled */ -class StaticWikibaseSite extends WikibaseSite { +class StaticWikibaseSite extends StaticSite { - use \mw\StaticSiteTrait; + /** + * Fetch a single Wikidata entity using the wbgetentities API + * + * See: + * https://www.wikidata.org/w/api.php?action=help&modules=wbgetentities + * + * @param $entity_id string Entity Q-ID + * @param $data array Additional data such as [ 'props' => '..' ] + * Some available props: + * aliases, claims, datatype, descriptions, info, labels, sitelinks, sitelinks/urls + * @return wb\DataModel + */ + public function fetchSingleEntity( $entity_id, $data = [] ) { + + $data = array_replace( [ + 'action' => 'wbgetentities', + 'ids' => $entity_id, + ], $data ); + + $entity = $this->fetch( $data ); + if( ! isset( $entity->entities->{ $entity_id } ) ) { + throw new Exception( "$entity_id does not exist" ); + } + + return $this->createDataModelFromObject( $entity->entities->{ $entity_id } ); + } + + /** + * Edit a Wikidata entity using the wbeditentity API + * + * You do not need to send the CSRF 'token' and the 'action' parameters. + * + * See: + * https://www.wikidata.org/w/api.php?action=help&modules=wbeditentity + * + * @param $data array API data request (with some extensions) + * Allowed extensions: + * summary.pre Add something before the summary + * summary.post Add something after the summary + * @return mixed + */ + public function editEntity( $data = [] ) { + + // extends the API arguments adding a 'summary.pre' argument + if( isset( $data[ 'summary.pre' ] ) ) { + $data[ 'summary' ] = $data[ 'summary.pre' ] . $data[ 'summary' ]; + unset( $data[ 'summary.pre' ] ); + } + + // extends the API arguments adding a 'summary.post' argument + if( isset( $data[ 'summary.post' ] ) ) { + $data[ 'summary' ] .= $data[ 'summary.post' ]; + unset( $data[ 'summary.post' ] ); + } + + return $this->post( array_replace( [ + 'action' => 'wbeditentity', + 'token' => $this->getToken( \mw\Tokens::CSRF ), + ], $data ) ); + } + + /** + * Create an empty Wikibase data model related to this site + * + * @param $entity_id string Entity Q-ID + * @return DataModel + */ + public function createDataModel( $entity_id = null ) { + return new \wb\DataModel( $this, $entity_id ); + } + + /** + * Create an empty Wikibase data model related to this site + * + * @param $data object Single Entity object retrieved from wbgetentities API + * @return DataModel + */ + public function createDataModelFromObject( $data ) { + $id = $data->id; + return \wb\DataModel::createFromObject( $data, $this, $id ); + } } diff --git a/include/mw/WikibaseSite.php b/include/mw/WikibaseSite.php deleted file mode 100644 index 2cee77c..0000000 --- a/include/mw/WikibaseSite.php +++ /dev/null @@ -1,108 +0,0 @@ -. - -# MediaWiki -namespace mw; - -/** - * A site with wikibase enabled - */ -class WikibaseSite extends Site { - - /** - * Fetch a single Wikidata entity using the wbgetentities API - * - * See: - * https://www.wikidata.org/w/api.php?action=help&modules=wbgetentities - * - * @param $entity_id string Entity Q-ID - * @param $data array Additional data such as [ 'props' => '..' ] - * Some available props: - * aliases, claims, datatype, descriptions, info, labels, sitelinks, sitelinks/urls - * @return wb\DataModel - */ - public function fetchSingleEntity( $entity_id, $data = [] ) { - - $data = array_replace( [ - 'action' => 'wbgetentities', - 'ids' => $entity_id, - ], $data ); - - $entity = $this->fetch( $data ); - if( ! isset( $entity->entities->{ $entity_id } ) ) { - throw new Exception( "$entity_id does not exist" ); - } - - return $this->createDataModelFromObject( $entity->entities->{ $entity_id } ); - } - - /** - * Edit a Wikidata entity using the wbeditentity API - * - * You do not need to send the CSRF 'token' and the 'action' parameters. - * - * See: - * https://www.wikidata.org/w/api.php?action=help&modules=wbeditentity - * - * @param $data array API data request (with some extensions) - * Allowed extensions: - * summary.pre Add something before the summary - * summary.post Add something after the summary - * @return mixed - */ - public function editEntity( $data = [] ) { - - // extends the API arguments adding a 'summary.pre' argument - if( isset( $data[ 'summary.pre' ] ) ) { - $data[ 'summary' ] = $data[ 'summary.pre' ] . $data[ 'summary' ]; - unset( $data[ 'summary.pre' ] ); - } - - // extends the API arguments adding a 'summary.post' argument - if( isset( $data[ 'summary.post' ] ) ) { - $data[ 'summary' ] .= $data[ 'summary.post' ]; - unset( $data[ 'summary.post' ] ); - } - - return $this->post( array_replace( [ - 'action' => 'wbeditentity', - 'token' => $this->getToken( \mw\Tokens::CSRF ), - ], $data ) ); - } - - /** - * Create an empty Wikibase data model related to this site - * - * @param $entity_id string Entity Q-ID - * @return DataModel - */ - public function createDataModel( $entity_id = null ) { - return new \wb\DataModel( $this, $entity_id ); - } - - /** - * Create an empty Wikibase data model related to this site - * - * @param $data object Single Entity object retrieved from wbgetentities API - * @return DataModel - */ - public function createDataModelFromObject( $data ) { - $id = $data->id; - return \wb\DataModel::createFromObject( $data, $this, $id ); - } - -}