Merge "Revert "Use display name in category page subheadings if provided""
[lhc/web/wiklou.git] / includes / PageProps.php
1 <?php
2 /**
3 * Access to properties of a page.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22 use Wikimedia\ScopedCallback;
23
24 /**
25 * Gives access to properties of a page.
26 *
27 * @since 1.27
28 *
29 */
30 class PageProps {
31
32 /**
33 * @var PageProps
34 */
35 private static $instance;
36
37 /**
38 * Overrides the default instance of this class
39 * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
40 *
41 * If this method is used it MUST also be called with null after a test to ensure a new
42 * default instance is created next time getInstance is called.
43 *
44 * @since 1.27
45 *
46 * @param PageProps|null $store
47 *
48 * @return ScopedCallback to reset the overridden value
49 * @throws MWException
50 */
51 public static function overrideInstance( PageProps $store = null ) {
52 if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
53 throw new MWException(
54 'Cannot override ' . __CLASS__ . 'default instance in operation.'
55 );
56 }
57 $previousValue = self::$instance;
58 self::$instance = $store;
59 return new ScopedCallback( function() use ( $previousValue ) {
60 self::$instance = $previousValue;
61 } );
62 }
63
64 /**
65 * @return PageProps
66 */
67 public static function getInstance() {
68 if ( self::$instance === null ) {
69 self::$instance = new self();
70 }
71 return self::$instance;
72 }
73
74 /** Cache parameters */
75 const CACHE_TTL = 10; // integer; TTL in seconds
76 const CACHE_SIZE = 100; // integer; max cached pages
77
78 /** Property cache */
79 private $cache = null;
80
81 /**
82 * Create a PageProps object
83 */
84 private function __construct() {
85 $this->cache = new ProcessCacheLRU( self::CACHE_SIZE );
86 }
87
88 /**
89 * Ensure that cache has at least this size
90 * @param int $size
91 */
92 public function ensureCacheSize( $size ) {
93 if ( $this->cache->getSize() < $size ) {
94 $this->cache->resize( $size );
95 }
96 }
97
98 /**
99 * Given one or more Titles and one or more names of properties,
100 * returns an associative array mapping page ID to property value.
101 * Pages in the provided set of Titles that do not have a value for
102 * the given properties will not appear in the returned array. If a
103 * single Title is provided, it does not need to be passed in an array,
104 * but an array will always be returned. If a single property name is
105 * provided, it does not need to be passed in an array. In that case,
106 * an associative array mapping page ID to property value will be
107 * returned; otherwise, an associative array mapping page ID to
108 * an associative array mapping property name to property value will be
109 * returned. An empty array will be returned if no matching properties
110 * were found.
111 *
112 * @param Title[]|Title $titles
113 * @param string[]|string $propertyNames
114 * @return array associative array mapping page ID to property value
115 */
116 public function getProperties( $titles, $propertyNames ) {
117 if ( is_array( $propertyNames ) ) {
118 $gotArray = true;
119 } else {
120 $propertyNames = [ $propertyNames ];
121 $gotArray = false;
122 }
123
124 $values = [];
125 $goodIDs = $this->getGoodIDs( $titles );
126 $queryIDs = [];
127 foreach ( $goodIDs as $pageID ) {
128 foreach ( $propertyNames as $propertyName ) {
129 $propertyValue = $this->getCachedProperty( $pageID, $propertyName );
130 if ( $propertyValue === false ) {
131 $queryIDs[] = $pageID;
132 break;
133 } else {
134 if ( $gotArray ) {
135 $values[$pageID][$propertyName] = $propertyValue;
136 } else {
137 $values[$pageID] = $propertyValue;
138 }
139 }
140 }
141 }
142
143 if ( $queryIDs ) {
144 $dbr = wfGetDB( DB_REPLICA );
145 $result = $dbr->select(
146 'page_props',
147 [
148 'pp_page',
149 'pp_propname',
150 'pp_value'
151 ],
152 [
153 'pp_page' => $queryIDs,
154 'pp_propname' => $propertyNames
155 ],
156 __METHOD__
157 );
158
159 foreach ( $result as $row ) {
160 $pageID = $row->pp_page;
161 $propertyName = $row->pp_propname;
162 $propertyValue = $row->pp_value;
163 $this->cacheProperty( $pageID, $propertyName, $propertyValue );
164 if ( $gotArray ) {
165 $values[$pageID][$propertyName] = $propertyValue;
166 } else {
167 $values[$pageID] = $propertyValue;
168 }
169 }
170 }
171
172 return $values;
173 }
174
175 /**
176 * Get all page property values.
177 * Given one or more Titles, returns an associative array mapping page
178 * ID to an associative array mapping property names to property
179 * values. Pages in the provided set of Titles that do not have any
180 * properties will not appear in the returned array. If a single Title
181 * is provided, it does not need to be passed in an array, but an array
182 * will always be returned. An empty array will be returned if no
183 * matching properties were found.
184 *
185 * @param Title[]|Title $titles
186 * @return array associative array mapping page ID to property value array
187 */
188 public function getAllProperties( $titles ) {
189 $values = [];
190 $goodIDs = $this->getGoodIDs( $titles );
191 $queryIDs = [];
192 foreach ( $goodIDs as $pageID ) {
193 $pageProperties = $this->getCachedProperties( $pageID );
194 if ( $pageProperties === false ) {
195 $queryIDs[] = $pageID;
196 } else {
197 $values[$pageID] = $pageProperties;
198 }
199 }
200
201 if ( $queryIDs != [] ) {
202 $dbr = wfGetDB( DB_REPLICA );
203 $result = $dbr->select(
204 'page_props',
205 [
206 'pp_page',
207 'pp_propname',
208 'pp_value'
209 ],
210 [
211 'pp_page' => $queryIDs,
212 ],
213 __METHOD__
214 );
215
216 $currentPageID = 0;
217 $pageProperties = [];
218 foreach ( $result as $row ) {
219 $pageID = $row->pp_page;
220 if ( $currentPageID != $pageID ) {
221 if ( $pageProperties != [] ) {
222 $this->cacheProperties( $currentPageID, $pageProperties );
223 $values[$currentPageID] = $pageProperties;
224 }
225 $currentPageID = $pageID;
226 $pageProperties = [];
227 }
228 $pageProperties[$row->pp_propname] = $row->pp_value;
229 }
230 if ( $pageProperties != [] ) {
231 $this->cacheProperties( $pageID, $pageProperties );
232 $values[$pageID] = $pageProperties;
233 }
234 }
235
236 return $values;
237 }
238
239 /**
240 * @param Title[]|Title $titles
241 * @return array array of good page IDs
242 */
243 private function getGoodIDs( $titles ) {
244 $result = [];
245 if ( is_array( $titles ) ) {
246 foreach ( $titles as $title ) {
247 $pageID = $title->getArticleID();
248 if ( $pageID > 0 ) {
249 $result[] = $pageID;
250 }
251 }
252 } else {
253 $pageID = $titles->getArticleID();
254 if ( $pageID > 0 ) {
255 $result[] = $pageID;
256 }
257 }
258 return $result;
259 }
260
261 /**
262 * Get a property from the cache.
263 *
264 * @param int $pageID page ID of page being queried
265 * @param string $propertyName name of property being queried
266 * @return string|bool property value array or false if not found
267 */
268 private function getCachedProperty( $pageID, $propertyName ) {
269 if ( $this->cache->has( $pageID, $propertyName, self::CACHE_TTL ) ) {
270 return $this->cache->get( $pageID, $propertyName );
271 }
272 if ( $this->cache->has( 0, $pageID, self::CACHE_TTL ) ) {
273 $pageProperties = $this->cache->get( 0, $pageID );
274 if ( isset( $pageProperties[$propertyName] ) ) {
275 return $pageProperties[$propertyName];
276 }
277 }
278 return false;
279 }
280
281 /**
282 * Get properties from the cache.
283 *
284 * @param int $pageID page ID of page being queried
285 * @return string|bool property value array or false if not found
286 */
287 private function getCachedProperties( $pageID ) {
288 if ( $this->cache->has( 0, $pageID, self::CACHE_TTL ) ) {
289 return $this->cache->get( 0, $pageID );
290 }
291 return false;
292 }
293
294 /**
295 * Save a property to the cache.
296 *
297 * @param int $pageID page ID of page being cached
298 * @param string $propertyName name of property being cached
299 * @param mixed $propertyValue value of property
300 */
301 private function cacheProperty( $pageID, $propertyName, $propertyValue ) {
302 $this->cache->set( $pageID, $propertyName, $propertyValue );
303 }
304
305 /**
306 * Save properties to the cache.
307 *
308 * @param int $pageID page ID of page being cached
309 * @param string[] $pageProperties associative array of page properties to be cached
310 */
311 private function cacheProperties( $pageID, $pageProperties ) {
312 $this->cache->clear( $pageID );
313 $this->cache->set( 0, $pageID, $pageProperties );
314 }
315 }