1<?php
2
3namespace MediaWiki\Page;
4
5use DBAccessObjectUtils;
6use InvalidArgumentException;
7use MediaWiki\Linker\LinkTarget;
8use MediaWiki\Page\Hook\WikiPageFactoryHook;
9use stdClass;
10use Title;
11use TitleFactory;
12use WikiCategoryPage;
13use WikiFilePage;
14use Wikimedia\Rdbms\ILoadBalancer;
15use WikiPage;
16
17/**
18 * @since 1.36
19 */
20class WikiPageFactory {
21
22	/** @var TitleFactory */
23	private $titleFactory;
24
25	/** @var WikiPageFactoryHook */
26	private $wikiPageFactoryHookRunner;
27
28	/** @var ILoadBalancer */
29	private $loadBalancer;
30
31	/**
32	 * @param TitleFactory $titleFactory
33	 * @param WikiPageFactoryHook $wikiPageFactoryHookRunner
34	 * @param ILoadBalancer $loadBalancer
35	 */
36	public function __construct(
37		TitleFactory $titleFactory,
38		WikiPageFactoryHook $wikiPageFactoryHookRunner,
39		ILoadBalancer $loadBalancer
40	) {
41		$this->titleFactory = $titleFactory;
42		$this->wikiPageFactoryHookRunner = $wikiPageFactoryHookRunner;
43		$this->loadBalancer = $loadBalancer;
44	}
45
46	/**
47	 * Create a WikiPage object from a title.
48	 *
49	 * @param PageIdentity $pageIdentity
50	 *
51	 * @return WikiPage
52	 */
53	public function newFromTitle( PageIdentity $pageIdentity ) {
54		if ( $pageIdentity instanceof WikiPage ) {
55			return $pageIdentity;
56		}
57
58		if ( !$pageIdentity->canExist() ) {
59			throw new InvalidArgumentException(
60				"The given PageIdentity does not represent a proper page"
61			);
62		}
63
64		$ns = $pageIdentity->getNamespace();
65
66		// TODO: remove the need for casting to Title. We'll have to create a new hook to
67		//       replace the WikiPageFactory hook.
68		$title = Title::castFromPageIdentity( $pageIdentity );
69
70		$page = null;
71		if ( !$this->wikiPageFactoryHookRunner->onWikiPageFactory( $title, $page ) ) {
72			return $page;
73		}
74
75		switch ( $ns ) {
76			case NS_FILE:
77				$page = new WikiFilePage( $title );
78				break;
79			case NS_CATEGORY:
80				$page = new WikiCategoryPage( $title );
81				break;
82			default:
83				$page = new WikiPage( $title );
84		}
85
86		return $page;
87	}
88
89	/**
90	 * Create a WikiPage object from a link target.
91	 *
92	 * @param LinkTarget $title
93	 *
94	 * @return WikiPage
95	 */
96	public function newFromLinkTarget( LinkTarget $title ) {
97		return $this->newFromTitle( $this->titleFactory->newFromLinkTarget( $title ) );
98	}
99
100	/**
101	 * Create a WikiPage object from a database row
102	 *
103	 * @param stdClass $row Database row containing at least fields returned by getQueryInfo().
104	 * @param string|int $from Source of $data:
105	 *        - "fromdb" or WikiPage::READ_NORMAL: from a replica DB
106	 *        - "fromdbmaster" or WikiPage::READ_LATEST: from the master DB
107	 *        - "forupdate" or WikiPage::READ_LOCKING: from the master DB using SELECT FOR UPDATE
108	 *
109	 * @return WikiPage
110	 */
111	public function newFromRow( $row, $from = 'fromdb' ) {
112		$page = $this->newFromTitle( $this->titleFactory->newFromRow( $row ) );
113		$page->loadFromRow( $row, $from );
114		return $page;
115	}
116
117	/**
118	 * Create a WikiPage object from a page ID
119	 *
120	 * @param int $id Article ID to load
121	 * @param string|int $from One of the following values:
122	 *        - "fromdb" or WikiPage::READ_NORMAL to select from a replica DB
123	 *        - "fromdbmaster" or WikiPage::READ_LATEST to select from the master database
124	 *
125	 * @return WikiPage|null Null when no page exists with that ID
126	 */
127	public function newFromID( $id, $from = 'fromdb' ) {
128		// page ids are never 0 or negative, see T63166
129		if ( $id < 1 ) {
130			return null;
131		}
132
133		$from = WikiPage::convertSelectType( $from );
134		[ $index ] = DBAccessObjectUtils::getDBOptions( $from );
135		$db = $this->loadBalancer->getMaintenanceConnectionRef( $index );
136		$pageQuery = WikiPage::getQueryInfo();
137		$row = $db->selectRow(
138			$pageQuery['tables'], $pageQuery['fields'], [ 'page_id' => $id ], __METHOD__,
139			[], $pageQuery['joins']
140		);
141		if ( !$row ) {
142			return null;
143		}
144		return $this->newFromRow( $row, $from );
145	}
146
147}
148