1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\Page;
22
23use MediaWiki\DAO\WikiAwareEntityTrait;
24use Wikimedia\Assert\Assert;
25use Wikimedia\Assert\ParameterAssertionException;
26use Wikimedia\NonSerializable\NonSerializableTrait;
27
28/**
29 * Immutable value object representing a page identity.
30 *
31 * Instances of this class are expected to always represent proper pages, that is,
32 * pages that can at least potentially exist as editable pages on the wiki.
33 * This class cannot represent Special pages, interwiki links, section links, etc.
34 *
35 * Code that deserializes instances of PageIdentityValue must ensure that the original
36 * meaning of the "local" Wiki ID is preserved: When an instance of PageIdentityValue
37 * is created with self::LOCAL as the Wiki ID on one wiki, gets serialized,
38 * stored, and later read and unserialized on another wiki, the value of the Wiki ID
39 * must be adjusted to refer to the original wiki.
40 *
41 * @since 1.36
42 */
43class PageIdentityValue implements ProperPageIdentity {
44
45	/* Use JSON, but beware the note on serialization above. */
46	use NonSerializableTrait;
47	use WikiAwareEntityTrait;
48
49	/** @var int */
50	private $pageId;
51
52	/** @var int */
53	private $namespace;
54
55	/** @var string */
56	private $dbKey;
57
58	/** @var bool|string */
59	private $wikiId;
60
61	/**
62	 * @param int $pageId The ID of this page, or 0 if the page does not exist.
63	 * @param int $namespace A valid namespace ID. Validation is the caller's responsibility!
64	 * @param string $dbKey A valid DB key. Validation is the caller's responsibility!
65	 * @param string|bool $wikiId The Id of the wiki this page belongs to,
66	 *        or self::LOCAL for the local wiki.
67	 */
68	public function __construct( int $pageId, int $namespace, string $dbKey, $wikiId ) {
69		Assert::parameter( $pageId >= 0, '$pageId', 'must not be negative' );
70		Assert::parameter( $namespace >= 0, '$namespace', 'must not be negative' );
71		$this->assertWikiIdParam( $wikiId );
72
73		if ( $dbKey === '' ) {
74			throw new ParameterAssertionException(
75				'$dbKey',
76				'PageIdentityValue cannot be created for an empty title.'
77			);
78		}
79
80		// Don't be mad about spaces.
81		$dbKey = str_replace( ' ', '_', $dbKey );
82
83		// Not full validation, but catches commons issues:
84		if ( preg_match( '/[\s#|]/', $dbKey ) ) {
85			throw new ParameterAssertionException(
86				'$dbKey',
87				'PageIdentityValue contains a bad character: ' . $dbKey
88			);
89		}
90
91		$this->pageId = $pageId;
92		$this->wikiId = $wikiId;
93		$this->namespace = $namespace;
94		$this->dbKey = $dbKey;
95	}
96
97	/**
98	 * Get the ID of the wiki provided to the constructor.
99	 *
100	 * @return string|false
101	 */
102	public function getWikiId() {
103		return $this->wikiId;
104	}
105
106	/**
107	 * The numerical page ID provided to the constructor.
108	 *
109	 * @param string|false $wikiId The wiki ID expected by the caller.
110	 *        Omit if expecting the local wiki.
111	 *
112	 * @return int
113	 */
114	public function getId( $wikiId = self::LOCAL ): int {
115		$this->assertWiki( $wikiId );
116		return $this->pageId;
117	}
118
119	/**
120	 * Returns whether the page currently exists.
121	 * Returns true if getId() returns a value greater than zero.
122	 * @return bool
123	 */
124	public function exists(): bool {
125		return $this->getId( $this->wikiId ) > 0;
126	}
127
128	/**
129	 * @return bool always true
130	 */
131	public function canExist(): bool {
132		return true;
133	}
134
135	/**
136	 * @inheritDoc
137	 *
138	 * @return int
139	 */
140	public function getNamespace(): int {
141		return $this->namespace;
142	}
143
144	/**
145	 * @inheritDoc
146	 *
147	 * @return string
148	 */
149	public function getDBkey(): string {
150		return $this->dbKey;
151	}
152
153	/**
154	 * Returns a string representation of the title, for logging. This is purely informative
155	 * and must not be used programmatically.
156	 *
157	 * @return string
158	 */
159	public function __toString(): string {
160		$name = '#' . $this->pageId;
161
162		if ( $this->wikiId ) {
163			$name .= '@' . $this->wikiId;
164		}
165
166		return $name . ' [' . $this->namespace . ':' . $this->dbKey . ']';
167	}
168
169	/**
170	 * @param PageIdentity $other
171	 *
172	 * @return bool
173	 */
174	public function isSamePageAs( PageIdentity $other ) {
175		// NOTE: keep in sync with Title::isSamePageAs()!
176		// NOTE: keep in sync with WikiPage::isSamePageAs()!
177
178		$wikiId = $this->getWikiId();
179		if ( $other->getWikiId() !== $wikiId
180			|| $other->getId( $wikiId ) !== $this->getId( $wikiId ) ) {
181			return false;
182		}
183
184		if ( $this->getId( $wikiId ) === 0 ) {
185			if ( $other->getNamespace() !== $this->getNamespace()
186				|| $other->getDBkey() !== $this->getDBkey() ) {
187				return false;
188			}
189		}
190
191		return true;
192	}
193
194}
195