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