MediaWiki REL1_31
WatchedItemStoreUnitTest.php
Go to the documentation of this file.
1<?php
4use Wikimedia\ScopedCallback;
5use Wikimedia\TestingAccessWrapper;
6
13
17 private function getMockDb() {
18 return $this->createMock( IDatabase::class );
19 }
20
24 private function getMockLoadBalancer(
25 $mockDb,
26 $expectedConnectionType = null
27 ) {
28 $mock = $this->getMockBuilder( LoadBalancer::class )
29 ->disableOriginalConstructor()
30 ->getMock();
31 if ( $expectedConnectionType !== null ) {
32 $mock->expects( $this->any() )
33 ->method( 'getConnectionRef' )
34 ->with( $expectedConnectionType )
35 ->will( $this->returnValue( $mockDb ) );
36 } else {
37 $mock->expects( $this->any() )
38 ->method( 'getConnectionRef' )
39 ->will( $this->returnValue( $mockDb ) );
40 }
41 return $mock;
42 }
43
47 private function getMockCache() {
48 $mock = $this->getMockBuilder( HashBagOStuff::class )
49 ->disableOriginalConstructor()
50 ->setMethods( [ 'get', 'set', 'delete', 'makeKey' ] )
51 ->getMock();
52 $mock->expects( $this->any() )
53 ->method( 'makeKey' )
54 ->will( $this->returnCallback( function () {
55 return implode( ':', func_get_args() );
56 } ) );
57 return $mock;
58 }
59
63 private function getMockReadOnlyMode( $readOnly = false ) {
64 $mock = $this->getMockBuilder( ReadOnlyMode::class )
65 ->disableOriginalConstructor()
66 ->getMock();
67 $mock->expects( $this->any() )
68 ->method( 'isReadOnly' )
69 ->will( $this->returnValue( $readOnly ) );
70 return $mock;
71 }
72
77 private function getMockNonAnonUserWithId( $id ) {
78 $mock = $this->createMock( User::class );
79 $mock->expects( $this->any() )
80 ->method( 'isAnon' )
81 ->will( $this->returnValue( false ) );
82 $mock->expects( $this->any() )
83 ->method( 'getId' )
84 ->will( $this->returnValue( $id ) );
85 return $mock;
86 }
87
91 private function getAnonUser() {
92 return User::newFromName( 'Anon_User' );
93 }
94
95 private function getFakeRow( array $rowValues ) {
96 $fakeRow = new stdClass();
97 foreach ( $rowValues as $valueName => $value ) {
98 $fakeRow->$valueName = $value;
99 }
100 return $fakeRow;
101 }
102
103 private function newWatchedItemStore( LoadBalancer $loadBalancer, HashBagOStuff $cache,
104 ReadOnlyMode $readOnlyMode
105 ) {
106 return new WatchedItemStore(
107 $loadBalancer,
108 $cache,
109 $readOnlyMode,
110 1000
111 );
112 }
113
114 public function testClearWatchedItems() {
115 $user = $this->getMockNonAnonUserWithId( 7 );
116
117 $mockDb = $this->getMockDb();
118 $mockDb->expects( $this->once() )
119 ->method( 'selectField' )
120 ->with(
121 'watchlist',
122 'COUNT(*)',
123 [
124 'wl_user' => $user->getId(),
125 ],
126 $this->isType( 'string' )
127 )
128 ->will( $this->returnValue( 12 ) );
129 $mockDb->expects( $this->once() )
130 ->method( 'delete' )
131 ->with(
132 'watchlist',
133 [ 'wl_user' => 7 ],
134 $this->isType( 'string' )
135 );
136
137 $mockCache = $this->getMockCache();
138 $mockCache->expects( $this->never() )->method( 'get' );
139 $mockCache->expects( $this->never() )->method( 'set' );
140 $mockCache->expects( $this->once() )
141 ->method( 'delete' )
142 ->with( 'RM-KEY' );
143
144 $store = $this->newWatchedItemStore(
145 $this->getMockLoadBalancer( $mockDb ),
146 $mockCache,
147 $this->getMockReadOnlyMode()
148 );
149 TestingAccessWrapper::newFromObject( $store )
150 ->cacheIndex = [ 0 => [ 'F' => [ 7 => 'RM-KEY', 9 => 'KEEP-KEY' ] ] ];
151
152 $this->assertTrue( $store->clearUserWatchedItems( $user ) );
153 }
154
156 $user = $this->getMockNonAnonUserWithId( 7 );
157
158 $mockDb = $this->getMockDb();
159 $mockDb->expects( $this->once() )
160 ->method( 'selectField' )
161 ->with(
162 'watchlist',
163 'COUNT(*)',
164 [
165 'wl_user' => $user->getId(),
166 ],
167 $this->isType( 'string' )
168 )
169 ->will( $this->returnValue( 99999 ) );
170
171 $mockCache = $this->getMockCache();
172 $mockCache->expects( $this->never() )->method( 'get' );
173 $mockCache->expects( $this->never() )->method( 'set' );
174 $mockCache->expects( $this->never() )->method( 'delete' );
175
176 $store = $this->newWatchedItemStore(
177 $this->getMockLoadBalancer( $mockDb ),
178 $mockCache,
179 $this->getMockReadOnlyMode()
180 );
181
182 $this->assertFalse( $store->clearUserWatchedItems( $user ) );
183 }
184
185 public function testCountWatchedItems() {
186 $user = $this->getMockNonAnonUserWithId( 1 );
187
188 $mockDb = $this->getMockDb();
189 $mockDb->expects( $this->exactly( 1 ) )
190 ->method( 'selectField' )
191 ->with(
192 'watchlist',
193 'COUNT(*)',
194 [
195 'wl_user' => $user->getId(),
196 ],
197 $this->isType( 'string' )
198 )
199 ->will( $this->returnValue( '12' ) );
200
201 $mockCache = $this->getMockCache();
202 $mockCache->expects( $this->never() )->method( 'get' );
203 $mockCache->expects( $this->never() )->method( 'set' );
204 $mockCache->expects( $this->never() )->method( 'delete' );
205
206 $store = $this->newWatchedItemStore(
207 $this->getMockLoadBalancer( $mockDb ),
208 $mockCache,
209 $this->getMockReadOnlyMode()
210 );
211
212 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
213 }
214
215 public function testCountWatchers() {
216 $titleValue = new TitleValue( 0, 'SomeDbKey' );
217
218 $mockDb = $this->getMockDb();
219 $mockDb->expects( $this->exactly( 1 ) )
220 ->method( 'selectField' )
221 ->with(
222 'watchlist',
223 'COUNT(*)',
224 [
225 'wl_namespace' => $titleValue->getNamespace(),
226 'wl_title' => $titleValue->getDBkey(),
227 ],
228 $this->isType( 'string' )
229 )
230 ->will( $this->returnValue( '7' ) );
231
232 $mockCache = $this->getMockCache();
233 $mockCache->expects( $this->never() )->method( 'get' );
234 $mockCache->expects( $this->never() )->method( 'set' );
235 $mockCache->expects( $this->never() )->method( 'delete' );
236
237 $store = $this->newWatchedItemStore(
238 $this->getMockLoadBalancer( $mockDb ),
239 $mockCache,
240 $this->getMockReadOnlyMode()
241 );
242
243 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
244 }
245
246 public function testCountWatchersMultiple() {
247 $titleValues = [
248 new TitleValue( 0, 'SomeDbKey' ),
249 new TitleValue( 0, 'OtherDbKey' ),
250 new TitleValue( 1, 'AnotherDbKey' ),
251 ];
252
253 $mockDb = $this->getMockDb();
254
255 $dbResult = [
256 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
257 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
258 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
259 ),
260 ];
261 $mockDb->expects( $this->once() )
262 ->method( 'makeWhereFrom2d' )
263 ->with(
264 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
265 $this->isType( 'string' ),
266 $this->isType( 'string' )
267 )
268 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
269 $mockDb->expects( $this->once() )
270 ->method( 'select' )
271 ->with(
272 'watchlist',
273 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
274 [ 'makeWhereFrom2d return value' ],
275 $this->isType( 'string' ),
276 [
277 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
278 ]
279 )
280 ->will(
281 $this->returnValue( $dbResult )
282 );
283
284 $mockCache = $this->getMockCache();
285 $mockCache->expects( $this->never() )->method( 'get' );
286 $mockCache->expects( $this->never() )->method( 'set' );
287 $mockCache->expects( $this->never() )->method( 'delete' );
288
289 $store = $this->newWatchedItemStore(
290 $this->getMockLoadBalancer( $mockDb ),
291 $mockCache,
292 $this->getMockReadOnlyMode()
293 );
294
295 $expected = [
296 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
297 1 => [ 'AnotherDbKey' => 500 ],
298 ];
299 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
300 }
301
303 return [
304 [ 50 ],
305 [ "50; DROP TABLE watchlist;\n--" ],
306 ];
307 }
308
312 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
313 $titleValues = [
314 new TitleValue( 0, 'SomeDbKey' ),
315 new TitleValue( 0, 'OtherDbKey' ),
316 new TitleValue( 1, 'AnotherDbKey' ),
317 ];
318
319 $mockDb = $this->getMockDb();
320
321 $dbResult = [
322 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
323 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
324 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
325 ),
326 ];
327 $mockDb->expects( $this->once() )
328 ->method( 'makeWhereFrom2d' )
329 ->with(
330 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
331 $this->isType( 'string' ),
332 $this->isType( 'string' )
333 )
334 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
335 $mockDb->expects( $this->once() )
336 ->method( 'select' )
337 ->with(
338 'watchlist',
339 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
340 [ 'makeWhereFrom2d return value' ],
341 $this->isType( 'string' ),
342 [
343 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
344 'HAVING' => 'COUNT(*) >= 50',
345 ]
346 )
347 ->will(
348 $this->returnValue( $dbResult )
349 );
350
351 $mockCache = $this->getMockCache();
352 $mockCache->expects( $this->never() )->method( 'get' );
353 $mockCache->expects( $this->never() )->method( 'set' );
354 $mockCache->expects( $this->never() )->method( 'delete' );
355
356 $store = $this->newWatchedItemStore(
357 $this->getMockLoadBalancer( $mockDb ),
358 $mockCache,
359 $this->getMockReadOnlyMode()
360 );
361
362 $expected = [
363 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
364 1 => [ 'AnotherDbKey' => 500 ],
365 ];
366 $this->assertEquals(
367 $expected,
368 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
369 );
370 }
371
372 public function testCountVisitingWatchers() {
373 $titleValue = new TitleValue( 0, 'SomeDbKey' );
374
375 $mockDb = $this->getMockDb();
376 $mockDb->expects( $this->exactly( 1 ) )
377 ->method( 'selectField' )
378 ->with(
379 'watchlist',
380 'COUNT(*)',
381 [
382 'wl_namespace' => $titleValue->getNamespace(),
383 'wl_title' => $titleValue->getDBkey(),
384 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
385 ],
386 $this->isType( 'string' )
387 )
388 ->will( $this->returnValue( '7' ) );
389 $mockDb->expects( $this->exactly( 1 ) )
390 ->method( 'addQuotes' )
391 ->will( $this->returnCallback( function ( $value ) {
392 return "'$value'";
393 } ) );
394 $mockDb->expects( $this->exactly( 1 ) )
395 ->method( 'timestamp' )
396 ->will( $this->returnCallback( function ( $value ) {
397 return 'TS' . $value . 'TS';
398 } ) );
399
400 $mockCache = $this->getMockCache();
401 $mockCache->expects( $this->never() )->method( 'set' );
402 $mockCache->expects( $this->never() )->method( 'get' );
403 $mockCache->expects( $this->never() )->method( 'delete' );
404
405 $store = $this->newWatchedItemStore(
406 $this->getMockLoadBalancer( $mockDb ),
407 $mockCache,
408 $this->getMockReadOnlyMode()
409 );
410
411 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
412 }
413
415 $titleValuesWithThresholds = [
416 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
417 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
418 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
419 ];
420
421 $dbResult = [
422 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
423 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
424 $this->getFakeRow(
425 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
426 ),
427 ];
428 $mockDb = $this->getMockDb();
429 $mockDb->expects( $this->exactly( 2 * 3 ) )
430 ->method( 'addQuotes' )
431 ->will( $this->returnCallback( function ( $value ) {
432 return "'$value'";
433 } ) );
434 $mockDb->expects( $this->exactly( 3 ) )
435 ->method( 'timestamp' )
436 ->will( $this->returnCallback( function ( $value ) {
437 return 'TS' . $value . 'TS';
438 } ) );
439 $mockDb->expects( $this->any() )
440 ->method( 'makeList' )
441 ->with(
442 $this->isType( 'array' ),
443 $this->isType( 'int' )
444 )
445 ->will( $this->returnCallback( function ( $a, $conj ) {
446 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
447 return implode( $sqlConj, array_map( function ( $s ) {
448 return '(' . $s . ')';
449 }, $a
450 ) );
451 } ) );
452 $mockDb->expects( $this->never() )
453 ->method( 'makeWhereFrom2d' );
454
455 $expectedCond =
456 '((wl_namespace = 0) AND (' .
457 "(((wl_title = 'SomeDbKey') AND (" .
458 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
459 ')) OR (' .
460 "(wl_title = 'OtherDbKey') AND (" .
461 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
462 '))))' .
463 ') OR ((wl_namespace = 1) AND (' .
464 "(((wl_title = 'AnotherDbKey') AND (".
465 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
466 ')))))';
467 $mockDb->expects( $this->once() )
468 ->method( 'select' )
469 ->with(
470 'watchlist',
471 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
472 $expectedCond,
473 $this->isType( 'string' ),
474 [
475 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
476 ]
477 )
478 ->will(
479 $this->returnValue( $dbResult )
480 );
481
482 $mockCache = $this->getMockCache();
483 $mockCache->expects( $this->never() )->method( 'get' );
484 $mockCache->expects( $this->never() )->method( 'set' );
485 $mockCache->expects( $this->never() )->method( 'delete' );
486
487 $store = $this->newWatchedItemStore(
488 $this->getMockLoadBalancer( $mockDb ),
489 $mockCache,
490 $this->getMockReadOnlyMode()
491 );
492
493 $expected = [
494 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
495 1 => [ 'AnotherDbKey' => 500 ],
496 ];
497 $this->assertEquals(
498 $expected,
499 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
500 );
501 }
502
504 $titleValuesWithThresholds = [
505 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
506 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
507 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
508 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
509 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
510 ];
511
512 $dbResult = [
513 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
514 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
515 $this->getFakeRow(
516 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
517 ),
518 $this->getFakeRow(
519 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '100' ]
520 ),
521 $this->getFakeRow(
522 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '200' ]
523 ),
524 ];
525 $mockDb = $this->getMockDb();
526 $mockDb->expects( $this->exactly( 2 * 3 ) )
527 ->method( 'addQuotes' )
528 ->will( $this->returnCallback( function ( $value ) {
529 return "'$value'";
530 } ) );
531 $mockDb->expects( $this->exactly( 3 ) )
532 ->method( 'timestamp' )
533 ->will( $this->returnCallback( function ( $value ) {
534 return 'TS' . $value . 'TS';
535 } ) );
536 $mockDb->expects( $this->any() )
537 ->method( 'makeList' )
538 ->with(
539 $this->isType( 'array' ),
540 $this->isType( 'int' )
541 )
542 ->will( $this->returnCallback( function ( $a, $conj ) {
543 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
544 return implode( $sqlConj, array_map( function ( $s ) {
545 return '(' . $s . ')';
546 }, $a
547 ) );
548 } ) );
549 $mockDb->expects( $this->once() )
550 ->method( 'makeWhereFrom2d' )
551 ->with(
552 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
553 $this->isType( 'string' ),
554 $this->isType( 'string' )
555 )
556 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
557
558 $expectedCond =
559 '((wl_namespace = 0) AND (' .
560 "(((wl_title = 'SomeDbKey') AND (" .
561 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
562 ')) OR (' .
563 "(wl_title = 'OtherDbKey') AND (" .
564 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
565 '))))' .
566 ') OR ((wl_namespace = 1) AND (' .
567 "(((wl_title = 'AnotherDbKey') AND (".
568 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
569 '))))' .
570 ') OR ' .
571 '(makeWhereFrom2d return value)';
572 $mockDb->expects( $this->once() )
573 ->method( 'select' )
574 ->with(
575 'watchlist',
576 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
577 $expectedCond,
578 $this->isType( 'string' ),
579 [
580 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
581 ]
582 )
583 ->will(
584 $this->returnValue( $dbResult )
585 );
586
587 $mockCache = $this->getMockCache();
588 $mockCache->expects( $this->never() )->method( 'get' );
589 $mockCache->expects( $this->never() )->method( 'set' );
590 $mockCache->expects( $this->never() )->method( 'delete' );
591
592 $store = $this->newWatchedItemStore(
593 $this->getMockLoadBalancer( $mockDb ),
594 $mockCache,
595 $this->getMockReadOnlyMode()
596 );
597
598 $expected = [
599 0 => [
600 'SomeDbKey' => 100, 'OtherDbKey' => 300,
601 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
602 ],
603 1 => [ 'AnotherDbKey' => 500 ],
604 ];
605 $this->assertEquals(
606 $expected,
607 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
608 );
609 }
610
615 $titleValuesWithThresholds = [
616 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
617 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
618 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
619 ];
620
621 $mockDb = $this->getMockDb();
622 $mockDb->expects( $this->any() )
623 ->method( 'makeList' )
624 ->will( $this->returnValue( 'makeList return value' ) );
625 $mockDb->expects( $this->once() )
626 ->method( 'select' )
627 ->with(
628 'watchlist',
629 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
630 'makeList return value',
631 $this->isType( 'string' ),
632 [
633 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
634 'HAVING' => 'COUNT(*) >= 50',
635 ]
636 )
637 ->will(
638 $this->returnValue( [] )
639 );
640
641 $mockCache = $this->getMockCache();
642 $mockCache->expects( $this->never() )->method( 'get' );
643 $mockCache->expects( $this->never() )->method( 'set' );
644 $mockCache->expects( $this->never() )->method( 'delete' );
645
646 $store = $this->newWatchedItemStore(
647 $this->getMockLoadBalancer( $mockDb ),
648 $mockCache,
649 $this->getMockReadOnlyMode()
650 );
651
652 $expected = [
653 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
654 1 => [ 'AnotherDbKey' => 0 ],
655 ];
656 $this->assertEquals(
657 $expected,
658 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
659 );
660 }
661
663 $user = $this->getMockNonAnonUserWithId( 1 );
664
665 $mockDb = $this->getMockDb();
666 $mockDb->expects( $this->exactly( 1 ) )
667 ->method( 'selectRowCount' )
668 ->with(
669 'watchlist',
670 '1',
671 [
672 "wl_notificationtimestamp IS NOT NULL",
673 'wl_user' => 1,
674 ],
675 $this->isType( 'string' )
676 )
677 ->will( $this->returnValue( '9' ) );
678
679 $mockCache = $this->getMockCache();
680 $mockCache->expects( $this->never() )->method( 'set' );
681 $mockCache->expects( $this->never() )->method( 'get' );
682 $mockCache->expects( $this->never() )->method( 'delete' );
683
684 $store = $this->newWatchedItemStore(
685 $this->getMockLoadBalancer( $mockDb ),
686 $mockCache,
687 $this->getMockReadOnlyMode()
688 );
689
690 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
691 }
692
697 $user = $this->getMockNonAnonUserWithId( 1 );
698
699 $mockDb = $this->getMockDb();
700 $mockDb->expects( $this->exactly( 1 ) )
701 ->method( 'selectRowCount' )
702 ->with(
703 'watchlist',
704 '1',
705 [
706 "wl_notificationtimestamp IS NOT NULL",
707 'wl_user' => 1,
708 ],
709 $this->isType( 'string' ),
710 [ 'LIMIT' => 50 ]
711 )
712 ->will( $this->returnValue( '50' ) );
713
714 $mockCache = $this->getMockCache();
715 $mockCache->expects( $this->never() )->method( 'set' );
716 $mockCache->expects( $this->never() )->method( 'get' );
717 $mockCache->expects( $this->never() )->method( 'delete' );
718
719 $store = $this->newWatchedItemStore(
720 $this->getMockLoadBalancer( $mockDb ),
721 $mockCache,
722 $this->getMockReadOnlyMode()
723 );
724
725 $this->assertSame(
726 true,
727 $store->countUnreadNotifications( $user, $limit )
728 );
729 }
730
735 $user = $this->getMockNonAnonUserWithId( 1 );
736
737 $mockDb = $this->getMockDb();
738 $mockDb->expects( $this->exactly( 1 ) )
739 ->method( 'selectRowCount' )
740 ->with(
741 'watchlist',
742 '1',
743 [
744 "wl_notificationtimestamp IS NOT NULL",
745 'wl_user' => 1,
746 ],
747 $this->isType( 'string' ),
748 [ 'LIMIT' => 50 ]
749 )
750 ->will( $this->returnValue( '9' ) );
751
752 $mockCache = $this->getMockCache();
753 $mockCache->expects( $this->never() )->method( 'set' );
754 $mockCache->expects( $this->never() )->method( 'get' );
755 $mockCache->expects( $this->never() )->method( 'delete' );
756
757 $store = $this->newWatchedItemStore(
758 $this->getMockLoadBalancer( $mockDb ),
759 $mockCache,
760 $this->getMockReadOnlyMode()
761 );
762
763 $this->assertEquals(
764 9,
765 $store->countUnreadNotifications( $user, $limit )
766 );
767 }
768
770 $mockDb = $this->getMockDb();
771 $mockDb->expects( $this->once() )
772 ->method( 'select' )
773 ->with(
774 'watchlist',
775 [
776 'wl_user',
777 'wl_notificationtimestamp',
778 ],
779 [
780 'wl_namespace' => 0,
781 'wl_title' => 'Old_Title',
782 ],
783 'WatchedItemStore::duplicateEntry',
784 [ 'FOR UPDATE' ]
785 )
786 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
787
788 $store = $this->newWatchedItemStore(
789 $this->getMockLoadBalancer( $mockDb ),
790 $this->getMockCache(),
791 $this->getMockReadOnlyMode()
792 );
793
794 $store->duplicateEntry(
795 Title::newFromText( 'Old_Title' ),
796 Title::newFromText( 'New_Title' )
797 );
798 }
799
801 $fakeRows = [
802 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
803 $this->getFakeRow( [ 'wl_user' => '2', 'wl_notificationtimestamp' => null ] ),
804 ];
805
806 $mockDb = $this->getMockDb();
807 $mockDb->expects( $this->at( 0 ) )
808 ->method( 'select' )
809 ->with(
810 'watchlist',
811 [
812 'wl_user',
813 'wl_notificationtimestamp',
814 ],
815 [
816 'wl_namespace' => 0,
817 'wl_title' => 'Old_Title',
818 ]
819 )
820 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
821 $mockDb->expects( $this->at( 1 ) )
822 ->method( 'replace' )
823 ->with(
824 'watchlist',
825 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
826 [
827 [
828 'wl_user' => 1,
829 'wl_namespace' => 0,
830 'wl_title' => 'New_Title',
831 'wl_notificationtimestamp' => '20151212010101',
832 ],
833 [
834 'wl_user' => 2,
835 'wl_namespace' => 0,
836 'wl_title' => 'New_Title',
837 'wl_notificationtimestamp' => null,
838 ],
839 ],
840 $this->isType( 'string' )
841 );
842
843 $mockCache = $this->getMockCache();
844 $mockCache->expects( $this->never() )->method( 'get' );
845 $mockCache->expects( $this->never() )->method( 'delete' );
846
847 $store = $this->newWatchedItemStore(
848 $this->getMockLoadBalancer( $mockDb ),
849 $mockCache,
850 $this->getMockReadOnlyMode()
851 );
852
853 $store->duplicateEntry(
854 Title::newFromText( 'Old_Title' ),
855 Title::newFromText( 'New_Title' )
856 );
857 }
858
860 $mockDb = $this->getMockDb();
861 $mockDb->expects( $this->at( 0 ) )
862 ->method( 'select' )
863 ->with(
864 'watchlist',
865 [
866 'wl_user',
867 'wl_notificationtimestamp',
868 ],
869 [
870 'wl_namespace' => 0,
871 'wl_title' => 'Old_Title',
872 ]
873 )
874 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
875 $mockDb->expects( $this->at( 1 ) )
876 ->method( 'select' )
877 ->with(
878 'watchlist',
879 [
880 'wl_user',
881 'wl_notificationtimestamp',
882 ],
883 [
884 'wl_namespace' => 1,
885 'wl_title' => 'Old_Title',
886 ]
887 )
888 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
889
890 $mockCache = $this->getMockCache();
891 $mockCache->expects( $this->never() )->method( 'get' );
892 $mockCache->expects( $this->never() )->method( 'delete' );
893
894 $store = $this->newWatchedItemStore(
895 $this->getMockLoadBalancer( $mockDb ),
896 $mockCache,
897 $this->getMockReadOnlyMode()
898 );
899
900 $store->duplicateAllAssociatedEntries(
901 Title::newFromText( 'Old_Title' ),
902 Title::newFromText( 'New_Title' )
903 );
904 }
905
906 public function provideLinkTargetPairs() {
907 return [
908 [ Title::newFromText( 'Old_Title' ), Title::newFromText( 'New_Title' ) ],
909 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
910 ];
911 }
912
917 LinkTarget $oldTarget,
918 LinkTarget $newTarget
919 ) {
920 $fakeRows = [
921 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
922 ];
923
924 $mockDb = $this->getMockDb();
925 $mockDb->expects( $this->at( 0 ) )
926 ->method( 'select' )
927 ->with(
928 'watchlist',
929 [
930 'wl_user',
931 'wl_notificationtimestamp',
932 ],
933 [
934 'wl_namespace' => $oldTarget->getNamespace(),
935 'wl_title' => $oldTarget->getDBkey(),
936 ]
937 )
938 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
939 $mockDb->expects( $this->at( 1 ) )
940 ->method( 'replace' )
941 ->with(
942 'watchlist',
943 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
944 [
945 [
946 'wl_user' => 1,
947 'wl_namespace' => $newTarget->getNamespace(),
948 'wl_title' => $newTarget->getDBkey(),
949 'wl_notificationtimestamp' => '20151212010101',
950 ],
951 ],
952 $this->isType( 'string' )
953 );
954 $mockDb->expects( $this->at( 2 ) )
955 ->method( 'select' )
956 ->with(
957 'watchlist',
958 [
959 'wl_user',
960 'wl_notificationtimestamp',
961 ],
962 [
963 'wl_namespace' => $oldTarget->getNamespace() + 1,
964 'wl_title' => $oldTarget->getDBkey(),
965 ]
966 )
967 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
968 $mockDb->expects( $this->at( 3 ) )
969 ->method( 'replace' )
970 ->with(
971 'watchlist',
972 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
973 [
974 [
975 'wl_user' => 1,
976 'wl_namespace' => $newTarget->getNamespace() + 1,
977 'wl_title' => $newTarget->getDBkey(),
978 'wl_notificationtimestamp' => '20151212010101',
979 ],
980 ],
981 $this->isType( 'string' )
982 );
983
984 $mockCache = $this->getMockCache();
985 $mockCache->expects( $this->never() )->method( 'get' );
986 $mockCache->expects( $this->never() )->method( 'delete' );
987
988 $store = $this->newWatchedItemStore(
989 $this->getMockLoadBalancer( $mockDb ),
990 $mockCache,
991 $this->getMockReadOnlyMode()
992 );
993
994 $store->duplicateAllAssociatedEntries(
995 $oldTarget,
996 $newTarget
997 );
998 }
999
1001 $mockDb = $this->getMockDb();
1002 $mockDb->expects( $this->once() )
1003 ->method( 'insert' )
1004 ->with(
1005 'watchlist',
1006 [
1007 [
1008 'wl_user' => 1,
1009 'wl_namespace' => 0,
1010 'wl_title' => 'Some_Page',
1011 'wl_notificationtimestamp' => null,
1012 ]
1013 ]
1014 );
1015
1016 $mockCache = $this->getMockCache();
1017 $mockCache->expects( $this->once() )
1018 ->method( 'delete' )
1019 ->with( '0:Some_Page:1' );
1020
1021 $store = $this->newWatchedItemStore(
1022 $this->getMockLoadBalancer( $mockDb ),
1023 $mockCache,
1024 $this->getMockReadOnlyMode()
1025 );
1026
1027 $store->addWatch(
1028 $this->getMockNonAnonUserWithId( 1 ),
1029 Title::newFromText( 'Some_Page' )
1030 );
1031 }
1032
1033 public function testAddWatch_anonymousUser() {
1034 $mockDb = $this->getMockDb();
1035 $mockDb->expects( $this->never() )
1036 ->method( 'insert' );
1037
1038 $mockCache = $this->getMockCache();
1039 $mockCache->expects( $this->never() )
1040 ->method( 'delete' );
1041
1042 $store = $this->newWatchedItemStore(
1043 $this->getMockLoadBalancer( $mockDb ),
1044 $mockCache,
1045 $this->getMockReadOnlyMode()
1046 );
1047
1048 $store->addWatch(
1049 $this->getAnonUser(),
1050 Title::newFromText( 'Some_Page' )
1051 );
1052 }
1053
1055 $store = $this->newWatchedItemStore(
1056 $this->getMockLoadBalancer( $this->getMockDb() ),
1057 $this->getMockCache(),
1058 $this->getMockReadOnlyMode( true )
1059 );
1060
1061 $this->assertFalse(
1062 $store->addWatchBatchForUser(
1063 $this->getMockNonAnonUserWithId( 1 ),
1064 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1065 )
1066 );
1067 }
1068
1070 $mockDb = $this->getMockDb();
1071 $mockDb->expects( $this->once() )
1072 ->method( 'insert' )
1073 ->with(
1074 'watchlist',
1075 [
1076 [
1077 'wl_user' => 1,
1078 'wl_namespace' => 0,
1079 'wl_title' => 'Some_Page',
1080 'wl_notificationtimestamp' => null,
1081 ],
1082 [
1083 'wl_user' => 1,
1084 'wl_namespace' => 1,
1085 'wl_title' => 'Some_Page',
1086 'wl_notificationtimestamp' => null,
1087 ]
1088 ]
1089 );
1090
1091 $mockCache = $this->getMockCache();
1092 $mockCache->expects( $this->exactly( 2 ) )
1093 ->method( 'delete' );
1094 $mockCache->expects( $this->at( 1 ) )
1095 ->method( 'delete' )
1096 ->with( '0:Some_Page:1' );
1097 $mockCache->expects( $this->at( 3 ) )
1098 ->method( 'delete' )
1099 ->with( '1:Some_Page:1' );
1100
1101 $store = $this->newWatchedItemStore(
1102 $this->getMockLoadBalancer( $mockDb ),
1103 $mockCache,
1104 $this->getMockReadOnlyMode()
1105 );
1106
1107 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1108
1109 $this->assertTrue(
1110 $store->addWatchBatchForUser(
1111 $mockUser,
1112 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1113 )
1114 );
1115 }
1116
1118 $mockDb = $this->getMockDb();
1119 $mockDb->expects( $this->never() )
1120 ->method( 'insert' );
1121
1122 $mockCache = $this->getMockCache();
1123 $mockCache->expects( $this->never() )
1124 ->method( 'delete' );
1125
1126 $store = $this->newWatchedItemStore(
1127 $this->getMockLoadBalancer( $mockDb ),
1128 $mockCache,
1129 $this->getMockReadOnlyMode()
1130 );
1131
1132 $this->assertFalse(
1133 $store->addWatchBatchForUser(
1134 $this->getAnonUser(),
1135 [ new TitleValue( 0, 'Other_Page' ) ]
1136 )
1137 );
1138 }
1139
1141 $user = $this->getMockNonAnonUserWithId( 1 );
1142 $mockDb = $this->getMockDb();
1143 $mockDb->expects( $this->never() )
1144 ->method( 'insert' );
1145
1146 $mockCache = $this->getMockCache();
1147 $mockCache->expects( $this->never() )
1148 ->method( 'delete' );
1149
1150 $store = $this->newWatchedItemStore(
1151 $this->getMockLoadBalancer( $mockDb ),
1152 $mockCache,
1153 $this->getMockReadOnlyMode()
1154 );
1155
1156 $this->assertTrue(
1157 $store->addWatchBatchForUser( $user, [] )
1158 );
1159 }
1160
1162 $mockDb = $this->getMockDb();
1163 $mockDb->expects( $this->once() )
1164 ->method( 'selectRow' )
1165 ->with(
1166 'watchlist',
1167 'wl_notificationtimestamp',
1168 [
1169 'wl_user' => 1,
1170 'wl_namespace' => 0,
1171 'wl_title' => 'SomeDbKey',
1172 ]
1173 )
1174 ->will( $this->returnValue(
1175 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1176 ) );
1177
1178 $mockCache = $this->getMockCache();
1179 $mockCache->expects( $this->once() )
1180 ->method( 'set' )
1181 ->with(
1182 '0:SomeDbKey:1'
1183 );
1184
1185 $store = $this->newWatchedItemStore(
1186 $this->getMockLoadBalancer( $mockDb ),
1187 $mockCache,
1188 $this->getMockReadOnlyMode()
1189 );
1190
1191 $watchedItem = $store->loadWatchedItem(
1192 $this->getMockNonAnonUserWithId( 1 ),
1193 new TitleValue( 0, 'SomeDbKey' )
1194 );
1195 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
1196 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1197 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1198 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1199 }
1200
1201 public function testLoadWatchedItem_noItem() {
1202 $mockDb = $this->getMockDb();
1203 $mockDb->expects( $this->once() )
1204 ->method( 'selectRow' )
1205 ->with(
1206 'watchlist',
1207 'wl_notificationtimestamp',
1208 [
1209 'wl_user' => 1,
1210 'wl_namespace' => 0,
1211 'wl_title' => 'SomeDbKey',
1212 ]
1213 )
1214 ->will( $this->returnValue( [] ) );
1215
1216 $mockCache = $this->getMockCache();
1217 $mockCache->expects( $this->never() )->method( 'get' );
1218 $mockCache->expects( $this->never() )->method( 'delete' );
1219
1220 $store = $this->newWatchedItemStore(
1221 $this->getMockLoadBalancer( $mockDb ),
1222 $mockCache,
1223 $this->getMockReadOnlyMode()
1224 );
1225
1226 $this->assertFalse(
1227 $store->loadWatchedItem(
1228 $this->getMockNonAnonUserWithId( 1 ),
1229 new TitleValue( 0, 'SomeDbKey' )
1230 )
1231 );
1232 }
1233
1235 $mockDb = $this->getMockDb();
1236 $mockDb->expects( $this->never() )
1237 ->method( 'selectRow' );
1238
1239 $mockCache = $this->getMockCache();
1240 $mockCache->expects( $this->never() )->method( 'get' );
1241 $mockCache->expects( $this->never() )->method( 'delete' );
1242
1243 $store = $this->newWatchedItemStore(
1244 $this->getMockLoadBalancer( $mockDb ),
1245 $mockCache,
1246 $this->getMockReadOnlyMode()
1247 );
1248
1249 $this->assertFalse(
1250 $store->loadWatchedItem(
1251 $this->getAnonUser(),
1252 new TitleValue( 0, 'SomeDbKey' )
1253 )
1254 );
1255 }
1256
1258 $mockDb = $this->getMockDb();
1259 $mockDb->expects( $this->once() )
1260 ->method( 'delete' )
1261 ->with(
1262 'watchlist',
1263 [
1264 'wl_user' => 1,
1265 'wl_namespace' => 0,
1266 'wl_title' => 'SomeDbKey',
1267 ]
1268 );
1269 $mockDb->expects( $this->once() )
1270 ->method( 'affectedRows' )
1271 ->will( $this->returnValue( 1 ) );
1272
1273 $mockCache = $this->getMockCache();
1274 $mockCache->expects( $this->never() )->method( 'get' );
1275 $mockCache->expects( $this->once() )
1276 ->method( 'delete' )
1277 ->with( '0:SomeDbKey:1' );
1278
1279 $store = $this->newWatchedItemStore(
1280 $this->getMockLoadBalancer( $mockDb ),
1281 $mockCache,
1282 $this->getMockReadOnlyMode()
1283 );
1284
1285 $this->assertTrue(
1286 $store->removeWatch(
1287 $this->getMockNonAnonUserWithId( 1 ),
1288 new TitleValue( 0, 'SomeDbKey' )
1289 )
1290 );
1291 }
1292
1293 public function testRemoveWatch_noItem() {
1294 $mockDb = $this->getMockDb();
1295 $mockDb->expects( $this->once() )
1296 ->method( 'delete' )
1297 ->with(
1298 'watchlist',
1299 [
1300 'wl_user' => 1,
1301 'wl_namespace' => 0,
1302 'wl_title' => 'SomeDbKey',
1303 ]
1304 );
1305 $mockDb->expects( $this->once() )
1306 ->method( 'affectedRows' )
1307 ->will( $this->returnValue( 0 ) );
1308
1309 $mockCache = $this->getMockCache();
1310 $mockCache->expects( $this->never() )->method( 'get' );
1311 $mockCache->expects( $this->once() )
1312 ->method( 'delete' )
1313 ->with( '0:SomeDbKey:1' );
1314
1315 $store = $this->newWatchedItemStore(
1316 $this->getMockLoadBalancer( $mockDb ),
1317 $mockCache,
1318 $this->getMockReadOnlyMode()
1319 );
1320
1321 $this->assertFalse(
1322 $store->removeWatch(
1323 $this->getMockNonAnonUserWithId( 1 ),
1324 new TitleValue( 0, 'SomeDbKey' )
1325 )
1326 );
1327 }
1328
1330 $mockDb = $this->getMockDb();
1331 $mockDb->expects( $this->never() )
1332 ->method( 'delete' );
1333
1334 $mockCache = $this->getMockCache();
1335 $mockCache->expects( $this->never() )->method( 'get' );
1336 $mockCache->expects( $this->never() )
1337 ->method( 'delete' );
1338
1339 $store = $this->newWatchedItemStore(
1340 $this->getMockLoadBalancer( $mockDb ),
1341 $mockCache,
1342 $this->getMockReadOnlyMode()
1343 );
1344
1345 $this->assertFalse(
1346 $store->removeWatch(
1347 $this->getAnonUser(),
1348 new TitleValue( 0, 'SomeDbKey' )
1349 )
1350 );
1351 }
1352
1354 $mockDb = $this->getMockDb();
1355 $mockDb->expects( $this->once() )
1356 ->method( 'selectRow' )
1357 ->with(
1358 'watchlist',
1359 'wl_notificationtimestamp',
1360 [
1361 'wl_user' => 1,
1362 'wl_namespace' => 0,
1363 'wl_title' => 'SomeDbKey',
1364 ]
1365 )
1366 ->will( $this->returnValue(
1367 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1368 ) );
1369
1370 $mockCache = $this->getMockCache();
1371 $mockCache->expects( $this->never() )->method( 'delete' );
1372 $mockCache->expects( $this->once() )
1373 ->method( 'get' )
1374 ->with(
1375 '0:SomeDbKey:1'
1376 )
1377 ->will( $this->returnValue( null ) );
1378 $mockCache->expects( $this->once() )
1379 ->method( 'set' )
1380 ->with(
1381 '0:SomeDbKey:1'
1382 );
1383
1384 $store = $this->newWatchedItemStore(
1385 $this->getMockLoadBalancer( $mockDb ),
1386 $mockCache,
1387 $this->getMockReadOnlyMode()
1388 );
1389
1390 $watchedItem = $store->getWatchedItem(
1391 $this->getMockNonAnonUserWithId( 1 ),
1392 new TitleValue( 0, 'SomeDbKey' )
1393 );
1394 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
1395 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1396 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1397 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1398 }
1399
1401 $mockDb = $this->getMockDb();
1402 $mockDb->expects( $this->never() )
1403 ->method( 'selectRow' );
1404
1405 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1406 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1407 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1408
1409 $mockCache = $this->getMockCache();
1410 $mockCache->expects( $this->never() )->method( 'delete' );
1411 $mockCache->expects( $this->never() )->method( 'set' );
1412 $mockCache->expects( $this->once() )
1413 ->method( 'get' )
1414 ->with(
1415 '0:SomeDbKey:1'
1416 )
1417 ->will( $this->returnValue( $cachedItem ) );
1418
1419 $store = $this->newWatchedItemStore(
1420 $this->getMockLoadBalancer( $mockDb ),
1421 $mockCache,
1422 $this->getMockReadOnlyMode()
1423 );
1424
1425 $this->assertEquals(
1426 $cachedItem,
1427 $store->getWatchedItem(
1428 $mockUser,
1429 $linkTarget
1430 )
1431 );
1432 }
1433
1434 public function testGetWatchedItem_noItem() {
1435 $mockDb = $this->getMockDb();
1436 $mockDb->expects( $this->once() )
1437 ->method( 'selectRow' )
1438 ->with(
1439 'watchlist',
1440 'wl_notificationtimestamp',
1441 [
1442 'wl_user' => 1,
1443 'wl_namespace' => 0,
1444 'wl_title' => 'SomeDbKey',
1445 ]
1446 )
1447 ->will( $this->returnValue( [] ) );
1448
1449 $mockCache = $this->getMockCache();
1450 $mockCache->expects( $this->never() )->method( 'set' );
1451 $mockCache->expects( $this->never() )->method( 'delete' );
1452 $mockCache->expects( $this->once() )
1453 ->method( 'get' )
1454 ->with( '0:SomeDbKey:1' )
1455 ->will( $this->returnValue( false ) );
1456
1457 $store = $this->newWatchedItemStore(
1458 $this->getMockLoadBalancer( $mockDb ),
1459 $mockCache,
1460 $this->getMockReadOnlyMode()
1461 );
1462
1463 $this->assertFalse(
1464 $store->getWatchedItem(
1465 $this->getMockNonAnonUserWithId( 1 ),
1466 new TitleValue( 0, 'SomeDbKey' )
1467 )
1468 );
1469 }
1470
1472 $mockDb = $this->getMockDb();
1473 $mockDb->expects( $this->never() )
1474 ->method( 'selectRow' );
1475
1476 $mockCache = $this->getMockCache();
1477 $mockCache->expects( $this->never() )->method( 'set' );
1478 $mockCache->expects( $this->never() )->method( 'get' );
1479 $mockCache->expects( $this->never() )->method( 'delete' );
1480
1481 $store = $this->newWatchedItemStore(
1482 $this->getMockLoadBalancer( $mockDb ),
1483 $mockCache,
1484 $this->getMockReadOnlyMode()
1485 );
1486
1487 $this->assertFalse(
1488 $store->getWatchedItem(
1489 $this->getAnonUser(),
1490 new TitleValue( 0, 'SomeDbKey' )
1491 )
1492 );
1493 }
1494
1495 public function testGetWatchedItemsForUser() {
1496 $mockDb = $this->getMockDb();
1497 $mockDb->expects( $this->once() )
1498 ->method( 'select' )
1499 ->with(
1500 'watchlist',
1501 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1502 [ 'wl_user' => 1 ]
1503 )
1504 ->will( $this->returnValue( [
1505 $this->getFakeRow( [
1506 'wl_namespace' => 0,
1507 'wl_title' => 'Foo1',
1508 'wl_notificationtimestamp' => '20151212010101',
1509 ] ),
1510 $this->getFakeRow( [
1511 'wl_namespace' => 1,
1512 'wl_title' => 'Foo2',
1513 'wl_notificationtimestamp' => null,
1514 ] ),
1515 ] ) );
1516
1517 $mockCache = $this->getMockCache();
1518 $mockCache->expects( $this->never() )->method( 'delete' );
1519 $mockCache->expects( $this->never() )->method( 'get' );
1520 $mockCache->expects( $this->never() )->method( 'set' );
1521
1522 $store = $this->newWatchedItemStore(
1523 $this->getMockLoadBalancer( $mockDb ),
1524 $mockCache,
1525 $this->getMockReadOnlyMode()
1526 );
1527 $user = $this->getMockNonAnonUserWithId( 1 );
1528
1529 $watchedItems = $store->getWatchedItemsForUser( $user );
1530
1531 $this->assertInternalType( 'array', $watchedItems );
1532 $this->assertCount( 2, $watchedItems );
1533 foreach ( $watchedItems as $watchedItem ) {
1534 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
1535 }
1536 $this->assertEquals(
1537 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1538 $watchedItems[0]
1539 );
1540 $this->assertEquals(
1541 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1542 $watchedItems[1]
1543 );
1544 }
1545
1546 public function provideDbTypes() {
1547 return [
1548 [ false, DB_REPLICA ],
1549 [ true, DB_MASTER ],
1550 ];
1551 }
1552
1556 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1557 $mockDb = $this->getMockDb();
1558 $mockCache = $this->getMockCache();
1559 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1560 $user = $this->getMockNonAnonUserWithId( 1 );
1561
1562 $mockDb->expects( $this->once() )
1563 ->method( 'select' )
1564 ->with(
1565 'watchlist',
1566 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1567 [ 'wl_user' => 1 ],
1568 $this->isType( 'string' ),
1569 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1570 )
1571 ->will( $this->returnValue( [] ) );
1572
1573 $store = $this->newWatchedItemStore(
1574 $mockLoadBalancer,
1575 $mockCache,
1576 $this->getMockReadOnlyMode()
1577 );
1578
1579 $watchedItems = $store->getWatchedItemsForUser(
1580 $user,
1581 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore::SORT_ASC ]
1582 );
1583 $this->assertEquals( [], $watchedItems );
1584 }
1585
1587 $store = $this->newWatchedItemStore(
1588 $this->getMockLoadBalancer( $this->getMockDb() ),
1589 $this->getMockCache(),
1590 $this->getMockReadOnlyMode()
1591 );
1592
1593 $this->setExpectedException( InvalidArgumentException::class );
1594 $store->getWatchedItemsForUser(
1595 $this->getMockNonAnonUserWithId( 1 ),
1596 [ 'sort' => 'foo' ]
1597 );
1598 }
1599
1601 $mockDb = $this->getMockDb();
1602 $mockDb->expects( $this->once() )
1603 ->method( 'selectRow' )
1604 ->with(
1605 'watchlist',
1606 'wl_notificationtimestamp',
1607 [
1608 'wl_user' => 1,
1609 'wl_namespace' => 0,
1610 'wl_title' => 'SomeDbKey',
1611 ]
1612 )
1613 ->will( $this->returnValue(
1614 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1615 ) );
1616
1617 $mockCache = $this->getMockCache();
1618 $mockCache->expects( $this->never() )->method( 'delete' );
1619 $mockCache->expects( $this->once() )
1620 ->method( 'get' )
1621 ->with( '0:SomeDbKey:1' )
1622 ->will( $this->returnValue( false ) );
1623 $mockCache->expects( $this->once() )
1624 ->method( 'set' )
1625 ->with(
1626 '0:SomeDbKey:1'
1627 );
1628
1629 $store = $this->newWatchedItemStore(
1630 $this->getMockLoadBalancer( $mockDb ),
1631 $mockCache,
1632 $this->getMockReadOnlyMode()
1633 );
1634
1635 $this->assertTrue(
1636 $store->isWatched(
1637 $this->getMockNonAnonUserWithId( 1 ),
1638 new TitleValue( 0, 'SomeDbKey' )
1639 )
1640 );
1641 }
1642
1643 public function testIsWatchedItem_noItem() {
1644 $mockDb = $this->getMockDb();
1645 $mockDb->expects( $this->once() )
1646 ->method( 'selectRow' )
1647 ->with(
1648 'watchlist',
1649 'wl_notificationtimestamp',
1650 [
1651 'wl_user' => 1,
1652 'wl_namespace' => 0,
1653 'wl_title' => 'SomeDbKey',
1654 ]
1655 )
1656 ->will( $this->returnValue( [] ) );
1657
1658 $mockCache = $this->getMockCache();
1659 $mockCache->expects( $this->never() )->method( 'set' );
1660 $mockCache->expects( $this->never() )->method( 'delete' );
1661 $mockCache->expects( $this->once() )
1662 ->method( 'get' )
1663 ->with( '0:SomeDbKey:1' )
1664 ->will( $this->returnValue( false ) );
1665
1666 $store = $this->newWatchedItemStore(
1667 $this->getMockLoadBalancer( $mockDb ),
1668 $mockCache,
1669 $this->getMockReadOnlyMode()
1670 );
1671
1672 $this->assertFalse(
1673 $store->isWatched(
1674 $this->getMockNonAnonUserWithId( 1 ),
1675 new TitleValue( 0, 'SomeDbKey' )
1676 )
1677 );
1678 }
1679
1681 $mockDb = $this->getMockDb();
1682 $mockDb->expects( $this->never() )
1683 ->method( 'selectRow' );
1684
1685 $mockCache = $this->getMockCache();
1686 $mockCache->expects( $this->never() )->method( 'set' );
1687 $mockCache->expects( $this->never() )->method( 'get' );
1688 $mockCache->expects( $this->never() )->method( 'delete' );
1689
1690 $store = $this->newWatchedItemStore(
1691 $this->getMockLoadBalancer( $mockDb ),
1692 $mockCache,
1693 $this->getMockReadOnlyMode()
1694 );
1695
1696 $this->assertFalse(
1697 $store->isWatched(
1698 $this->getAnonUser(),
1699 new TitleValue( 0, 'SomeDbKey' )
1700 )
1701 );
1702 }
1703
1705 $targets = [
1706 new TitleValue( 0, 'SomeDbKey' ),
1707 new TitleValue( 1, 'AnotherDbKey' ),
1708 ];
1709
1710 $mockDb = $this->getMockDb();
1711 $dbResult = [
1712 $this->getFakeRow( [
1713 'wl_namespace' => '0',
1714 'wl_title' => 'SomeDbKey',
1715 'wl_notificationtimestamp' => '20151212010101',
1716 ] ),
1717 $this->getFakeRow(
1718 [
1719 'wl_namespace' => '1',
1720 'wl_title' => 'AnotherDbKey',
1721 'wl_notificationtimestamp' => null,
1722 ]
1723 ),
1724 ];
1725
1726 $mockDb->expects( $this->once() )
1727 ->method( 'makeWhereFrom2d' )
1728 ->with(
1729 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1730 $this->isType( 'string' ),
1731 $this->isType( 'string' )
1732 )
1733 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1734 $mockDb->expects( $this->once() )
1735 ->method( 'select' )
1736 ->with(
1737 'watchlist',
1738 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1739 [
1740 'makeWhereFrom2d return value',
1741 'wl_user' => 1
1742 ],
1743 $this->isType( 'string' )
1744 )
1745 ->will( $this->returnValue( $dbResult ) );
1746
1747 $mockCache = $this->getMockCache();
1748 $mockCache->expects( $this->exactly( 2 ) )
1749 ->method( 'get' )
1750 ->withConsecutive(
1751 [ '0:SomeDbKey:1' ],
1752 [ '1:AnotherDbKey:1' ]
1753 )
1754 ->will( $this->returnValue( null ) );
1755 $mockCache->expects( $this->never() )->method( 'set' );
1756 $mockCache->expects( $this->never() )->method( 'delete' );
1757
1758 $store = $this->newWatchedItemStore(
1759 $this->getMockLoadBalancer( $mockDb ),
1760 $mockCache,
1761 $this->getMockReadOnlyMode()
1762 );
1763
1764 $this->assertEquals(
1765 [
1766 0 => [ 'SomeDbKey' => '20151212010101', ],
1767 1 => [ 'AnotherDbKey' => null, ],
1768 ],
1769 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1770 );
1771 }
1772
1774 $targets = [
1775 new TitleValue( 0, 'OtherDbKey' ),
1776 ];
1777
1778 $mockDb = $this->getMockDb();
1779
1780 $mockDb->expects( $this->once() )
1781 ->method( 'makeWhereFrom2d' )
1782 ->with(
1783 [ [ 'OtherDbKey' => 1 ] ],
1784 $this->isType( 'string' ),
1785 $this->isType( 'string' )
1786 )
1787 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1788 $mockDb->expects( $this->once() )
1789 ->method( 'select' )
1790 ->with(
1791 'watchlist',
1792 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1793 [
1794 'makeWhereFrom2d return value',
1795 'wl_user' => 1
1796 ],
1797 $this->isType( 'string' )
1798 )
1799 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1800
1801 $mockCache = $this->getMockCache();
1802 $mockCache->expects( $this->once() )
1803 ->method( 'get' )
1804 ->with( '0:OtherDbKey:1' )
1805 ->will( $this->returnValue( null ) );
1806 $mockCache->expects( $this->never() )->method( 'set' );
1807 $mockCache->expects( $this->never() )->method( 'delete' );
1808
1809 $store = $this->newWatchedItemStore(
1810 $this->getMockLoadBalancer( $mockDb ),
1811 $mockCache,
1812 $this->getMockReadOnlyMode()
1813 );
1814
1815 $this->assertEquals(
1816 [
1817 0 => [ 'OtherDbKey' => false, ],
1818 ],
1819 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1820 );
1821 }
1822
1824 $targets = [
1825 new TitleValue( 0, 'SomeDbKey' ),
1826 new TitleValue( 1, 'AnotherDbKey' ),
1827 ];
1828
1829 $user = $this->getMockNonAnonUserWithId( 1 );
1830 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1831
1832 $mockDb = $this->getMockDb();
1833
1834 $mockDb->expects( $this->once() )
1835 ->method( 'makeWhereFrom2d' )
1836 ->with(
1837 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1838 $this->isType( 'string' ),
1839 $this->isType( 'string' )
1840 )
1841 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1842 $mockDb->expects( $this->once() )
1843 ->method( 'select' )
1844 ->with(
1845 'watchlist',
1846 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1847 [
1848 'makeWhereFrom2d return value',
1849 'wl_user' => 1
1850 ],
1851 $this->isType( 'string' )
1852 )
1853 ->will( $this->returnValue( [
1854 $this->getFakeRow(
1855 [ 'wl_namespace' => '1', 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1856 )
1857 ] ) );
1858
1859 $mockCache = $this->getMockCache();
1860 $mockCache->expects( $this->at( 1 ) )
1861 ->method( 'get' )
1862 ->with( '0:SomeDbKey:1' )
1863 ->will( $this->returnValue( $cachedItem ) );
1864 $mockCache->expects( $this->at( 3 ) )
1865 ->method( 'get' )
1866 ->with( '1:AnotherDbKey:1' )
1867 ->will( $this->returnValue( null ) );
1868 $mockCache->expects( $this->never() )->method( 'set' );
1869 $mockCache->expects( $this->never() )->method( 'delete' );
1870
1871 $store = $this->newWatchedItemStore(
1872 $this->getMockLoadBalancer( $mockDb ),
1873 $mockCache,
1874 $this->getMockReadOnlyMode()
1875 );
1876
1877 $this->assertEquals(
1878 [
1879 0 => [ 'SomeDbKey' => '20151212010101', ],
1880 1 => [ 'AnotherDbKey' => null, ],
1881 ],
1882 $store->getNotificationTimestampsBatch( $user, $targets )
1883 );
1884 }
1885
1887 $targets = [
1888 new TitleValue( 0, 'SomeDbKey' ),
1889 new TitleValue( 1, 'AnotherDbKey' ),
1890 ];
1891
1892 $user = $this->getMockNonAnonUserWithId( 1 );
1893 $cachedItems = [
1894 new WatchedItem( $user, $targets[0], '20151212010101' ),
1895 new WatchedItem( $user, $targets[1], null ),
1896 ];
1897 $mockDb = $this->getMockDb();
1898 $mockDb->expects( $this->never() )->method( $this->anything() );
1899
1900 $mockCache = $this->getMockCache();
1901 $mockCache->expects( $this->at( 1 ) )
1902 ->method( 'get' )
1903 ->with( '0:SomeDbKey:1' )
1904 ->will( $this->returnValue( $cachedItems[0] ) );
1905 $mockCache->expects( $this->at( 3 ) )
1906 ->method( 'get' )
1907 ->with( '1:AnotherDbKey:1' )
1908 ->will( $this->returnValue( $cachedItems[1] ) );
1909 $mockCache->expects( $this->never() )->method( 'set' );
1910 $mockCache->expects( $this->never() )->method( 'delete' );
1911
1912 $store = $this->newWatchedItemStore(
1913 $this->getMockLoadBalancer( $mockDb ),
1914 $mockCache,
1915 $this->getMockReadOnlyMode()
1916 );
1917
1918 $this->assertEquals(
1919 [
1920 0 => [ 'SomeDbKey' => '20151212010101', ],
1921 1 => [ 'AnotherDbKey' => null, ],
1922 ],
1923 $store->getNotificationTimestampsBatch( $user, $targets )
1924 );
1925 }
1926
1928 $targets = [
1929 new TitleValue( 0, 'SomeDbKey' ),
1930 new TitleValue( 1, 'AnotherDbKey' ),
1931 ];
1932
1933 $mockDb = $this->getMockDb();
1934 $mockDb->expects( $this->never() )->method( $this->anything() );
1935
1936 $mockCache = $this->getMockCache();
1937 $mockCache->expects( $this->never() )->method( $this->anything() );
1938
1939 $store = $this->newWatchedItemStore(
1940 $this->getMockLoadBalancer( $mockDb ),
1941 $mockCache,
1942 $this->getMockReadOnlyMode()
1943 );
1944
1945 $this->assertEquals(
1946 [
1947 0 => [ 'SomeDbKey' => false, ],
1948 1 => [ 'AnotherDbKey' => false, ],
1949 ],
1950 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1951 );
1952 }
1953
1955 $mockDb = $this->getMockDb();
1956 $mockDb->expects( $this->never() )
1957 ->method( 'selectRow' );
1958
1959 $mockCache = $this->getMockCache();
1960 $mockCache->expects( $this->never() )->method( 'get' );
1961 $mockCache->expects( $this->never() )->method( 'set' );
1962 $mockCache->expects( $this->never() )->method( 'delete' );
1963
1964 $store = $this->newWatchedItemStore(
1965 $this->getMockLoadBalancer( $mockDb ),
1966 $mockCache,
1967 $this->getMockReadOnlyMode()
1968 );
1969
1970 $this->assertFalse(
1971 $store->resetNotificationTimestamp(
1972 $this->getAnonUser(),
1973 Title::newFromText( 'SomeDbKey' )
1974 )
1975 );
1976 }
1977
1979 $mockDb = $this->getMockDb();
1980 $mockDb->expects( $this->once() )
1981 ->method( 'selectRow' )
1982 ->with(
1983 'watchlist',
1984 'wl_notificationtimestamp',
1985 [
1986 'wl_user' => 1,
1987 'wl_namespace' => 0,
1988 'wl_title' => 'SomeDbKey',
1989 ]
1990 )
1991 ->will( $this->returnValue( [] ) );
1992
1993 $mockCache = $this->getMockCache();
1994 $mockCache->expects( $this->never() )->method( 'get' );
1995 $mockCache->expects( $this->never() )->method( 'set' );
1996 $mockCache->expects( $this->never() )->method( 'delete' );
1997
1998 $store = $this->newWatchedItemStore(
1999 $this->getMockLoadBalancer( $mockDb ),
2000 $mockCache,
2001 $this->getMockReadOnlyMode()
2002 );
2003
2004 $this->assertFalse(
2005 $store->resetNotificationTimestamp(
2006 $this->getMockNonAnonUserWithId( 1 ),
2007 Title::newFromText( 'SomeDbKey' )
2008 )
2009 );
2010 }
2011
2013 $user = $this->getMockNonAnonUserWithId( 1 );
2014 $title = Title::newFromText( 'SomeDbKey' );
2015
2016 $mockDb = $this->getMockDb();
2017 $mockDb->expects( $this->once() )
2018 ->method( 'selectRow' )
2019 ->with(
2020 'watchlist',
2021 'wl_notificationtimestamp',
2022 [
2023 'wl_user' => 1,
2024 'wl_namespace' => 0,
2025 'wl_title' => 'SomeDbKey',
2026 ]
2027 )
2028 ->will( $this->returnValue(
2029 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2030 ) );
2031
2032 $mockCache = $this->getMockCache();
2033 $mockCache->expects( $this->never() )->method( 'get' );
2034 $mockCache->expects( $this->once() )
2035 ->method( 'set' )
2036 ->with(
2037 '0:SomeDbKey:1',
2038 $this->isInstanceOf( WatchedItem::class )
2039 );
2040 $mockCache->expects( $this->once() )
2041 ->method( 'delete' )
2042 ->with( '0:SomeDbKey:1' );
2043
2044 $store = $this->newWatchedItemStore(
2045 $this->getMockLoadBalancer( $mockDb ),
2046 $mockCache,
2047 $this->getMockReadOnlyMode()
2048 );
2049
2050 // Note: This does not actually assert the job is correct
2051 $callableCallCounter = 0;
2052 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2053 $callableCallCounter++;
2054 $this->assertInternalType( 'callable', $callable );
2055 };
2056 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2057
2058 $this->assertTrue(
2059 $store->resetNotificationTimestamp(
2060 $user,
2061 $title
2062 )
2063 );
2064 $this->assertEquals( 1, $callableCallCounter );
2065
2066 ScopedCallback::consume( $scopedOverride );
2067 }
2068
2070 $user = $this->getMockNonAnonUserWithId( 1 );
2071 $title = Title::newFromText( 'SomeDbKey' );
2072
2073 $mockDb = $this->getMockDb();
2074 $mockDb->expects( $this->never() )
2075 ->method( 'selectRow' );
2076
2077 $mockCache = $this->getMockCache();
2078 $mockCache->expects( $this->never() )->method( 'get' );
2079 $mockCache->expects( $this->never() )->method( 'set' );
2080 $mockCache->expects( $this->once() )
2081 ->method( 'delete' )
2082 ->with( '0:SomeDbKey:1' );
2083
2084 $store = $this->newWatchedItemStore(
2085 $this->getMockLoadBalancer( $mockDb ),
2086 $mockCache,
2087 $this->getMockReadOnlyMode()
2088 );
2089
2090 // Note: This does not actually assert the job is correct
2091 $callableCallCounter = 0;
2092 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2093 $callableCallCounter++;
2094 $this->assertInternalType( 'callable', $callable );
2095 };
2096 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2097
2098 $this->assertTrue(
2099 $store->resetNotificationTimestamp(
2100 $user,
2101 $title,
2102 'force'
2103 )
2104 );
2105 $this->assertEquals( 1, $callableCallCounter );
2106
2107 ScopedCallback::consume( $scopedOverride );
2108 }
2109
2116 private function getMockTitle( $text, $ns = 0 ) {
2117 $title = $this->createMock( Title::class );
2118 $title->expects( $this->any() )
2119 ->method( 'getText' )
2120 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2121 $title->expects( $this->any() )
2122 ->method( 'getDbKey' )
2123 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2124 $title->expects( $this->any() )
2125 ->method( 'getNamespace' )
2126 ->will( $this->returnValue( $ns ) );
2127 return $title;
2128 }
2129
2130 private function verifyCallbackJob(
2131 $callback,
2132 LinkTarget $expectedTitle,
2133 $expectedUserId,
2134 callable $notificationTimestampCondition
2135 ) {
2136 $this->assertInternalType( 'callable', $callback );
2137
2138 $callbackReflector = new ReflectionFunction( $callback );
2139 $vars = $callbackReflector->getStaticVariables();
2140 $this->assertArrayHasKey( 'job', $vars );
2141 $this->assertInstanceOf( ActivityUpdateJob::class, $vars['job'] );
2142
2144 $job = $vars['job'];
2145 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2146 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2147
2148 $jobParams = $job->getParams();
2149 $this->assertArrayHasKey( 'type', $jobParams );
2150 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2151 $this->assertArrayHasKey( 'userid', $jobParams );
2152 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2153 $this->assertArrayHasKey( 'notifTime', $jobParams );
2154 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2155 }
2156
2158 $user = $this->getMockNonAnonUserWithId( 1 );
2159 $oldid = 22;
2160 $title = $this->getMockTitle( 'SomeTitle' );
2161 $title->expects( $this->once() )
2162 ->method( 'getNextRevisionID' )
2163 ->with( $oldid )
2164 ->will( $this->returnValue( false ) );
2165
2166 $mockDb = $this->getMockDb();
2167 $mockDb->expects( $this->never() )
2168 ->method( 'selectRow' );
2169
2170 $mockCache = $this->getMockCache();
2171 $mockCache->expects( $this->never() )->method( 'get' );
2172 $mockCache->expects( $this->never() )->method( 'set' );
2173 $mockCache->expects( $this->once() )
2174 ->method( 'delete' )
2175 ->with( '0:SomeTitle:1' );
2176
2177 $store = $this->newWatchedItemStore(
2178 $this->getMockLoadBalancer( $mockDb ),
2179 $mockCache,
2180 $this->getMockReadOnlyMode()
2181 );
2182
2183 $callableCallCounter = 0;
2184 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2185 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2186 $callableCallCounter++;
2187 $this->verifyCallbackJob(
2188 $callable,
2189 $title,
2190 $user->getId(),
2191 function ( $time ) {
2192 return $time === null;
2193 }
2194 );
2195 }
2196 );
2197
2198 $this->assertTrue(
2199 $store->resetNotificationTimestamp(
2200 $user,
2201 $title,
2202 'force',
2203 $oldid
2204 )
2205 );
2206 $this->assertEquals( 1, $callableCallCounter );
2207
2208 ScopedCallback::consume( $scopedOverride );
2209 }
2210
2212 $user = $this->getMockNonAnonUserWithId( 1 );
2213 $oldid = 22;
2214 $title = $this->getMockTitle( 'SomeDbKey' );
2215 $title->expects( $this->once() )
2216 ->method( 'getNextRevisionID' )
2217 ->with( $oldid )
2218 ->will( $this->returnValue( 33 ) );
2219
2220 $mockDb = $this->getMockDb();
2221 $mockDb->expects( $this->once() )
2222 ->method( 'selectRow' )
2223 ->with(
2224 'watchlist',
2225 'wl_notificationtimestamp',
2226 [
2227 'wl_user' => 1,
2228 'wl_namespace' => 0,
2229 'wl_title' => 'SomeDbKey',
2230 ]
2231 )
2232 ->will( $this->returnValue(
2233 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2234 ) );
2235
2236 $mockCache = $this->getMockCache();
2237 $mockCache->expects( $this->never() )->method( 'get' );
2238 $mockCache->expects( $this->once() )
2239 ->method( 'set' )
2240 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2241 $mockCache->expects( $this->once() )
2242 ->method( 'delete' )
2243 ->with( '0:SomeDbKey:1' );
2244
2245 $store = $this->newWatchedItemStore(
2246 $this->getMockLoadBalancer( $mockDb ),
2247 $mockCache,
2248 $this->getMockReadOnlyMode()
2249 );
2250
2251 $addUpdateCallCounter = 0;
2252 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2253 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2254 $addUpdateCallCounter++;
2255 $this->verifyCallbackJob(
2256 $callable,
2257 $title,
2258 $user->getId(),
2259 function ( $time ) {
2260 return $time !== null && $time > '20151212010101';
2261 }
2262 );
2263 }
2264 );
2265
2266 $getTimestampCallCounter = 0;
2267 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2268 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2269 $getTimestampCallCounter++;
2270 $this->assertEquals( $title, $titleParam );
2271 $this->assertEquals( $oldid, $oldidParam );
2272 }
2273 );
2274
2275 $this->assertTrue(
2276 $store->resetNotificationTimestamp(
2277 $user,
2278 $title,
2279 'force',
2280 $oldid
2281 )
2282 );
2283 $this->assertEquals( 1, $addUpdateCallCounter );
2284 $this->assertEquals( 1, $getTimestampCallCounter );
2285
2286 ScopedCallback::consume( $scopedOverrideDeferred );
2287 ScopedCallback::consume( $scopedOverrideRevision );
2288 }
2289
2291 $user = $this->getMockNonAnonUserWithId( 1 );
2292 $oldid = 22;
2293 $title = $this->getMockTitle( 'SomeDbKey' );
2294 $title->expects( $this->once() )
2295 ->method( 'getNextRevisionID' )
2296 ->with( $oldid )
2297 ->will( $this->returnValue( 33 ) );
2298
2299 $mockDb = $this->getMockDb();
2300 $mockDb->expects( $this->once() )
2301 ->method( 'selectRow' )
2302 ->with(
2303 'watchlist',
2304 'wl_notificationtimestamp',
2305 [
2306 'wl_user' => 1,
2307 'wl_namespace' => 0,
2308 'wl_title' => 'SomeDbKey',
2309 ]
2310 )
2311 ->will( $this->returnValue( false ) );
2312
2313 $mockCache = $this->getMockCache();
2314 $mockCache->expects( $this->never() )->method( 'get' );
2315 $mockCache->expects( $this->never() )->method( 'set' );
2316 $mockCache->expects( $this->once() )
2317 ->method( 'delete' )
2318 ->with( '0:SomeDbKey:1' );
2319
2320 $store = $this->newWatchedItemStore(
2321 $this->getMockLoadBalancer( $mockDb ),
2322 $mockCache,
2323 $this->getMockReadOnlyMode()
2324 );
2325
2326 $callableCallCounter = 0;
2327 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2328 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2329 $callableCallCounter++;
2330 $this->verifyCallbackJob(
2331 $callable,
2332 $title,
2333 $user->getId(),
2334 function ( $time ) {
2335 return $time === null;
2336 }
2337 );
2338 }
2339 );
2340
2341 $this->assertTrue(
2342 $store->resetNotificationTimestamp(
2343 $user,
2344 $title,
2345 'force',
2346 $oldid
2347 )
2348 );
2349 $this->assertEquals( 1, $callableCallCounter );
2350
2351 ScopedCallback::consume( $scopedOverride );
2352 }
2353
2355 $user = $this->getMockNonAnonUserWithId( 1 );
2356 $oldid = 22;
2357 $title = $this->getMockTitle( 'SomeDbKey' );
2358 $title->expects( $this->once() )
2359 ->method( 'getNextRevisionID' )
2360 ->with( $oldid )
2361 ->will( $this->returnValue( 33 ) );
2362
2363 $mockDb = $this->getMockDb();
2364 $mockDb->expects( $this->once() )
2365 ->method( 'selectRow' )
2366 ->with(
2367 'watchlist',
2368 'wl_notificationtimestamp',
2369 [
2370 'wl_user' => 1,
2371 'wl_namespace' => 0,
2372 'wl_title' => 'SomeDbKey',
2373 ]
2374 )
2375 ->will( $this->returnValue(
2376 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2377 ) );
2378
2379 $mockCache = $this->getMockCache();
2380 $mockCache->expects( $this->never() )->method( 'get' );
2381 $mockCache->expects( $this->once() )
2382 ->method( 'set' )
2383 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2384 $mockCache->expects( $this->once() )
2385 ->method( 'delete' )
2386 ->with( '0:SomeDbKey:1' );
2387
2388 $store = $this->newWatchedItemStore(
2389 $this->getMockLoadBalancer( $mockDb ),
2390 $mockCache,
2391 $this->getMockReadOnlyMode()
2392 );
2393
2394 $addUpdateCallCounter = 0;
2395 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2396 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2397 $addUpdateCallCounter++;
2398 $this->verifyCallbackJob(
2399 $callable,
2400 $title,
2401 $user->getId(),
2402 function ( $time ) {
2403 return $time === '30151212010101';
2404 }
2405 );
2406 }
2407 );
2408
2409 $getTimestampCallCounter = 0;
2410 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2411 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2412 $getTimestampCallCounter++;
2413 $this->assertEquals( $title, $titleParam );
2414 $this->assertEquals( $oldid, $oldidParam );
2415 }
2416 );
2417
2418 $this->assertTrue(
2419 $store->resetNotificationTimestamp(
2420 $user,
2421 $title,
2422 'force',
2423 $oldid
2424 )
2425 );
2426 $this->assertEquals( 1, $addUpdateCallCounter );
2427 $this->assertEquals( 1, $getTimestampCallCounter );
2428
2429 ScopedCallback::consume( $scopedOverrideDeferred );
2430 ScopedCallback::consume( $scopedOverrideRevision );
2431 }
2432
2434 $user = $this->getMockNonAnonUserWithId( 1 );
2435 $oldid = 22;
2436 $title = $this->getMockTitle( 'SomeDbKey' );
2437 $title->expects( $this->once() )
2438 ->method( 'getNextRevisionID' )
2439 ->with( $oldid )
2440 ->will( $this->returnValue( 33 ) );
2441
2442 $mockDb = $this->getMockDb();
2443 $mockDb->expects( $this->once() )
2444 ->method( 'selectRow' )
2445 ->with(
2446 'watchlist',
2447 'wl_notificationtimestamp',
2448 [
2449 'wl_user' => 1,
2450 'wl_namespace' => 0,
2451 'wl_title' => 'SomeDbKey',
2452 ]
2453 )
2454 ->will( $this->returnValue(
2455 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2456 ) );
2457
2458 $mockCache = $this->getMockCache();
2459 $mockCache->expects( $this->never() )->method( 'get' );
2460 $mockCache->expects( $this->once() )
2461 ->method( 'set' )
2462 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2463 $mockCache->expects( $this->once() )
2464 ->method( 'delete' )
2465 ->with( '0:SomeDbKey:1' );
2466
2467 $store = $this->newWatchedItemStore(
2468 $this->getMockLoadBalancer( $mockDb ),
2469 $mockCache,
2470 $this->getMockReadOnlyMode()
2471 );
2472
2473 $addUpdateCallCounter = 0;
2474 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2475 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2476 $addUpdateCallCounter++;
2477 $this->verifyCallbackJob(
2478 $callable,
2479 $title,
2480 $user->getId(),
2481 function ( $time ) {
2482 return $time === false;
2483 }
2484 );
2485 }
2486 );
2487
2488 $getTimestampCallCounter = 0;
2489 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2490 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2491 $getTimestampCallCounter++;
2492 $this->assertEquals( $title, $titleParam );
2493 $this->assertEquals( $oldid, $oldidParam );
2494 }
2495 );
2496
2497 $this->assertTrue(
2498 $store->resetNotificationTimestamp(
2499 $user,
2500 $title,
2501 '',
2502 $oldid
2503 )
2504 );
2505 $this->assertEquals( 1, $addUpdateCallCounter );
2506 $this->assertEquals( 1, $getTimestampCallCounter );
2507
2508 ScopedCallback::consume( $scopedOverrideDeferred );
2509 ScopedCallback::consume( $scopedOverrideRevision );
2510 }
2511
2513 $store = $this->newWatchedItemStore(
2514 $this->getMockLoadBalancer( $this->getMockDb() ),
2515 $this->getMockCache(),
2516 $this->getMockReadOnlyMode()
2517 );
2518 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2519 }
2520
2522 $user = $this->getMockNonAnonUserWithId( 1 );
2523 $timestamp = '20100101010101';
2524
2525 $mockDb = $this->getMockDb();
2526 $mockDb->expects( $this->once() )
2527 ->method( 'update' )
2528 ->with(
2529 'watchlist',
2530 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2531 [ 'wl_user' => 1 ]
2532 )
2533 ->will( $this->returnValue( true ) );
2534 $mockDb->expects( $this->exactly( 1 ) )
2535 ->method( 'timestamp' )
2536 ->will( $this->returnCallback( function ( $value ) {
2537 return 'TS' . $value . 'TS';
2538 } ) );
2539
2540 $store = $this->newWatchedItemStore(
2541 $this->getMockLoadBalancer( $mockDb ),
2542 $this->getMockCache(),
2543 $this->getMockReadOnlyMode()
2544 );
2545
2546 $this->assertTrue(
2547 $store->setNotificationTimestampsForUser( $user, $timestamp )
2548 );
2549 }
2550
2552 $user = $this->getMockNonAnonUserWithId( 1 );
2553 $timestamp = null;
2554
2555 $mockDb = $this->getMockDb();
2556 $mockDb->expects( $this->once() )
2557 ->method( 'update' )
2558 ->with(
2559 'watchlist',
2560 [ 'wl_notificationtimestamp' => null ],
2561 [ 'wl_user' => 1 ]
2562 )
2563 ->will( $this->returnValue( true ) );
2564 $mockDb->expects( $this->exactly( 0 ) )
2565 ->method( 'timestamp' )
2566 ->will( $this->returnCallback( function ( $value ) {
2567 return 'TS' . $value . 'TS';
2568 } ) );
2569
2570 $store = $this->newWatchedItemStore(
2571 $this->getMockLoadBalancer( $mockDb ),
2572 $this->getMockCache(),
2573 $this->getMockReadOnlyMode()
2574 );
2575
2576 $this->assertTrue(
2577 $store->setNotificationTimestampsForUser( $user, $timestamp )
2578 );
2579 }
2580
2582 $user = $this->getMockNonAnonUserWithId( 1 );
2583 $timestamp = '20100101010101';
2584 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2585
2586 $mockDb = $this->getMockDb();
2587 $mockDb->expects( $this->once() )
2588 ->method( 'update' )
2589 ->with(
2590 'watchlist',
2591 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2592 [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
2593 )
2594 ->will( $this->returnValue( true ) );
2595 $mockDb->expects( $this->exactly( 1 ) )
2596 ->method( 'timestamp' )
2597 ->will( $this->returnCallback( function ( $value ) {
2598 return 'TS' . $value . 'TS';
2599 } ) );
2600 $mockDb->expects( $this->once() )
2601 ->method( 'makeWhereFrom2d' )
2602 ->with(
2603 [ [ 'Foo' => 1, 'Bar' => 1 ] ],
2604 $this->isType( 'string' ),
2605 $this->isType( 'string' )
2606 )
2607 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
2608
2609 $store = $this->newWatchedItemStore(
2610 $this->getMockLoadBalancer( $mockDb ),
2611 $this->getMockCache(),
2612 $this->getMockReadOnlyMode()
2613 );
2614
2615 $this->assertTrue(
2616 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2617 );
2618 }
2619
2621 $mockDb = $this->getMockDb();
2622 $mockDb->expects( $this->once() )
2623 ->method( 'selectFieldValues' )
2624 ->with(
2625 'watchlist',
2626 'wl_user',
2627 [
2628 'wl_user != 1',
2629 'wl_namespace' => 0,
2630 'wl_title' => 'SomeDbKey',
2631 'wl_notificationtimestamp IS NULL'
2632 ]
2633 )
2634 ->will( $this->returnValue( [ '2', '3' ] ) );
2635 $mockDb->expects( $this->once() )
2636 ->method( 'update' )
2637 ->with(
2638 'watchlist',
2639 [ 'wl_notificationtimestamp' => null ],
2640 [
2641 'wl_user' => [ 2, 3 ],
2642 'wl_namespace' => 0,
2643 'wl_title' => 'SomeDbKey',
2644 ]
2645 );
2646
2647 $mockCache = $this->getMockCache();
2648 $mockCache->expects( $this->never() )->method( 'set' );
2649 $mockCache->expects( $this->never() )->method( 'get' );
2650 $mockCache->expects( $this->never() )->method( 'delete' );
2651
2652 $store = $this->newWatchedItemStore(
2653 $this->getMockLoadBalancer( $mockDb ),
2654 $mockCache,
2655 $this->getMockReadOnlyMode()
2656 );
2657
2658 $this->assertEquals(
2659 [ 2, 3 ],
2660 $store->updateNotificationTimestamp(
2661 $this->getMockNonAnonUserWithId( 1 ),
2662 new TitleValue( 0, 'SomeDbKey' ),
2663 '20151212010101'
2664 )
2665 );
2666 }
2667
2669 $mockDb = $this->getMockDb();
2670 $mockDb->expects( $this->once() )
2671 ->method( 'selectFieldValues' )
2672 ->with(
2673 'watchlist',
2674 'wl_user',
2675 [
2676 'wl_user != 1',
2677 'wl_namespace' => 0,
2678 'wl_title' => 'SomeDbKey',
2679 'wl_notificationtimestamp IS NULL'
2680 ]
2681 )
2682 ->will(
2683 $this->returnValue( [] )
2684 );
2685 $mockDb->expects( $this->never() )
2686 ->method( 'update' );
2687
2688 $mockCache = $this->getMockCache();
2689 $mockCache->expects( $this->never() )->method( 'set' );
2690 $mockCache->expects( $this->never() )->method( 'get' );
2691 $mockCache->expects( $this->never() )->method( 'delete' );
2692
2693 $store = $this->newWatchedItemStore(
2694 $this->getMockLoadBalancer( $mockDb ),
2695 $mockCache,
2696 $this->getMockReadOnlyMode()
2697 );
2698
2699 $watchers = $store->updateNotificationTimestamp(
2700 $this->getMockNonAnonUserWithId( 1 ),
2701 new TitleValue( 0, 'SomeDbKey' ),
2702 '20151212010101'
2703 );
2704 $this->assertInternalType( 'array', $watchers );
2705 $this->assertEmpty( $watchers );
2706 }
2707
2709 $user = $this->getMockNonAnonUserWithId( 1 );
2710 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2711
2712 $mockDb = $this->getMockDb();
2713 $mockDb->expects( $this->once() )
2714 ->method( 'selectRow' )
2715 ->will( $this->returnValue(
2716 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2717 ) );
2718 $mockDb->expects( $this->once() )
2719 ->method( 'selectFieldValues' )
2720 ->will(
2721 $this->returnValue( [ '2', '3' ] )
2722 );
2723 $mockDb->expects( $this->once() )
2724 ->method( 'update' );
2725
2726 $mockCache = $this->getMockCache();
2727 $mockCache->expects( $this->once() )
2728 ->method( 'set' )
2729 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2730 $mockCache->expects( $this->once() )
2731 ->method( 'get' )
2732 ->with( '0:SomeDbKey:1' );
2733 $mockCache->expects( $this->once() )
2734 ->method( 'delete' )
2735 ->with( '0:SomeDbKey:1' );
2736
2737 $store = $this->newWatchedItemStore(
2738 $this->getMockLoadBalancer( $mockDb ),
2739 $mockCache,
2740 $this->getMockReadOnlyMode()
2741 );
2742
2743 // This will add the item to the cache
2744 $store->getWatchedItem( $user, $titleValue );
2745
2746 $store->updateNotificationTimestamp(
2747 $this->getMockNonAnonUserWithId( 1 ),
2748 $titleValue,
2749 '20151212010101'
2750 );
2751 }
2752
2753}
</td >< td > &</td >< td > t want your writing to be edited mercilessly and redistributed at will
they could even be mouse clicks or menu items whatever suits your program You should also get your if any
Definition COPYING.txt:326
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Simple store for keeping values in an associative array for the current process.
A service class for fetching the wiki's current read-only mode.
Represents a page (or page fragment) title within MediaWiki.
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:591
testCountWatchersMultiple_withMinimumWatchers( $minWatchers)
provideIntWithDbUnsafeVersion
testDuplicateAllAssociatedEntries_somethingToDuplicate(LinkTarget $oldTarget, LinkTarget $newTarget)
provideLinkTargetPairs
testCountUnreadNotifications_withUnreadLimit_overLimit( $limit)
provideIntWithDbUnsafeVersion
testCountUnreadNotifications_withUnreadLimit_underLimit( $limit)
provideIntWithDbUnsafeVersion
getMockLoadBalancer( $mockDb, $expectedConnectionType=null)
testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType)
provideDbTypes
verifyCallbackJob( $callback, LinkTarget $expectedTitle, $expectedUserId, callable $notificationTimestampCondition)
newWatchedItemStore(LoadBalancer $loadBalancer, HashBagOStuff $cache, ReadOnlyMode $readOnlyMode)
testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers)
provideIntWithDbUnsafeVersion
Storage layer class for WatchedItems.
Representation of a pair of user and title for watchlist entries.
Database connection, tracking, load balancing, and transaction manager for a cluster.
when a variable name is used in a function
Definition design.txt:94
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same so they can t rely on Unix and must forbid reads to even standard directories like tmp lest users read each others files We cannot assume that the user has the ability to install or run any programs not written as web accessible PHP scripts Since anything that works on cheap shared hosting will work if you have shell or root access MediaWiki s design is based around catering to the lowest common denominator Although we support higher end setups as the way many things work by default is tailored toward shared hosting These defaults are unconventional from the point of view of and they certainly aren t ideal for someone who s installing MediaWiki as MediaWiki does not conform to normal Unix filesystem layout Hopefully we ll offer direct support for standard layouts in the but for now *any change to the location of files is unsupported *Moving things and leaving symlinks will *probably *not break anything
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition hooks.txt:2228
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1795
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:964
const LIST_AND
Definition Defines.php:53
$cache
Definition mcc.php:33
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:29
if(count( $args)< 1) $job