MediaWiki  1.29.1
WatchedItemQueryServiceUnitTest.php
Go to the documentation of this file.
1 <?php
2 
3 use Wikimedia\TestingAccessWrapper;
4 
8 class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
9 
13  private function getMockDb() {
14  $mock = $this->getMockBuilder( Database::class )
15  ->disableOriginalConstructor()
16  ->getMock();
17 
18  $mock->expects( $this->any() )
19  ->method( 'makeList' )
20  ->with(
21  $this->isType( 'array' ),
22  $this->isType( 'int' )
23  )
24  ->will( $this->returnCallback( function( $a, $conj ) {
25  $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
26  return join( $sqlConj, array_map( function( $s ) {
27  return '(' . $s . ')';
28  }, $a
29  ) );
30  } ) );
31 
32  $mock->expects( $this->any() )
33  ->method( 'addQuotes' )
34  ->will( $this->returnCallback( function( $value ) {
35  return "'$value'";
36  } ) );
37 
38  $mock->expects( $this->any() )
39  ->method( 'timestamp' )
40  ->will( $this->returnArgument( 0 ) );
41 
42  $mock->expects( $this->any() )
43  ->method( 'bitAnd' )
44  ->willReturnCallback( function( $a, $b ) {
45  return "($a & $b)";
46  } );
47 
48  return $mock;
49  }
50 
55  private function getMockLoadBalancer( $mockDb ) {
56  $mock = $this->getMockBuilder( LoadBalancer::class )
57  ->disableOriginalConstructor()
58  ->getMock();
59  $mock->expects( $this->any() )
60  ->method( 'getConnectionRef' )
61  ->with( DB_SLAVE )
62  ->will( $this->returnValue( $mockDb ) );
63  return $mock;
64  }
65 
70  private function getMockNonAnonUserWithId( $id ) {
71  $mock = $this->getMockBuilder( User::class )->getMock();
72  $mock->expects( $this->any() )
73  ->method( 'isAnon' )
74  ->will( $this->returnValue( false ) );
75  $mock->expects( $this->any() )
76  ->method( 'getId' )
77  ->will( $this->returnValue( $id ) );
78  return $mock;
79  }
80 
85  private function getMockUnrestrictedNonAnonUserWithId( $id ) {
86  $mock = $this->getMockNonAnonUserWithId( $id );
87  $mock->expects( $this->any() )
88  ->method( 'isAllowed' )
89  ->will( $this->returnValue( true ) );
90  $mock->expects( $this->any() )
91  ->method( 'isAllowedAny' )
92  ->will( $this->returnValue( true ) );
93  $mock->expects( $this->any() )
94  ->method( 'useRCPatrol' )
95  ->will( $this->returnValue( true ) );
96  return $mock;
97  }
98 
104  private function getMockNonAnonUserWithIdAndRestrictedPermissions( $id, $notAllowedAction ) {
105  $mock = $this->getMockNonAnonUserWithId( $id );
106 
107  $mock->expects( $this->any() )
108  ->method( 'isAllowed' )
109  ->will( $this->returnCallback( function( $action ) use ( $notAllowedAction ) {
110  return $action !== $notAllowedAction;
111  } ) );
112  $mock->expects( $this->any() )
113  ->method( 'isAllowedAny' )
114  ->will( $this->returnCallback( function() use ( $notAllowedAction ) {
115  $actions = func_get_args();
116  return !in_array( $notAllowedAction, $actions );
117  } ) );
118 
119  return $mock;
120  }
121 
127  $mock = $this->getMockNonAnonUserWithId( $id );
128 
129  $mock->expects( $this->any() )
130  ->method( 'isAllowed' )
131  ->will( $this->returnValue( true ) );
132  $mock->expects( $this->any() )
133  ->method( 'isAllowedAny' )
134  ->will( $this->returnValue( true ) );
135 
136  $mock->expects( $this->any() )
137  ->method( 'useRCPatrol' )
138  ->will( $this->returnValue( false ) );
139  $mock->expects( $this->any() )
140  ->method( 'useNPPatrol' )
141  ->will( $this->returnValue( false ) );
142 
143  return $mock;
144  }
145 
146  private function getMockAnonUser() {
147  $mock = $this->getMockBuilder( User::class )->getMock();
148  $mock->expects( $this->any() )
149  ->method( 'isAnon' )
150  ->will( $this->returnValue( true ) );
151  return $mock;
152  }
153 
154  private function getFakeRow( array $rowValues ) {
155  $fakeRow = new stdClass();
156  foreach ( $rowValues as $valueName => $value ) {
157  $fakeRow->$valueName = $value;
158  }
159  return $fakeRow;
160  }
161 
163  $mockDb = $this->getMockDb();
164  $mockDb->expects( $this->once() )
165  ->method( 'select' )
166  ->with(
167  [ 'recentchanges', 'watchlist', 'page' ],
168  [
169  'rc_id',
170  'rc_namespace',
171  'rc_title',
172  'rc_timestamp',
173  'rc_type',
174  'rc_deleted',
175  'wl_notificationtimestamp',
176  'rc_cur_id',
177  'rc_this_oldid',
178  'rc_last_oldid',
179  ],
180  [
181  'wl_user' => 1,
182  '(rc_this_oldid=page_latest) OR (rc_type=3)',
183  ],
184  $this->isType( 'string' ),
185  [
186  'LIMIT' => 3,
187  ],
188  [
189  'watchlist' => [
190  'INNER JOIN',
191  [
192  'wl_namespace=rc_namespace',
193  'wl_title=rc_title'
194  ]
195  ],
196  'page' => [
197  'LEFT JOIN',
198  'rc_cur_id=page_id',
199  ],
200  ]
201  )
202  ->will( $this->returnValue( [
203  $this->getFakeRow( [
204  'rc_id' => 1,
205  'rc_namespace' => 0,
206  'rc_title' => 'Foo1',
207  'rc_timestamp' => '20151212010101',
208  'rc_type' => RC_NEW,
209  'rc_deleted' => 0,
210  'wl_notificationtimestamp' => '20151212010101',
211  ] ),
212  $this->getFakeRow( [
213  'rc_id' => 2,
214  'rc_namespace' => 1,
215  'rc_title' => 'Foo2',
216  'rc_timestamp' => '20151212010102',
217  'rc_type' => RC_NEW,
218  'rc_deleted' => 0,
219  'wl_notificationtimestamp' => null,
220  ] ),
221  $this->getFakeRow( [
222  'rc_id' => 3,
223  'rc_namespace' => 1,
224  'rc_title' => 'Foo3',
225  'rc_timestamp' => '20151212010103',
226  'rc_type' => RC_NEW,
227  'rc_deleted' => 0,
228  'wl_notificationtimestamp' => null,
229  ] ),
230  ] ) );
231 
232  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
234 
235  $startFrom = null;
236  $items = $queryService->getWatchedItemsWithRecentChangeInfo(
237  $user, [ 'limit' => 2 ], $startFrom
238  );
239 
240  $this->assertInternalType( 'array', $items );
241  $this->assertCount( 2, $items );
242 
243  foreach ( $items as list( $watchedItem, $recentChangeInfo ) ) {
244  $this->assertInstanceOf( WatchedItem::class, $watchedItem );
245  $this->assertInternalType( 'array', $recentChangeInfo );
246  }
247 
248  $this->assertEquals(
249  new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
250  $items[0][0]
251  );
252  $this->assertEquals(
253  [
254  'rc_id' => 1,
255  'rc_namespace' => 0,
256  'rc_title' => 'Foo1',
257  'rc_timestamp' => '20151212010101',
258  'rc_type' => RC_NEW,
259  'rc_deleted' => 0,
260  ],
261  $items[0][1]
262  );
263 
264  $this->assertEquals(
265  new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
266  $items[1][0]
267  );
268  $this->assertEquals(
269  [
270  'rc_id' => 2,
271  'rc_namespace' => 1,
272  'rc_title' => 'Foo2',
273  'rc_timestamp' => '20151212010102',
274  'rc_type' => RC_NEW,
275  'rc_deleted' => 0,
276  ],
277  $items[1][1]
278  );
279 
280  $this->assertEquals( [ '20151212010103', 3 ], $startFrom );
281  }
282 
284  $mockDb = $this->getMockDb();
285  $mockDb->expects( $this->once() )
286  ->method( 'select' )
287  ->with(
288  [ 'recentchanges', 'watchlist', 'page', 'extension_dummy_table' ],
289  [
290  'rc_id',
291  'rc_namespace',
292  'rc_title',
293  'rc_timestamp',
294  'rc_type',
295  'rc_deleted',
296  'wl_notificationtimestamp',
297  'rc_cur_id',
298  'rc_this_oldid',
299  'rc_last_oldid',
300  'extension_dummy_field',
301  ],
302  [
303  'wl_user' => 1,
304  '(rc_this_oldid=page_latest) OR (rc_type=3)',
305  'extension_dummy_cond',
306  ],
307  $this->isType( 'string' ),
308  [
309  'extension_dummy_option',
310  ],
311  [
312  'watchlist' => [
313  'INNER JOIN',
314  [
315  'wl_namespace=rc_namespace',
316  'wl_title=rc_title'
317  ]
318  ],
319  'page' => [
320  'LEFT JOIN',
321  'rc_cur_id=page_id',
322  ],
323  'extension_dummy_join_cond' => [],
324  ]
325  )
326  ->will( $this->returnValue( [
327  $this->getFakeRow( [
328  'rc_id' => 1,
329  'rc_namespace' => 0,
330  'rc_title' => 'Foo1',
331  'rc_timestamp' => '20151212010101',
332  'rc_type' => RC_NEW,
333  'rc_deleted' => 0,
334  'wl_notificationtimestamp' => '20151212010101',
335  ] ),
336  $this->getFakeRow( [
337  'rc_id' => 2,
338  'rc_namespace' => 1,
339  'rc_title' => 'Foo2',
340  'rc_timestamp' => '20151212010102',
341  'rc_type' => RC_NEW,
342  'rc_deleted' => 0,
343  'wl_notificationtimestamp' => null,
344  ] ),
345  ] ) );
346 
348 
349  $mockExtension = $this->getMockBuilder( WatchedItemQueryServiceExtension::class )
350  ->getMock();
351  $mockExtension->expects( $this->once() )
352  ->method( 'modifyWatchedItemsWithRCInfoQuery' )
353  ->with(
354  $this->identicalTo( $user ),
355  $this->isType( 'array' ),
356  $this->isInstanceOf( IDatabase::class ),
357  $this->isType( 'array' ),
358  $this->isType( 'array' ),
359  $this->isType( 'array' ),
360  $this->isType( 'array' ),
361  $this->isType( 'array' )
362  )
363  ->will( $this->returnCallback( function (
364  $user, $options, $db, &$tables, &$fields, &$conds, &$dbOptions, &$joinConds
365  ) {
366  $tables[] = 'extension_dummy_table';
367  $fields[] = 'extension_dummy_field';
368  $conds[] = 'extension_dummy_cond';
369  $dbOptions[] = 'extension_dummy_option';
370  $joinConds['extension_dummy_join_cond'] = [];
371  } ) );
372  $mockExtension->expects( $this->once() )
373  ->method( 'modifyWatchedItemsWithRCInfo' )
374  ->with(
375  $this->identicalTo( $user ),
376  $this->isType( 'array' ),
377  $this->isInstanceOf( IDatabase::class ),
378  $this->isType( 'array' ),
379  $this->anything(),
380  $this->anything() // Can't test for null here, PHPUnit applies this after the callback
381  )
382  ->will( $this->returnCallback( function ( $user, $options, $db, &$items, $res, &$startFrom ) {
383  foreach ( $items as $i => &$item ) {
384  $item[1]['extension_dummy_field'] = $i;
385  }
386  unset( $item );
387 
388  $this->assertNull( $startFrom );
389  $startFrom = [ '20160203123456', 42 ];
390  } ) );
391 
392  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
393  TestingAccessWrapper::newFromObject( $queryService )->extensions = [ $mockExtension ];
394 
395  $startFrom = null;
396  $items = $queryService->getWatchedItemsWithRecentChangeInfo(
397  $user, [], $startFrom
398  );
399 
400  $this->assertInternalType( 'array', $items );
401  $this->assertCount( 2, $items );
402 
403  foreach ( $items as list( $watchedItem, $recentChangeInfo ) ) {
404  $this->assertInstanceOf( WatchedItem::class, $watchedItem );
405  $this->assertInternalType( 'array', $recentChangeInfo );
406  }
407 
408  $this->assertEquals(
409  new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
410  $items[0][0]
411  );
412  $this->assertEquals(
413  [
414  'rc_id' => 1,
415  'rc_namespace' => 0,
416  'rc_title' => 'Foo1',
417  'rc_timestamp' => '20151212010101',
418  'rc_type' => RC_NEW,
419  'rc_deleted' => 0,
420  'extension_dummy_field' => 0,
421  ],
422  $items[0][1]
423  );
424 
425  $this->assertEquals(
426  new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
427  $items[1][0]
428  );
429  $this->assertEquals(
430  [
431  'rc_id' => 2,
432  'rc_namespace' => 1,
433  'rc_title' => 'Foo2',
434  'rc_timestamp' => '20151212010102',
435  'rc_type' => RC_NEW,
436  'rc_deleted' => 0,
437  'extension_dummy_field' => 1,
438  ],
439  $items[1][1]
440  );
441 
442  $this->assertEquals( [ '20160203123456', 42 ], $startFrom );
443  }
444 
446  return [
447  [
448  [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_FLAGS ] ],
449  null,
450  [ 'rc_type', 'rc_minor', 'rc_bot' ],
451  [],
452  [],
453  ],
454  [
455  [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER ] ],
456  null,
457  [ 'rc_user_text' ],
458  [],
459  [],
460  ],
461  [
462  [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER_ID ] ],
463  null,
464  [ 'rc_user' ],
465  [],
466  [],
467  ],
468  [
469  [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
470  null,
471  [ 'rc_comment' ],
472  [],
473  [],
474  ],
475  [
476  [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_PATROL_INFO ] ],
477  null,
478  [ 'rc_patrolled', 'rc_log_type' ],
479  [],
480  [],
481  ],
482  [
483  [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_SIZES ] ],
484  null,
485  [ 'rc_old_len', 'rc_new_len' ],
486  [],
487  [],
488  ],
489  [
490  [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_LOG_INFO ] ],
491  null,
492  [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ],
493  [],
494  [],
495  ],
496  [
497  [ 'namespaceIds' => [ 0, 1 ] ],
498  null,
499  [],
500  [ 'wl_namespace' => [ 0, 1 ] ],
501  [],
502  ],
503  [
504  [ 'namespaceIds' => [ 0, "1; DROP TABLE watchlist;\n--" ] ],
505  null,
506  [],
507  [ 'wl_namespace' => [ 0, 1 ] ],
508  [],
509  ],
510  [
511  [ 'rcTypes' => [ RC_EDIT, RC_NEW ] ],
512  null,
513  [],
514  [ 'rc_type' => [ RC_EDIT, RC_NEW ] ],
515  [],
516  ],
517  [
519  null,
520  [],
521  [],
522  [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
523  ],
524  [
526  null,
527  [],
528  [],
529  [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
530  ],
531  [
532  [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'start' => '20151212010101' ],
533  null,
534  [],
535  [ "rc_timestamp <= '20151212010101'" ],
536  [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
537  ],
538  [
539  [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'end' => '20151212010101' ],
540  null,
541  [],
542  [ "rc_timestamp >= '20151212010101'" ],
543  [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
544  ],
545  [
546  [
548  'start' => '20151212020101',
549  'end' => '20151212010101'
550  ],
551  null,
552  [],
553  [ "rc_timestamp <= '20151212020101'", "rc_timestamp >= '20151212010101'" ],
554  [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
555  ],
556  [
557  [ 'dir' => WatchedItemQueryService::DIR_NEWER, 'start' => '20151212010101' ],
558  null,
559  [],
560  [ "rc_timestamp >= '20151212010101'" ],
561  [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
562  ],
563  [
564  [ 'dir' => WatchedItemQueryService::DIR_NEWER, 'end' => '20151212010101' ],
565  null,
566  [],
567  [ "rc_timestamp <= '20151212010101'" ],
568  [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
569  ],
570  [
571  [
573  'start' => '20151212010101',
574  'end' => '20151212020101'
575  ],
576  null,
577  [],
578  [ "rc_timestamp >= '20151212010101'", "rc_timestamp <= '20151212020101'" ],
579  [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
580  ],
581  [
582  [ 'limit' => 10 ],
583  null,
584  [],
585  [],
586  [ 'LIMIT' => 11 ],
587  ],
588  [
589  [ 'limit' => "10; DROP TABLE watchlist;\n--" ],
590  null,
591  [],
592  [],
593  [ 'LIMIT' => 11 ],
594  ],
595  [
596  [ 'filters' => [ WatchedItemQueryService::FILTER_MINOR ] ],
597  null,
598  [],
599  [ 'rc_minor != 0' ],
600  [],
601  ],
602  [
603  [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_MINOR ] ],
604  null,
605  [],
606  [ 'rc_minor = 0' ],
607  [],
608  ],
609  [
610  [ 'filters' => [ WatchedItemQueryService::FILTER_BOT ] ],
611  null,
612  [],
613  [ 'rc_bot != 0' ],
614  [],
615  ],
616  [
617  [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_BOT ] ],
618  null,
619  [],
620  [ 'rc_bot = 0' ],
621  [],
622  ],
623  [
624  [ 'filters' => [ WatchedItemQueryService::FILTER_ANON ] ],
625  null,
626  [],
627  [ 'rc_user = 0' ],
628  [],
629  ],
630  [
631  [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_ANON ] ],
632  null,
633  [],
634  [ 'rc_user != 0' ],
635  [],
636  ],
637  [
638  [ 'filters' => [ WatchedItemQueryService::FILTER_PATROLLED ] ],
639  null,
640  [],
641  [ 'rc_patrolled != 0' ],
642  [],
643  ],
644  [
646  null,
647  [],
648  [ 'rc_patrolled = 0' ],
649  [],
650  ],
651  [
652  [ 'filters' => [ WatchedItemQueryService::FILTER_UNREAD ] ],
653  null,
654  [],
655  [ 'rc_timestamp >= wl_notificationtimestamp' ],
656  [],
657  ],
658  [
659  [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_UNREAD ] ],
660  null,
661  [],
662  [ 'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp' ],
663  [],
664  ],
665  [
666  [ 'onlyByUser' => 'SomeOtherUser' ],
667  null,
668  [],
669  [ 'rc_user_text' => 'SomeOtherUser' ],
670  [],
671  ],
672  [
673  [ 'notByUser' => 'SomeOtherUser' ],
674  null,
675  [],
676  [ "rc_user_text != 'SomeOtherUser'" ],
677  [],
678  ],
679  [
681  [ '20151212010101', 123 ],
682  [],
683  [
684  "(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))"
685  ],
686  [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
687  ],
688  [
690  [ '20151212010101', 123 ],
691  [],
692  [
693  "(rc_timestamp > '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id >= 123))"
694  ],
695  [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
696  ],
697  [
699  [ '20151212010101', "123; DROP TABLE watchlist;\n--" ],
700  [],
701  [
702  "(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))"
703  ],
704  [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
705  ],
706  ];
707  }
708 
713  array $options,
714  $startFrom,
715  array $expectedExtraFields,
716  array $expectedExtraConds,
717  array $expectedDbOptions
718  ) {
719  $expectedFields = array_merge(
720  [
721  'rc_id',
722  'rc_namespace',
723  'rc_title',
724  'rc_timestamp',
725  'rc_type',
726  'rc_deleted',
727  'wl_notificationtimestamp',
728 
729  'rc_cur_id',
730  'rc_this_oldid',
731  'rc_last_oldid',
732  ],
733  $expectedExtraFields
734  );
735  $expectedConds = array_merge(
736  [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)', ],
737  $expectedExtraConds
738  );
739 
740  $mockDb = $this->getMockDb();
741  $mockDb->expects( $this->once() )
742  ->method( 'select' )
743  ->with(
744  [ 'recentchanges', 'watchlist', 'page' ],
745  $expectedFields,
746  $expectedConds,
747  $this->isType( 'string' ),
748  $expectedDbOptions,
749  [
750  'watchlist' => [
751  'INNER JOIN',
752  [
753  'wl_namespace=rc_namespace',
754  'wl_title=rc_title'
755  ]
756  ],
757  'page' => [
758  'LEFT JOIN',
759  'rc_cur_id=page_id',
760  ],
761  ]
762  )
763  ->will( $this->returnValue( [] ) );
764 
765  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
767 
768  $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options, $startFrom );
769 
770  $this->assertEmpty( $items );
771  $this->assertNull( $startFrom );
772  }
773 
774  public function filterPatrolledOptionProvider() {
775  return [
778  ];
779  }
780 
785  $filtersOption
786  ) {
787  $mockDb = $this->getMockDb();
788  $mockDb->expects( $this->once() )
789  ->method( 'select' )
790  ->with(
791  [ 'recentchanges', 'watchlist', 'page' ],
792  $this->isType( 'array' ),
793  [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ],
794  $this->isType( 'string' ),
795  $this->isType( 'array' ),
796  $this->isType( 'array' )
797  )
798  ->will( $this->returnValue( [] ) );
799 
801 
802  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
803  $items = $queryService->getWatchedItemsWithRecentChangeInfo(
804  $user,
805  [ 'filters' => [ $filtersOption ] ]
806  );
807 
808  $this->assertEmpty( $items );
809  }
810 
811  public function mysqlIndexOptimizationProvider() {
812  return [
813  [
814  'mysql',
815  [],
816  [ "rc_timestamp > ''" ],
817  ],
818  [
819  'mysql',
820  [ 'start' => '20151212010101', 'dir' => WatchedItemQueryService::DIR_OLDER ],
821  [ "rc_timestamp <= '20151212010101'" ],
822  ],
823  [
824  'mysql',
825  [ 'end' => '20151212010101', 'dir' => WatchedItemQueryService::DIR_OLDER ],
826  [ "rc_timestamp >= '20151212010101'" ],
827  ],
828  [
829  'postgres',
830  [],
831  [],
832  ],
833  ];
834  }
835 
840  $dbType,
841  array $options,
842  array $expectedExtraConds
843  ) {
844  $commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
845  $conds = array_merge( $commonConds, $expectedExtraConds );
846 
847  $mockDb = $this->getMockDb();
848  $mockDb->expects( $this->once() )
849  ->method( 'select' )
850  ->with(
851  [ 'recentchanges', 'watchlist', 'page' ],
852  $this->isType( 'array' ),
853  $conds,
854  $this->isType( 'string' ),
855  $this->isType( 'array' ),
856  $this->isType( 'array' )
857  )
858  ->will( $this->returnValue( [] ) );
859  $mockDb->expects( $this->any() )
860  ->method( 'getType' )
861  ->will( $this->returnValue( $dbType ) );
862 
863  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
865 
866  $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
867 
868  $this->assertEmpty( $items );
869  }
870 
872  return [
873  [
874  [],
875  'deletedhistory',
876  [
877  '(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
879  ],
880  ],
881  [
882  [],
883  'suppressrevision',
884  [
885  '(rc_type != ' . RC_LOG . ') OR (' .
886  '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
888  ],
889  ],
890  [
891  [],
892  'viewsuppressed',
893  [
894  '(rc_type != ' . RC_LOG . ') OR (' .
895  '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
897  ],
898  ],
899  [
900  [ 'onlyByUser' => 'SomeOtherUser' ],
901  'deletedhistory',
902  [
903  'rc_user_text' => 'SomeOtherUser',
904  '(rc_deleted & ' . Revision::DELETED_USER . ') != ' . Revision::DELETED_USER,
905  '(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
907  ],
908  ],
909  [
910  [ 'onlyByUser' => 'SomeOtherUser' ],
911  'suppressrevision',
912  [
913  'rc_user_text' => 'SomeOtherUser',
914  '(rc_deleted & ' . ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ) . ') != ' .
916  '(rc_type != ' . RC_LOG . ') OR (' .
917  '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
919  ],
920  ],
921  [
922  [ 'onlyByUser' => 'SomeOtherUser' ],
923  'viewsuppressed',
924  [
925  'rc_user_text' => 'SomeOtherUser',
926  '(rc_deleted & ' . ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ) . ') != ' .
928  '(rc_type != ' . RC_LOG . ') OR (' .
929  '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
931  ],
932  ],
933  ];
934  }
935 
940  array $options,
941  $notAllowedAction,
942  array $expectedExtraConds
943  ) {
944  $commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
945  $conds = array_merge( $commonConds, $expectedExtraConds );
946 
947  $mockDb = $this->getMockDb();
948  $mockDb->expects( $this->once() )
949  ->method( 'select' )
950  ->with(
951  [ 'recentchanges', 'watchlist', 'page' ],
952  $this->isType( 'array' ),
953  $conds,
954  $this->isType( 'string' ),
955  $this->isType( 'array' ),
956  $this->isType( 'array' )
957  )
958  ->will( $this->returnValue( [] ) );
959 
960  $user = $this->getMockNonAnonUserWithIdAndRestrictedPermissions( 1, $notAllowedAction );
961 
962  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
963  $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
964 
965  $this->assertEmpty( $items );
966  }
967 
969  $mockDb = $this->getMockDb();
970  $mockDb->expects( $this->once() )
971  ->method( 'select' )
972  ->with(
973  [ 'recentchanges', 'watchlist' ],
974  [
975  'rc_id',
976  'rc_namespace',
977  'rc_title',
978  'rc_timestamp',
979  'rc_type',
980  'rc_deleted',
981  'wl_notificationtimestamp',
982 
983  'rc_cur_id',
984  'rc_this_oldid',
985  'rc_last_oldid',
986  ],
987  [ 'wl_user' => 1, ],
988  $this->isType( 'string' ),
989  [],
990  [
991  'watchlist' => [
992  'INNER JOIN',
993  [
994  'wl_namespace=rc_namespace',
995  'wl_title=rc_title'
996  ]
997  ],
998  ]
999  )
1000  ->will( $this->returnValue( [] ) );
1001 
1002  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1004 
1005  $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, [ 'allRevisions' => true ] );
1006 
1007  $this->assertEmpty( $items );
1008  }
1009 
1011  return [
1012  [
1013  [ 'rcTypes' => [ 1337 ] ],
1014  null,
1015  'Bad value for parameter $options[\'rcTypes\']',
1016  ],
1017  [
1018  [ 'rcTypes' => [ 'edit' ] ],
1019  null,
1020  'Bad value for parameter $options[\'rcTypes\']',
1021  ],
1022  [
1023  [ 'rcTypes' => [ RC_EDIT, 1337 ] ],
1024  null,
1025  'Bad value for parameter $options[\'rcTypes\']',
1026  ],
1027  [
1028  [ 'dir' => 'foo' ],
1029  null,
1030  'Bad value for parameter $options[\'dir\']',
1031  ],
1032  [
1033  [ 'start' => '20151212010101' ],
1034  null,
1035  'Bad value for parameter $options[\'dir\']: must be provided',
1036  ],
1037  [
1038  [ 'end' => '20151212010101' ],
1039  null,
1040  'Bad value for parameter $options[\'dir\']: must be provided',
1041  ],
1042  [
1043  [],
1044  [ '20151212010101', 123 ],
1045  'Bad value for parameter $options[\'dir\']: must be provided',
1046  ],
1047  [
1049  '20151212010101',
1050  'Bad value for parameter $startFrom: must be a two-element array',
1051  ],
1052  [
1054  [ '20151212010101' ],
1055  'Bad value for parameter $startFrom: must be a two-element array',
1056  ],
1057  [
1059  [ '20151212010101', 123, 'foo' ],
1060  'Bad value for parameter $startFrom: must be a two-element array',
1061  ],
1062  [
1063  [ 'watchlistOwner' => $this->getMockUnrestrictedNonAnonUserWithId( 2 ) ],
1064  null,
1065  'Bad value for parameter $options[\'watchlistOwnerToken\']',
1066  ],
1067  [
1068  [ 'watchlistOwner' => 'Other User', 'watchlistOwnerToken' => 'some-token' ],
1069  null,
1070  'Bad value for parameter $options[\'watchlistOwner\']',
1071  ],
1072  ];
1073  }
1074 
1079  array $options,
1080  $startFrom,
1081  $expectedInExceptionMessage
1082  ) {
1083  $mockDb = $this->getMockDb();
1084  $mockDb->expects( $this->never() )
1085  ->method( $this->anything() );
1086 
1087  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1089 
1090  $this->setExpectedException( InvalidArgumentException::class, $expectedInExceptionMessage );
1091  $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options, $startFrom );
1092  }
1093 
1095  $mockDb = $this->getMockDb();
1096  $mockDb->expects( $this->once() )
1097  ->method( 'select' )
1098  ->with(
1099  [ 'recentchanges', 'watchlist', 'page' ],
1100  [
1101  'rc_id',
1102  'rc_namespace',
1103  'rc_title',
1104  'rc_timestamp',
1105  'rc_type',
1106  'rc_deleted',
1107  'wl_notificationtimestamp',
1108  'rc_cur_id',
1109  ],
1110  [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ],
1111  $this->isType( 'string' ),
1112  [],
1113  [
1114  'watchlist' => [
1115  'INNER JOIN',
1116  [
1117  'wl_namespace=rc_namespace',
1118  'wl_title=rc_title'
1119  ]
1120  ],
1121  'page' => [
1122  'LEFT JOIN',
1123  'rc_cur_id=page_id',
1124  ],
1125  ]
1126  )
1127  ->will( $this->returnValue( [] ) );
1128 
1129  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1131 
1132  $items = $queryService->getWatchedItemsWithRecentChangeInfo(
1133  $user,
1134  [ 'usedInGenerator' => true ]
1135  );
1136 
1137  $this->assertEmpty( $items );
1138  }
1139 
1141  $mockDb = $this->getMockDb();
1142  $mockDb->expects( $this->once() )
1143  ->method( 'select' )
1144  ->with(
1145  [ 'recentchanges', 'watchlist' ],
1146  [
1147  'rc_id',
1148  'rc_namespace',
1149  'rc_title',
1150  'rc_timestamp',
1151  'rc_type',
1152  'rc_deleted',
1153  'wl_notificationtimestamp',
1154  'rc_this_oldid',
1155  ],
1156  [ 'wl_user' => 1 ],
1157  $this->isType( 'string' ),
1158  [],
1159  [
1160  'watchlist' => [
1161  'INNER JOIN',
1162  [
1163  'wl_namespace=rc_namespace',
1164  'wl_title=rc_title'
1165  ]
1166  ],
1167  ]
1168  )
1169  ->will( $this->returnValue( [] ) );
1170 
1171  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1173 
1174  $items = $queryService->getWatchedItemsWithRecentChangeInfo(
1175  $user,
1176  [ 'usedInGenerator' => true, 'allRevisions' => true, ]
1177  );
1178 
1179  $this->assertEmpty( $items );
1180  }
1181 
1183  $mockDb = $this->getMockDb();
1184  $mockDb->expects( $this->once() )
1185  ->method( 'select' )
1186  ->with(
1187  $this->isType( 'array' ),
1188  $this->isType( 'array' ),
1189  [
1190  'wl_user' => 2,
1191  '(rc_this_oldid=page_latest) OR (rc_type=3)',
1192  ],
1193  $this->isType( 'string' ),
1194  $this->isType( 'array' ),
1195  $this->isType( 'array' )
1196  )
1197  ->will( $this->returnValue( [] ) );
1198 
1199  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1201  $otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2 );
1202  $otherUser->expects( $this->once() )
1203  ->method( 'getOption' )
1204  ->with( 'watchlisttoken' )
1205  ->willReturn( '0123456789abcdef' );
1206 
1207  $items = $queryService->getWatchedItemsWithRecentChangeInfo(
1208  $user,
1209  [ 'watchlistOwner' => $otherUser, 'watchlistOwnerToken' => '0123456789abcdef' ]
1210  );
1211 
1212  $this->assertEmpty( $items );
1213  }
1214 
1215  public function invalidWatchlistTokenProvider() {
1216  return [
1217  [ 'wrongToken' ],
1218  [ '' ],
1219  ];
1220  }
1221 
1226  $mockDb = $this->getMockDb();
1227  $mockDb->expects( $this->never() )
1228  ->method( $this->anything() );
1229 
1230  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1232  $otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2 );
1233  $otherUser->expects( $this->once() )
1234  ->method( 'getOption' )
1235  ->with( 'watchlisttoken' )
1236  ->willReturn( '0123456789abcdef' );
1237 
1238  $this->setExpectedException( ApiUsageException::class, 'Incorrect watchlist token provided' );
1239  $queryService->getWatchedItemsWithRecentChangeInfo(
1240  $user,
1241  [ 'watchlistOwner' => $otherUser, 'watchlistOwnerToken' => $token ]
1242  );
1243  }
1244 
1245  public function testGetWatchedItemsForUser() {
1246  $mockDb = $this->getMockDb();
1247  $mockDb->expects( $this->once() )
1248  ->method( 'select' )
1249  ->with(
1250  'watchlist',
1251  [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1252  [ 'wl_user' => 1 ]
1253  )
1254  ->will( $this->returnValue( [
1255  $this->getFakeRow( [
1256  'wl_namespace' => 0,
1257  'wl_title' => 'Foo1',
1258  'wl_notificationtimestamp' => '20151212010101',
1259  ] ),
1260  $this->getFakeRow( [
1261  'wl_namespace' => 1,
1262  'wl_title' => 'Foo2',
1263  'wl_notificationtimestamp' => null,
1264  ] ),
1265  ] ) );
1266 
1267  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1268  $user = $this->getMockNonAnonUserWithId( 1 );
1269 
1270  $items = $queryService->getWatchedItemsForUser( $user );
1271 
1272  $this->assertInternalType( 'array', $items );
1273  $this->assertCount( 2, $items );
1274  $this->assertContainsOnlyInstancesOf( WatchedItem::class, $items );
1275  $this->assertEquals(
1276  new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1277  $items[0]
1278  );
1279  $this->assertEquals(
1280  new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1281  $items[1]
1282  );
1283  }
1284 
1286  return [
1287  [
1288  [ 'namespaceIds' => [ 0, 1 ], ],
1289  [ 'wl_namespace' => [ 0, 1 ], ],
1290  []
1291  ],
1292  [
1293  [ 'sort' => WatchedItemQueryService::SORT_ASC, ],
1294  [],
1295  [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1296  ],
1297  [
1298  [
1299  'namespaceIds' => [ 0 ],
1301  ],
1302  [ 'wl_namespace' => [ 0 ], ],
1303  [ 'ORDER BY' => 'wl_title ASC' ]
1304  ],
1305  [
1306  [ 'limit' => 10 ],
1307  [],
1308  [ 'LIMIT' => 10 ]
1309  ],
1310  [
1311  [
1312  'namespaceIds' => [ 0, "1; DROP TABLE watchlist;\n--" ],
1313  'limit' => "10; DROP TABLE watchlist;\n--",
1314  ],
1315  [ 'wl_namespace' => [ 0, 1 ], ],
1316  [ 'LIMIT' => 10 ]
1317  ],
1318  [
1320  [ 'wl_notificationtimestamp IS NOT NULL' ],
1321  []
1322  ],
1323  [
1325  [ 'wl_notificationtimestamp IS NULL' ],
1326  []
1327  ],
1328  [
1329  [ 'sort' => WatchedItemQueryService::SORT_DESC, ],
1330  [],
1331  [ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
1332  ],
1333  [
1334  [
1335  'namespaceIds' => [ 0 ],
1337  ],
1338  [ 'wl_namespace' => [ 0 ], ],
1339  [ 'ORDER BY' => 'wl_title DESC' ]
1340  ],
1341  ];
1342  }
1343 
1348  array $options,
1349  array $expectedConds,
1350  array $expectedDbOptions
1351  ) {
1352  $mockDb = $this->getMockDb();
1353  $user = $this->getMockNonAnonUserWithId( 1 );
1354 
1355  $expectedConds = array_merge( [ 'wl_user' => 1 ], $expectedConds );
1356  $mockDb->expects( $this->once() )
1357  ->method( 'select' )
1358  ->with(
1359  'watchlist',
1360  [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1361  $expectedConds,
1362  $this->isType( 'string' ),
1363  $expectedDbOptions
1364  )
1365  ->will( $this->returnValue( [] ) );
1366 
1367  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1368 
1369  $items = $queryService->getWatchedItemsForUser( $user, $options );
1370  $this->assertEmpty( $items );
1371  }
1372 
1374  return [
1375  [
1376  [
1377  'from' => new TitleValue( 0, 'SomeDbKey' ),
1379  ],
1380  [ "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'SomeDbKey'))", ],
1381  [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1382  ],
1383  [
1384  [
1385  'from' => new TitleValue( 0, 'SomeDbKey' ),
1387  ],
1388  [ "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeDbKey'))", ],
1389  [ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
1390  ],
1391  [
1392  [
1393  'until' => new TitleValue( 0, 'SomeDbKey' ),
1395  ],
1396  [ "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeDbKey'))", ],
1397  [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1398  ],
1399  [
1400  [
1401  'until' => new TitleValue( 0, 'SomeDbKey' ),
1403  ],
1404  [ "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'SomeDbKey'))", ],
1405  [ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
1406  ],
1407  [
1408  [
1409  'from' => new TitleValue( 0, 'AnotherDbKey' ),
1410  'until' => new TitleValue( 0, 'SomeOtherDbKey' ),
1411  'startFrom' => new TitleValue( 0, 'SomeDbKey' ),
1413  ],
1414  [
1415  "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'AnotherDbKey'))",
1416  "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeOtherDbKey'))",
1417  "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'SomeDbKey'))",
1418  ],
1419  [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1420  ],
1421  [
1422  [
1423  'from' => new TitleValue( 0, 'SomeOtherDbKey' ),
1424  'until' => new TitleValue( 0, 'AnotherDbKey' ),
1425  'startFrom' => new TitleValue( 0, 'SomeDbKey' ),
1427  ],
1428  [
1429  "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeOtherDbKey'))",
1430  "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'AnotherDbKey'))",
1431  "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeDbKey'))",
1432  ],
1433  [ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
1434  ],
1435  ];
1436  }
1437 
1442  array $options,
1443  array $expectedConds,
1444  array $expectedDbOptions
1445  ) {
1446  $user = $this->getMockNonAnonUserWithId( 1 );
1447 
1448  $expectedConds = array_merge( [ 'wl_user' => 1 ], $expectedConds );
1449 
1450  $mockDb = $this->getMockDb();
1451  $mockDb->expects( $this->any() )
1452  ->method( 'addQuotes' )
1453  ->will( $this->returnCallback( function( $value ) {
1454  return "'$value'";
1455  } ) );
1456  $mockDb->expects( $this->any() )
1457  ->method( 'makeList' )
1458  ->with(
1459  $this->isType( 'array' ),
1460  $this->isType( 'int' )
1461  )
1462  ->will( $this->returnCallback( function( $a, $conj ) {
1463  $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
1464  return join( $sqlConj, array_map( function( $s ) {
1465  return '(' . $s . ')';
1466  }, $a
1467  ) );
1468  } ) );
1469  $mockDb->expects( $this->once() )
1470  ->method( 'select' )
1471  ->with(
1472  'watchlist',
1473  [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1474  $expectedConds,
1475  $this->isType( 'string' ),
1476  $expectedDbOptions
1477  )
1478  ->will( $this->returnValue( [] ) );
1479 
1480  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1481 
1482  $items = $queryService->getWatchedItemsForUser( $user, $options );
1483  $this->assertEmpty( $items );
1484  }
1485 
1487  return [
1488  [
1489  [ 'sort' => 'foo' ],
1490  'Bad value for parameter $options[\'sort\']'
1491  ],
1492  [
1493  [ 'filter' => 'foo' ],
1494  'Bad value for parameter $options[\'filter\']'
1495  ],
1496  [
1497  [ 'from' => new TitleValue( 0, 'SomeDbKey' ), ],
1498  'Bad value for parameter $options[\'sort\']: must be provided'
1499  ],
1500  [
1501  [ 'until' => new TitleValue( 0, 'SomeDbKey' ), ],
1502  'Bad value for parameter $options[\'sort\']: must be provided'
1503  ],
1504  [
1505  [ 'startFrom' => new TitleValue( 0, 'SomeDbKey' ), ],
1506  'Bad value for parameter $options[\'sort\']: must be provided'
1507  ],
1508  ];
1509  }
1510 
1515  array $options,
1516  $expectedInExceptionMessage
1517  ) {
1518  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $this->getMockDb() ) );
1519 
1520  $this->setExpectedException( InvalidArgumentException::class, $expectedInExceptionMessage );
1521  $queryService->getWatchedItemsForUser( $this->getMockNonAnonUserWithId( 1 ), $options );
1522  }
1523 
1525  $mockDb = $this->getMockDb();
1526 
1527  $mockDb->expects( $this->never() )
1528  ->method( $this->anything() );
1529 
1530  $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1531 
1532  $items = $queryService->getWatchedItemsForUser( $this->getMockAnonUser() );
1533  $this->assertEmpty( $items );
1534  }
1535 
1536 }
Revision\DELETED_USER
const DELETED_USER
Definition: Revision.php:92
WatchedItemQueryServiceUnitTest\testGetWatchedItemsForUser
testGetWatchedItemsForUser()
Definition: WatchedItemQueryServiceUnitTest.php:1245
WatchedItemQueryServiceUnitTest\mysqlIndexOptimizationProvider
mysqlIndexOptimizationProvider()
Definition: WatchedItemQueryServiceUnitTest.php:811
WatchedItemQueryServiceUnitTest\testGetWatchedItemsWithRecentChangeInfo_mysqlIndexOptimization
testGetWatchedItemsWithRecentChangeInfo_mysqlIndexOptimization( $dbType, array $options, array $expectedExtraConds)
mysqlIndexOptimizationProvider
Definition: WatchedItemQueryServiceUnitTest.php:839
Revision\DELETED_RESTRICTED
const DELETED_RESTRICTED
Definition: Revision.php:93
WatchedItemQueryServiceUnitTest\testGetWatchedItemsWithRecentChangeInfo_watchlistOwnerAndInvalidToken
testGetWatchedItemsWithRecentChangeInfo_watchlistOwnerAndInvalidToken( $token)
invalidWatchlistTokenProvider
Definition: WatchedItemQueryServiceUnitTest.php:1225
WatchedItemQueryServiceUnitTest\testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorAllRevisionsOptions
testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorAllRevisionsOptions()
Definition: WatchedItemQueryServiceUnitTest.php:1140
$tables
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
Definition: hooks.txt:990
WatchedItemQueryService\INCLUDE_COMMENT
const INCLUDE_COMMENT
Definition: WatchedItemQueryService.php:26
WatchedItemQueryServiceUnitTest\testGetWatchedItemsWithRecentChangeInfo
testGetWatchedItemsWithRecentChangeInfo()
Definition: WatchedItemQueryServiceUnitTest.php:162
WatchedItemQueryServiceUnitTest\getWatchedItemsWithRecentChangeInfoOptionsProvider
getWatchedItemsWithRecentChangeInfoOptionsProvider()
Definition: WatchedItemQueryServiceUnitTest.php:445
WatchedItemQueryServiceUnitTest
WatchedItemQueryService.
Definition: WatchedItemQueryServiceUnitTest.php:8
WatchedItemQueryServiceUnitTest\getMockAnonUser
getMockAnonUser()
Definition: WatchedItemQueryServiceUnitTest.php:146
WatchedItemQueryServiceUnitTest\testGetWatchedItemsWithRecentChangeInfo_allRevisionsOptionAndEmptyResult
testGetWatchedItemsWithRecentChangeInfo_allRevisionsOptionAndEmptyResult()
Definition: WatchedItemQueryServiceUnitTest.php:968
WatchedItemQueryServiceUnitTest\getWatchedItemsWithRecentChangeInfoInvalidOptionsProvider
getWatchedItemsWithRecentChangeInfoInvalidOptionsProvider()
Definition: WatchedItemQueryServiceUnitTest.php:1010
WatchedItemQueryServiceUnitTest\getMockNonAnonUserWithIdAndRestrictedPermissions
getMockNonAnonUserWithIdAndRestrictedPermissions( $id, $notAllowedAction)
Definition: WatchedItemQueryServiceUnitTest.php:104
WatchedItemQueryService\FILTER_PATROLLED
const FILTER_PATROLLED
Definition: WatchedItemQueryService.php:40
WatchedItemQueryServiceUnitTest\getMockLoadBalancer
getMockLoadBalancer( $mockDb)
Definition: WatchedItemQueryServiceUnitTest.php:55
WatchedItemQueryServiceUnitTest\testGetWatchedItemsWithRecentChangeInfo_userPermissionRelatedExtraChecks
testGetWatchedItemsWithRecentChangeInfo_userPermissionRelatedExtraChecks(array $options, $notAllowedAction, array $expectedExtraConds)
userPermissionRelatedExtraChecksProvider
Definition: WatchedItemQueryServiceUnitTest.php:939
WatchedItemQueryServiceUnitTest\testGetWatchedItemsForUser_invalidOptionThrowsException
testGetWatchedItemsForUser_invalidOptionThrowsException(array $options, $expectedInExceptionMessage)
getWatchedItemsForUserInvalidOptionsProvider
Definition: WatchedItemQueryServiceUnitTest.php:1514
RC_LOG
const RC_LOG
Definition: Defines.php:142
use
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
Definition: MIT-LICENSE.txt:10
$user
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 account $user
Definition: hooks.txt:246
anything
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
Definition: distributors.txt:39
WatchedItemQueryServiceUnitTest\testGetWatchedItemsWithRecentChangeInfo_extension
testGetWatchedItemsWithRecentChangeInfo_extension()
Definition: WatchedItemQueryServiceUnitTest.php:283
RC_EDIT
const RC_EDIT
Definition: Defines.php:140
DB_SLAVE
const DB_SLAVE
Definition: Defines.php:34
$s
$s
Definition: mergeMessageFileList.php:188
$res
$res
Definition: database.txt:21
WatchedItemQueryServiceUnitTest\filterPatrolledOptionProvider
filterPatrolledOptionProvider()
Definition: WatchedItemQueryServiceUnitTest.php:774
php
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:35
LIST_AND
const LIST_AND
Definition: Defines.php:41
WatchedItemQueryService\INCLUDE_LOG_INFO
const INCLUDE_LOG_INFO
Definition: WatchedItemQueryService.php:29
WatchedItemQueryServiceUnitTest\getMockNonAnonUserWithIdAndNoPatrolRights
getMockNonAnonUserWithIdAndNoPatrolRights( $id)
Definition: WatchedItemQueryServiceUnitTest.php:126
WatchedItemQueryServiceUnitTest\getWatchedItemsForUserInvalidOptionsProvider
getWatchedItemsForUserInvalidOptionsProvider()
Definition: WatchedItemQueryServiceUnitTest.php:1486
WatchedItemQueryService\FILTER_MINOR
const FILTER_MINOR
Definition: WatchedItemQueryService.php:34
WatchedItemQueryService\FILTER_NOT_CHANGED
const FILTER_NOT_CHANGED
Definition: WatchedItemQueryService.php:45
WatchedItemQueryService\INCLUDE_USER
const INCLUDE_USER
Definition: WatchedItemQueryService.php:24
WatchedItemQueryService\FILTER_NOT_BOT
const FILTER_NOT_BOT
Definition: WatchedItemQueryService.php:37
WatchedItemQueryService
Definition: WatchedItemQueryService.php:18
WatchedItemQueryService\INCLUDE_PATROL_INFO
const INCLUDE_PATROL_INFO
Definition: WatchedItemQueryService.php:27
WatchedItemQueryServiceUnitTest\testGetWatchedItemsForUser_userNotAllowedToViewWatchlist
testGetWatchedItemsForUser_userNotAllowedToViewWatchlist()
Definition: WatchedItemQueryServiceUnitTest.php:1524
WatchedItemQueryService\FILTER_BOT
const FILTER_BOT
Definition: WatchedItemQueryService.php:36
WatchedItemQueryService\INCLUDE_USER_ID
const INCLUDE_USER_ID
Definition: WatchedItemQueryService.php:25
WatchedItemQueryServiceUnitTest\invalidWatchlistTokenProvider
invalidWatchlistTokenProvider()
Definition: WatchedItemQueryServiceUnitTest.php:1215
WatchedItemQueryServiceUnitTest\testGetWatchedItemsWithRecentChangeInfo_optionsAndEmptyResult
testGetWatchedItemsWithRecentChangeInfo_optionsAndEmptyResult(array $options, $startFrom, array $expectedExtraFields, array $expectedExtraConds, array $expectedDbOptions)
getWatchedItemsWithRecentChangeInfoOptionsProvider
Definition: WatchedItemQueryServiceUnitTest.php:712
WatchedItemQueryServiceUnitTest\provideGetWatchedItemsForUserOptions
provideGetWatchedItemsForUserOptions()
Definition: WatchedItemQueryServiceUnitTest.php:1285
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
WatchedItemQueryServiceUnitTest\testGetWatchedItemsWithRecentChangeInfo_invalidOptions
testGetWatchedItemsWithRecentChangeInfo_invalidOptions(array $options, $startFrom, $expectedInExceptionMessage)
getWatchedItemsWithRecentChangeInfoInvalidOptionsProvider
Definition: WatchedItemQueryServiceUnitTest.php:1078
LogPage\DELETED_ACTION
const DELETED_ACTION
Definition: LogPage.php:32
WatchedItemQueryService\DIR_NEWER
const DIR_NEWER
Definition: WatchedItemQueryService.php:21
any
they could even be mouse clicks or menu items whatever suits your program You should also get your if any
Definition: COPYING.txt:326
$value
$value
Definition: styleTest.css.php:45
WatchedItemQueryServiceUnitTest\getMockUnrestrictedNonAnonUserWithId
getMockUnrestrictedNonAnonUserWithId( $id)
Definition: WatchedItemQueryServiceUnitTest.php:85
WatchedItemQueryService\SORT_DESC
const SORT_DESC
Definition: WatchedItemQueryService.php:48
WatchedItem
Representation of a pair of user and title for watchlist entries.
Definition: WatchedItem.php:32
WatchedItemQueryService\FILTER_NOT_PATROLLED
const FILTER_NOT_PATROLLED
Definition: WatchedItemQueryService.php:41
WatchedItemQueryService\SORT_ASC
const SORT_ASC
Definition: WatchedItemQueryService.php:47
WatchedItemQueryService\FILTER_NOT_MINOR
const FILTER_NOT_MINOR
Definition: WatchedItemQueryService.php:35
RC_NEW
const RC_NEW
Definition: Defines.php:141
WatchedItemQueryService\INCLUDE_FLAGS
const INCLUDE_FLAGS
Definition: WatchedItemQueryService.php:23
WatchedItemQueryServiceUnitTest\testGetWatchedItemsForUser_optionsAndEmptyResult
testGetWatchedItemsForUser_optionsAndEmptyResult(array $options, array $expectedConds, array $expectedDbOptions)
provideGetWatchedItemsForUserOptions
Definition: WatchedItemQueryServiceUnitTest.php:1347
WatchedItemQueryService\DIR_OLDER
const DIR_OLDER
Definition: WatchedItemQueryService.php:20
WatchedItemQueryServiceUnitTest\testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorOptionAndEmptyResult
testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorOptionAndEmptyResult()
Definition: WatchedItemQueryServiceUnitTest.php:1094
WatchedItemQueryServiceUnitTest\testGetWatchedItemsWithRecentChangeInfo_watchlistOwnerOptionAndEmptyResult
testGetWatchedItemsWithRecentChangeInfo_watchlistOwnerOptionAndEmptyResult()
Definition: WatchedItemQueryServiceUnitTest.php:1182
WatchedItemQueryService\FILTER_CHANGED
const FILTER_CHANGED
Definition: WatchedItemQueryService.php:44
WatchedItemQueryService\FILTER_NOT_UNREAD
const FILTER_NOT_UNREAD
Definition: WatchedItemQueryService.php:43
WatchedItemQueryServiceUnitTest\provideGetWatchedItemsForUser_fromUntilStartFromOptions
provideGetWatchedItemsForUser_fromUntilStartFromOptions()
Definition: WatchedItemQueryServiceUnitTest.php:1373
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 as
Definition: distributors.txt:9
WatchedItemQueryService\FILTER_NOT_ANON
const FILTER_NOT_ANON
Definition: WatchedItemQueryService.php:39
WatchedItemQueryServiceUnitTest\getMockDb
getMockDb()
Definition: WatchedItemQueryServiceUnitTest.php:13
LogPage\DELETED_RESTRICTED
const DELETED_RESTRICTED
Definition: LogPage.php:35
WatchedItemQueryServiceUnitTest\testGetWatchedItemsWithRecentChangeInfo_filterPatrolledAndUserWithNoPatrolRights
testGetWatchedItemsWithRecentChangeInfo_filterPatrolledAndUserWithNoPatrolRights( $filtersOption)
filterPatrolledOptionProvider
Definition: WatchedItemQueryServiceUnitTest.php:784
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
WatchedItemQueryServiceUnitTest\getMockNonAnonUserWithId
getMockNonAnonUserWithId( $id)
Definition: WatchedItemQueryServiceUnitTest.php:70
WatchedItemQueryService\INCLUDE_SIZES
const INCLUDE_SIZES
Definition: WatchedItemQueryService.php:28
WatchedItemQueryServiceUnitTest\testGetWatchedItemsForUser_fromUntilStartFromOptions
testGetWatchedItemsForUser_fromUntilStartFromOptions(array $options, array $expectedConds, array $expectedDbOptions)
provideGetWatchedItemsForUser_fromUntilStartFromOptions
Definition: WatchedItemQueryServiceUnitTest.php:1441
WatchedItemQueryService\FILTER_ANON
const FILTER_ANON
Definition: WatchedItemQueryService.php:38
WatchedItemQueryService\FILTER_UNREAD
const FILTER_UNREAD
Definition: WatchedItemQueryService.php:42
WatchedItemQueryServiceUnitTest\getFakeRow
getFakeRow(array $rowValues)
Definition: WatchedItemQueryServiceUnitTest.php:154
WatchedItemQueryServiceUnitTest\userPermissionRelatedExtraChecksProvider
userPermissionRelatedExtraChecksProvider()
Definition: WatchedItemQueryServiceUnitTest.php:871
$options
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1049
array
the array() calling protocol came about after MediaWiki 1.4rc1.
TitleValue
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:36