MediaWiki REL1_34
DatabaseBlock.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Block;
24
27use CommentStore;
29use Hooks;
30use Html;
32use IP;
37use MWException;
39use stdClass;
40use Title;
41use User;
42use WebResponse;
45
59 public $mAuto;
60
66
68 private $mId;
69
71 private $mFromMaster;
72
75
77 private $isHardblock;
78
81
84
103 public function __construct( array $options = [] ) {
104 parent::__construct( $options );
105
106 $defaults = [
107 'user' => null,
108 'auto' => false,
109 'expiry' => '',
110 'anonOnly' => false,
111 'createAccount' => false,
112 'enableAutoblock' => false,
113 'blockEmail' => false,
114 'allowUsertalk' => false,
115 'sitewide' => true,
116 ];
117
118 $options += $defaults;
119
120 if ( $this->target instanceof User && $options['user'] ) {
121 # Needed for foreign users
122 $this->forcedTargetID = $options['user'];
123 }
124
125 $this->setExpiry( wfGetDB( DB_REPLICA )->decodeExpiry( $options['expiry'] ) );
126
127 # Boolean settings
128 $this->mAuto = (bool)$options['auto'];
129 $this->isHardblock( !$options['anonOnly'] );
130 $this->isAutoblocking( (bool)$options['enableAutoblock'] );
131 $this->isSitewide( (bool)$options['sitewide'] );
132 $this->isEmailBlocked( (bool)$options['blockEmail'] );
133 $this->isCreateAccountBlocked( (bool)$options['createAccount'] );
134 $this->isUsertalkEditAllowed( (bool)$options['allowUsertalk'] );
135
136 $this->mFromMaster = false;
137 }
138
145 public static function newFromID( $id ) {
147 $blockQuery = self::getQueryInfo();
148 $res = $dbr->selectRow(
149 $blockQuery['tables'],
150 $blockQuery['fields'],
151 [ 'ipb_id' => $id ],
152 __METHOD__,
153 [],
154 $blockQuery['joins']
155 );
156 if ( $res ) {
157 return self::newFromRow( $res );
158 } else {
159 return null;
160 }
161 }
162
172 public static function getQueryInfo() {
173 $commentQuery = CommentStore::getStore()->getJoin( 'ipb_reason' );
174 $actorQuery = ActorMigration::newMigration()->getJoin( 'ipb_by' );
175 return [
176 'tables' => [ 'ipblocks' ] + $commentQuery['tables'] + $actorQuery['tables'],
177 'fields' => [
178 'ipb_id',
179 'ipb_address',
180 'ipb_timestamp',
181 'ipb_auto',
182 'ipb_anon_only',
183 'ipb_create_account',
184 'ipb_enable_autoblock',
185 'ipb_expiry',
186 'ipb_deleted',
187 'ipb_block_email',
188 'ipb_allow_usertalk',
189 'ipb_parent_block_id',
190 'ipb_sitewide',
191 ] + $commentQuery['fields'] + $actorQuery['fields'],
192 'joins' => $commentQuery['joins'] + $actorQuery['joins'],
193 ];
194 }
195
203 public function equals( DatabaseBlock $block ) {
204 return (
205 (string)$this->target == (string)$block->target
206 && $this->type == $block->type
207 && $this->mAuto == $block->mAuto
208 && $this->isHardblock() == $block->isHardblock()
209 && $this->isCreateAccountBlocked() == $block->isCreateAccountBlocked()
210 && $this->getExpiry() == $block->getExpiry()
211 && $this->isAutoblocking() == $block->isAutoblocking()
212 && $this->getHideName() == $block->getHideName()
213 && $this->isEmailBlocked() == $block->isEmailBlocked()
214 && $this->isUsertalkEditAllowed() == $block->isUsertalkEditAllowed()
215 && $this->getReason() == $block->getReason()
216 && $this->isSitewide() == $block->isSitewide()
217 // DatabaseBlock::getRestrictions() may perform a database query, so
218 // keep it at the end.
219 && $this->getBlockRestrictionStore()->equals(
220 $this->getRestrictions(), $block->getRestrictions()
221 )
222 );
223 }
224
237 protected static function newLoad(
238 $specificTarget,
239 $specificType,
240 $fromMaster,
241 $vagueTarget = null
242 ) {
243 $db = wfGetDB( $fromMaster ? DB_MASTER : DB_REPLICA );
244
245 if ( $specificType !== null ) {
246 $conds = [
247 'ipb_address' => [ (string)$specificTarget ],
248 ];
249 } else {
250 $conds = [ 'ipb_address' => [] ];
251 }
252
253 # Be aware that the != '' check is explicit, since empty values will be
254 # passed by some callers (T31116)
255 if ( $vagueTarget != '' ) {
256 list( $target, $type ) = self::parseTarget( $vagueTarget );
257 switch ( $type ) {
258 case self::TYPE_USER:
259 # Slightly weird, but who are we to argue?
260 $conds['ipb_address'][] = (string)$target;
261 break;
262
263 case self::TYPE_IP:
264 $conds['ipb_address'][] = (string)$target;
265 $conds[] = self::getRangeCond( IP::toHex( $target ) );
266 $conds = $db->makeList( $conds, LIST_OR );
267 break;
268
269 case self::TYPE_RANGE:
270 list( $start, $end ) = IP::parseRange( $target );
271 $conds['ipb_address'][] = (string)$target;
272 $conds[] = self::getRangeCond( $start, $end );
273 $conds = $db->makeList( $conds, LIST_OR );
274 break;
275
276 default:
277 throw new MWException( "Tried to load block with invalid type" );
278 }
279 }
280
281 $blockQuery = self::getQueryInfo();
282 $res = $db->select(
283 $blockQuery['tables'], $blockQuery['fields'], $conds, __METHOD__, [], $blockQuery['joins']
284 );
285
286 $blocks = [];
287 $blockIds = [];
288 $autoBlocks = [];
289 foreach ( $res as $row ) {
290 $block = self::newFromRow( $row );
291
292 # Don't use expired blocks
293 if ( $block->isExpired() ) {
294 continue;
295 }
296
297 # Don't use anon only blocks on users
298 if ( $specificType == self::TYPE_USER && !$block->isHardblock() ) {
299 continue;
300 }
301
302 // Check for duplicate autoblocks
303 if ( $block->getType() === self::TYPE_AUTO ) {
304 $autoBlocks[] = $block;
305 } else {
306 $blocks[] = $block;
307 $blockIds[] = $block->getId();
308 }
309 }
310
311 // Only add autoblocks that aren't duplicates
312 foreach ( $autoBlocks as $block ) {
313 if ( !in_array( $block->mParentBlockId, $blockIds ) ) {
314 $blocks[] = $block;
315 }
316 }
317
318 return $blocks;
319 }
320
333 protected static function chooseMostSpecificBlock( array $blocks ) {
334 if ( count( $blocks ) === 1 ) {
335 return $blocks[0];
336 }
337
338 # This result could contain a block on the user, a block on the IP, and a russian-doll
339 # set of rangeblocks. We want to choose the most specific one, so keep a leader board.
340 $bestBlock = null;
341
342 # Lower will be better
343 $bestBlockScore = 100;
344 foreach ( $blocks as $block ) {
345 if ( $block->getType() == self::TYPE_RANGE ) {
346 # This is the number of bits that are allowed to vary in the block, give
347 # or take some floating point errors
348 $target = $block->getTarget();
349 $max = IP::isIPv6( $target ) ? 128 : 32;
350 list( $network, $bits ) = IP::parseCIDR( $target );
351 $size = $max - $bits;
352
353 # Rank a range block covering a single IP equally with a single-IP block
354 $score = self::TYPE_RANGE - 1 + ( $size / $max );
355
356 } else {
357 $score = $block->getType();
358 }
359
360 if ( $score < $bestBlockScore ) {
361 $bestBlockScore = $score;
362 $bestBlock = $block;
363 }
364 }
365
366 return $bestBlock;
367 }
368
375 public static function getRangeCond( $start, $end = null ) {
376 if ( $end === null ) {
377 $end = $start;
378 }
379 # Per T16634, we want to include relevant active rangeblocks; for
380 # rangeblocks, we want to include larger ranges which enclose the given
381 # range. We know that all blocks must be smaller than $wgBlockCIDRLimit,
382 # so we can improve performance by filtering on a LIKE clause
383 $chunk = self::getIpFragment( $start );
385 $like = $dbr->buildLike( $chunk, $dbr->anyString() );
386
387 # Fairly hard to make a malicious SQL statement out of hex characters,
388 # but stranger things have happened...
389 $safeStart = $dbr->addQuotes( $start );
390 $safeEnd = $dbr->addQuotes( $end );
391
392 return $dbr->makeList(
393 [
394 "ipb_range_start $like",
395 "ipb_range_start <= $safeStart",
396 "ipb_range_end >= $safeEnd",
397 ],
399 );
400 }
401
408 protected static function getIpFragment( $hex ) {
409 global $wgBlockCIDRLimit;
410 if ( substr( $hex, 0, 3 ) == 'v6-' ) {
411 return 'v6-' . substr( substr( $hex, 3 ), 0, floor( $wgBlockCIDRLimit['IPv6'] / 4 ) );
412 } else {
413 return substr( $hex, 0, floor( $wgBlockCIDRLimit['IPv4'] / 4 ) );
414 }
415 }
416
422 protected function initFromRow( $row ) {
423 $this->setTarget( $row->ipb_address );
425 $row->ipb_by, $row->ipb_by_text, $row->ipb_by_actor ?? null
426 ) );
427
428 $this->setTimestamp( wfTimestamp( TS_MW, $row->ipb_timestamp ) );
429 $this->mAuto = $row->ipb_auto;
430 $this->setHideName( $row->ipb_deleted );
431 $this->mId = (int)$row->ipb_id;
432 $this->mParentBlockId = $row->ipb_parent_block_id;
433
434 // I wish I didn't have to do this
435 $db = wfGetDB( DB_REPLICA );
436 $this->setExpiry( $db->decodeExpiry( $row->ipb_expiry ) );
437 $this->setReason(
439 // Legacy because $row may have come from self::selectFields()
440 ->getCommentLegacy( $db, 'ipb_reason', $row )->text
441 );
442
443 $this->isHardblock( !$row->ipb_anon_only );
444 $this->isAutoblocking( $row->ipb_enable_autoblock );
445 $this->isSitewide( (bool)$row->ipb_sitewide );
446
447 $this->isCreateAccountBlocked( $row->ipb_create_account );
448 $this->isEmailBlocked( $row->ipb_block_email );
449 $this->isUsertalkEditAllowed( $row->ipb_allow_usertalk );
450 }
451
457 public static function newFromRow( $row ) {
458 $block = new DatabaseBlock;
459 $block->initFromRow( $row );
460 return $block;
461 }
462
469 public function delete() {
470 if ( wfReadOnly() ) {
471 return false;
472 }
473
474 if ( !$this->getId() ) {
475 throw new MWException(
476 __METHOD__ . " requires that the mId member be filled\n"
477 );
478 }
479
480 $dbw = wfGetDB( DB_MASTER );
481
482 $this->getBlockRestrictionStore()->deleteByParentBlockId( $this->getId() );
483 $dbw->delete( 'ipblocks', [ 'ipb_parent_block_id' => $this->getId() ], __METHOD__ );
484
485 $this->getBlockRestrictionStore()->deleteByBlockId( $this->getId() );
486 $dbw->delete( 'ipblocks', [ 'ipb_id' => $this->getId() ], __METHOD__ );
487
488 return $dbw->affectedRows() > 0;
489 }
490
499 public function insert( IDatabase $dbw = null ) {
501
502 if ( !$this->getBlocker() || $this->getBlocker()->getName() === '' ) {
503 throw new MWException( 'Cannot insert a block without a blocker set' );
504 }
505
506 wfDebug( __METHOD__ . "; timestamp {$this->mTimestamp}\n" );
507
508 if ( $dbw === null ) {
509 $dbw = wfGetDB( DB_MASTER );
510 }
511
513
514 $row = $this->getDatabaseArray( $dbw );
515
516 $dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
517 $affected = $dbw->affectedRows();
518 if ( $affected ) {
519 $this->setId( $dbw->insertId() );
520 if ( $this->restrictions ) {
521 $this->getBlockRestrictionStore()->insert( $this->restrictions );
522 }
523 }
524
525 # Don't collide with expired blocks.
526 # Do this after trying to insert to avoid locking.
527 if ( !$affected ) {
528 # T96428: The ipb_address index uses a prefix on a field, so
529 # use a standard SELECT + DELETE to avoid annoying gap locks.
530 $ids = $dbw->selectFieldValues( 'ipblocks',
531 'ipb_id',
532 [
533 'ipb_address' => $row['ipb_address'],
534 'ipb_user' => $row['ipb_user'],
535 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() )
536 ],
537 __METHOD__
538 );
539 if ( $ids ) {
540 $dbw->delete( 'ipblocks', [ 'ipb_id' => $ids ], __METHOD__ );
541 $this->getBlockRestrictionStore()->deleteByBlockId( $ids );
542 $dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
543 $affected = $dbw->affectedRows();
544 $this->setId( $dbw->insertId() );
545 if ( $this->restrictions ) {
546 $this->getBlockRestrictionStore()->insert( $this->restrictions );
547 }
548 }
549 }
550
551 if ( $affected ) {
552 $auto_ipd_ids = $this->doRetroactiveAutoblock();
553
554 if ( $wgBlockDisablesLogin && $this->target instanceof User ) {
555 // Change user login token to force them to be logged out.
556 $this->target->setToken();
557 $this->target->saveSettings();
558 }
559
560 return [ 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ];
561 }
562
563 return false;
564 }
565
573 public function update() {
574 wfDebug( __METHOD__ . "; timestamp {$this->mTimestamp}\n" );
575 $dbw = wfGetDB( DB_MASTER );
576
577 $dbw->startAtomic( __METHOD__ );
578
579 $result = $dbw->update(
580 'ipblocks',
581 $this->getDatabaseArray( $dbw ),
582 [ 'ipb_id' => $this->getId() ],
583 __METHOD__
584 );
585
586 // Only update the restrictions if they have been modified.
587 if ( $this->restrictions !== null ) {
588 // An empty array should remove all of the restrictions.
589 if ( empty( $this->restrictions ) ) {
590 $success = $this->getBlockRestrictionStore()->deleteByBlockId( $this->getId() );
591 } else {
592 $success = $this->getBlockRestrictionStore()->update( $this->restrictions );
593 }
594 // Update the result. The first false is the result, otherwise, true.
595 $result = $result && $success;
596 }
597
598 if ( $this->isAutoblocking() ) {
599 // update corresponding autoblock(s) (T50813)
600 $dbw->update(
601 'ipblocks',
602 $this->getAutoblockUpdateArray( $dbw ),
603 [ 'ipb_parent_block_id' => $this->getId() ],
604 __METHOD__
605 );
606
607 // Only update the restrictions if they have been modified.
608 if ( $this->restrictions !== null ) {
609 $this->getBlockRestrictionStore()->updateByParentBlockId( $this->getId(), $this->restrictions );
610 }
611 } else {
612 // autoblock no longer required, delete corresponding autoblock(s)
613 $this->getBlockRestrictionStore()->deleteByParentBlockId( $this->getId() );
614 $dbw->delete(
615 'ipblocks',
616 [ 'ipb_parent_block_id' => $this->getId() ],
617 __METHOD__
618 );
619 }
620
621 $dbw->endAtomic( __METHOD__ );
622
623 if ( $result ) {
624 $auto_ipd_ids = $this->doRetroactiveAutoblock();
625 return [ 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ];
626 }
627
628 return $result;
629 }
630
636 protected function getDatabaseArray( IDatabase $dbw ) {
637 $expiry = $dbw->encodeExpiry( $this->getExpiry() );
638
639 if ( $this->forcedTargetID ) {
641 } else {
642 $uid = $this->target instanceof User ? $this->target->getId() : 0;
643 }
644
645 $a = [
646 'ipb_address' => (string)$this->target,
647 'ipb_user' => $uid,
648 'ipb_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
649 'ipb_auto' => $this->mAuto,
650 'ipb_anon_only' => !$this->isHardblock(),
651 'ipb_create_account' => $this->isCreateAccountBlocked(),
652 'ipb_enable_autoblock' => $this->isAutoblocking(),
653 'ipb_expiry' => $expiry,
654 'ipb_range_start' => $this->getRangeStart(),
655 'ipb_range_end' => $this->getRangeEnd(),
656 'ipb_deleted' => intval( $this->getHideName() ), // typecast required for SQLite
657 'ipb_block_email' => $this->isEmailBlocked(),
658 'ipb_allow_usertalk' => $this->isUsertalkEditAllowed(),
659 'ipb_parent_block_id' => $this->mParentBlockId,
660 'ipb_sitewide' => $this->isSitewide(),
661 ] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->getReason() )
662 + ActorMigration::newMigration()->getInsertValues( $dbw, 'ipb_by', $this->getBlocker() );
663
664 return $a;
665 }
666
671 protected function getAutoblockUpdateArray( IDatabase $dbw ) {
672 return [
673 'ipb_create_account' => $this->isCreateAccountBlocked(),
674 'ipb_deleted' => (int)$this->getHideName(), // typecast required for SQLite
675 'ipb_allow_usertalk' => $this->isUsertalkEditAllowed(),
676 'ipb_sitewide' => $this->isSitewide(),
677 ] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->getReason() )
678 + ActorMigration::newMigration()->getInsertValues( $dbw, 'ipb_by', $this->getBlocker() );
679 }
680
687 protected function doRetroactiveAutoblock() {
688 $blockIds = [];
689 # If autoblock is enabled, autoblock the LAST IP(s) used
690 if ( $this->isAutoblocking() && $this->getType() == self::TYPE_USER ) {
691 wfDebug( "Doing retroactive autoblocks for " . $this->getTarget() . "\n" );
692
693 $continue = Hooks::run(
694 'PerformRetroactiveAutoblock', [ $this, &$blockIds ] );
695
696 if ( $continue ) {
697 self::defaultRetroactiveAutoblock( $this, $blockIds );
698 }
699 }
700 return $blockIds;
701 }
702
710 protected static function defaultRetroactiveAutoblock( DatabaseBlock $block, array &$blockIds ) {
711 global $wgPutIPinRC;
712
713 // No IPs are in recentchanges table, so nothing to select
714 if ( !$wgPutIPinRC ) {
715 return;
716 }
717
718 // Autoblocks only apply to TYPE_USER
719 if ( $block->getType() !== self::TYPE_USER ) {
720 return;
721 }
722 $target = $block->getTarget(); // TYPE_USER => always a User object
723
725 $rcQuery = ActorMigration::newMigration()->getWhere( $dbr, 'rc_user', $target, false );
726
727 $options = [ 'ORDER BY' => 'rc_timestamp DESC' ];
728
729 // Just the last IP used.
730 $options['LIMIT'] = 1;
731
732 $res = $dbr->select(
733 [ 'recentchanges' ] + $rcQuery['tables'],
734 [ 'rc_ip' ],
735 $rcQuery['conds'],
736 __METHOD__,
737 $options,
738 $rcQuery['joins']
739 );
740
741 if ( !$res->numRows() ) {
742 # No results, don't autoblock anything
743 wfDebug( "No IP found to retroactively autoblock\n" );
744 } else {
745 foreach ( $res as $row ) {
746 if ( $row->rc_ip ) {
747 $id = $block->doAutoblock( $row->rc_ip );
748 if ( $id ) {
749 $blockIds[] = $id;
750 }
751 }
752 }
753 }
754 }
755
763 public static function isWhitelistedFromAutoblocks( $ip ) {
764 // Try to get the autoblock_whitelist from the cache, as it's faster
765 // than getting the msg raw and explode()'ing it.
766 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
767 $lines = $cache->getWithSetCallback(
768 $cache->makeKey( 'ip-autoblock', 'whitelist' ),
769 $cache::TTL_DAY,
770 function ( $curValue, &$ttl, array &$setOpts ) {
771 $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
772
773 return explode( "\n",
774 wfMessage( 'autoblock_whitelist' )->inContentLanguage()->plain() );
775 }
776 );
777
778 wfDebug( "Checking the autoblock whitelist..\n" );
779
780 foreach ( $lines as $line ) {
781 # List items only
782 if ( substr( $line, 0, 1 ) !== '*' ) {
783 continue;
784 }
785
786 $wlEntry = substr( $line, 1 );
787 $wlEntry = trim( $wlEntry );
788
789 wfDebug( "Checking $ip against $wlEntry..." );
790
791 # Is the IP in this range?
792 if ( IP::isInRange( $ip, $wlEntry ) ) {
793 wfDebug( " IP $ip matches $wlEntry, not autoblocking\n" );
794 return true;
795 } else {
796 wfDebug( " No match\n" );
797 }
798 }
799
800 return false;
801 }
802
809 public function doAutoblock( $autoblockIP ) {
810 # If autoblocks are disabled, go away.
811 if ( !$this->isAutoblocking() ) {
812 return false;
813 }
814
815 # Check for presence on the autoblock whitelist.
816 if ( self::isWhitelistedFromAutoblocks( $autoblockIP ) ) {
817 return false;
818 }
819
820 // Avoid PHP 7.1 warning of passing $this by reference
821 $block = $this;
822 # Allow hooks to cancel the autoblock.
823 if ( !Hooks::run( 'AbortAutoblock', [ $autoblockIP, &$block ] ) ) {
824 wfDebug( "Autoblock aborted by hook.\n" );
825 return false;
826 }
827
828 # It's okay to autoblock. Go ahead and insert/update the block...
829
830 # Do not add a *new* block if the IP is already blocked.
831 $ipblock = self::newFromTarget( $autoblockIP );
832 if ( $ipblock ) {
833 # Check if the block is an autoblock and would exceed the user block
834 # if renewed. If so, do nothing, otherwise prolong the block time...
835 if ( $ipblock->mAuto && // @todo Why not compare $ipblock->mExpiry?
836 $this->getExpiry() > self::getAutoblockExpiry( $ipblock->getTimestamp() )
837 ) {
838 # Reset block timestamp to now and its expiry to
839 # $wgAutoblockExpiry in the future
840 $ipblock->updateTimestamp();
841 }
842 return false;
843 }
844
845 # Make a new block object with the desired properties.
846 $autoblock = new DatabaseBlock;
847 wfDebug( "Autoblocking {$this->getTarget()}@" . $autoblockIP . "\n" );
848 $autoblock->setTarget( $autoblockIP );
849 $autoblock->setBlocker( $this->getBlocker() );
850 $autoblock->setReason(
851 wfMessage( 'autoblocker', $this->getTarget(), $this->getReason() )
852 ->inContentLanguage()->plain()
853 );
854 $timestamp = wfTimestampNow();
855 $autoblock->setTimestamp( $timestamp );
856 $autoblock->mAuto = 1;
857 $autoblock->isCreateAccountBlocked( $this->isCreateAccountBlocked() );
858 # Continue suppressing the name if needed
859 $autoblock->setHideName( $this->getHideName() );
860 $autoblock->isUsertalkEditAllowed( $this->isUsertalkEditAllowed() );
861 $autoblock->mParentBlockId = $this->mId;
862 $autoblock->isSitewide( $this->isSitewide() );
863 $autoblock->setRestrictions( $this->getRestrictions() );
864
865 if ( $this->getExpiry() == 'infinity' ) {
866 # Original block was indefinite, start an autoblock now
867 $autoblock->setExpiry( self::getAutoblockExpiry( $timestamp ) );
868 } else {
869 # If the user is already blocked with an expiry date, we don't
870 # want to pile on top of that.
871 $autoblock->setExpiry( min( $this->getExpiry(), self::getAutoblockExpiry( $timestamp ) ) );
872 }
873
874 # Insert the block...
875 $status = $autoblock->insert();
876 return $status
877 ? $status['id']
878 : false;
879 }
880
885 public function deleteIfExpired() {
886 if ( $this->isExpired() ) {
887 wfDebug( __METHOD__ . " -- deleting\n" );
888 $this->delete();
889 $retVal = true;
890 } else {
891 wfDebug( __METHOD__ . " -- not expired\n" );
892 $retVal = false;
893 }
894
895 return $retVal;
896 }
897
902 public function isExpired() {
903 $timestamp = wfTimestampNow();
904 wfDebug( __METHOD__ . " checking current " . $timestamp . " vs $this->mExpiry\n" );
905
906 if ( !$this->getExpiry() ) {
907 return false;
908 } else {
909 return $timestamp > $this->getExpiry();
910 }
911 }
912
919 public function isValid() {
920 wfDeprecated( __METHOD__, '1.33' );
921 return $this->getTarget() != null;
922 }
923
927 public function updateTimestamp() {
928 if ( $this->mAuto ) {
929 $this->setTimestamp( wfTimestamp() );
930 $this->setExpiry( self::getAutoblockExpiry( $this->getTimestamp() ) );
931
932 $dbw = wfGetDB( DB_MASTER );
933 $dbw->update( 'ipblocks',
934 [ /* SET */
935 'ipb_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
936 'ipb_expiry' => $dbw->timestamp( $this->getExpiry() ),
937 ],
938 [ /* WHERE */
939 'ipb_id' => $this->getId(),
940 ],
941 __METHOD__
942 );
943 }
944 }
945
951 public function getRangeStart() {
952 switch ( $this->type ) {
953 case self::TYPE_USER:
954 return '';
955 case self::TYPE_IP:
956 return IP::toHex( $this->target );
957 case self::TYPE_RANGE:
958 list( $start, /*...*/ ) = IP::parseRange( $this->target );
959 return $start;
960 default:
961 throw new MWException( "Block with invalid type" );
962 }
963 }
964
970 public function getRangeEnd() {
971 switch ( $this->type ) {
972 case self::TYPE_USER:
973 return '';
974 case self::TYPE_IP:
975 return IP::toHex( $this->target );
976 case self::TYPE_RANGE:
977 list( /*...*/, $end ) = IP::parseRange( $this->target );
978 return $end;
979 default:
980 throw new MWException( "Block with invalid type" );
981 }
982 }
983
987 public function getId() {
988 return $this->mId;
989 }
990
997 private function setId( $blockId ) {
998 $this->mId = (int)$blockId;
999
1000 if ( is_array( $this->restrictions ) ) {
1001 $this->restrictions = $this->getBlockRestrictionStore()->setBlockId(
1002 $blockId, $this->restrictions
1003 );
1004 }
1005
1006 return $this;
1007 }
1008
1013 public function getParentBlockId() {
1014 return $this->mParentBlockId;
1015 }
1016
1023 public function fromMaster( $x = null ) {
1024 return wfSetVar( $this->mFromMaster, $x );
1025 }
1026
1032 public function isHardblock( $x = null ) {
1033 wfSetVar( $this->isHardblock, $x );
1034
1035 # You can't *not* hardblock a user
1036 return $this->getType() == self::TYPE_USER
1037 ? true
1039 }
1040
1045 public function isAutoblocking( $x = null ) {
1046 wfSetVar( $this->isAutoblocking, $x );
1047
1048 # You can't put an autoblock on an IP or range as we don't have any history to
1049 # look over to get more IPs from
1050 return $this->getType() == self::TYPE_USER
1051 ? $this->isAutoblocking
1052 : false;
1053 }
1054
1059 public function getRedactedName() {
1060 if ( $this->mAuto ) {
1061 return Html::element(
1062 'span',
1063 [ 'class' => 'mw-autoblockid' ],
1064 wfMessage( 'autoblockid', $this->mId )->text()
1065 );
1066 } else {
1067 return htmlspecialchars( $this->getTarget() );
1068 }
1069 }
1070
1077 public static function getAutoblockExpiry( $timestamp ) {
1078 global $wgAutoblockExpiry;
1079
1080 return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry );
1081 }
1082
1086 public static function purgeExpired() {
1087 if ( wfReadOnly() ) {
1088 return;
1089 }
1090
1091 DeferredUpdates::addUpdate( new AutoCommitUpdate(
1092 wfGetDB( DB_MASTER ),
1093 __METHOD__,
1094 function ( IDatabase $dbw, $fname ) {
1095 $ids = $dbw->selectFieldValues( 'ipblocks',
1096 'ipb_id',
1097 [ 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
1098 $fname
1099 );
1100 if ( $ids ) {
1101 $blockRestrictionStore = MediaWikiServices::getInstance()->getBlockRestrictionStore();
1102 $blockRestrictionStore->deleteByBlockId( $ids );
1103
1104 $dbw->delete( 'ipblocks', [ 'ipb_id' => $ids ], $fname );
1105 }
1106 }
1107 ) );
1108 }
1109
1130 public static function newFromTarget( $specificTarget, $vagueTarget = null, $fromMaster = false ) {
1131 $blocks = self::newListFromTarget( $specificTarget, $vagueTarget, $fromMaster );
1132 return self::chooseMostSpecificBlock( $blocks );
1133 }
1134
1144 public static function newListFromTarget(
1145 $specificTarget,
1146 $vagueTarget = null,
1147 $fromMaster = false
1148 ) {
1149 list( $target, $type ) = self::parseTarget( $specificTarget );
1150 if ( $type == self::TYPE_ID || $type == self::TYPE_AUTO ) {
1151 $block = self::newFromID( $target );
1152 return $block ? [ $block ] : [];
1153 } elseif ( $target === null && $vagueTarget == '' ) {
1154 # We're not going to find anything useful here
1155 # Be aware that the == '' check is explicit, since empty values will be
1156 # passed by some callers (T31116)
1157 return [];
1158 } elseif ( in_array(
1159 $type,
1160 [ self::TYPE_USER, self::TYPE_IP, self::TYPE_RANGE, null ] )
1161 ) {
1162 return self::newLoad( $target, $type, $fromMaster, $vagueTarget );
1163 }
1164 return [];
1165 }
1166
1177 public static function getBlocksForIPList( array $ipChain, $isAnon, $fromMaster = false ) {
1178 if ( $ipChain === [] ) {
1179 return [];
1180 }
1181
1182 $conds = [];
1183 $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
1184 foreach ( array_unique( $ipChain ) as $ipaddr ) {
1185 # Discard invalid IP addresses. Since XFF can be spoofed and we do not
1186 # necessarily trust the header given to us, make sure that we are only
1187 # checking for blocks on well-formatted IP addresses (IPv4 and IPv6).
1188 # Do not treat private IP spaces as special as it may be desirable for wikis
1189 # to block those IP ranges in order to stop misbehaving proxies that spoof XFF.
1190 if ( !IP::isValid( $ipaddr ) ) {
1191 continue;
1192 }
1193 # Don't check trusted IPs (includes local CDNs which will be in every request)
1194 if ( $proxyLookup->isTrustedProxy( $ipaddr ) ) {
1195 continue;
1196 }
1197 # Check both the original IP (to check against single blocks), as well as build
1198 # the clause to check for rangeblocks for the given IP.
1199 $conds['ipb_address'][] = $ipaddr;
1200 $conds[] = self::getRangeCond( IP::toHex( $ipaddr ) );
1201 }
1202
1203 if ( $conds === [] ) {
1204 return [];
1205 }
1206
1207 if ( $fromMaster ) {
1208 $db = wfGetDB( DB_MASTER );
1209 } else {
1210 $db = wfGetDB( DB_REPLICA );
1211 }
1212 $conds = $db->makeList( $conds, LIST_OR );
1213 if ( !$isAnon ) {
1214 $conds = [ $conds, 'ipb_anon_only' => 0 ];
1215 }
1216 $blockQuery = self::getQueryInfo();
1217 $rows = $db->select(
1218 $blockQuery['tables'],
1219 array_merge( [ 'ipb_range_start', 'ipb_range_end' ], $blockQuery['fields'] ),
1220 $conds,
1221 __METHOD__,
1222 [],
1223 $blockQuery['joins']
1224 );
1225
1226 $blocks = [];
1227 foreach ( $rows as $row ) {
1228 $block = self::newFromRow( $row );
1229 if ( !$block->isExpired() ) {
1230 $blocks[] = $block;
1231 }
1232 }
1233
1234 return $blocks;
1235 }
1236
1258 public static function chooseBlock( array $blocks, array $ipChain ) {
1259 if ( $blocks === [] ) {
1260 return null;
1261 } elseif ( count( $blocks ) == 1 ) {
1262 return $blocks[0];
1263 }
1264
1265 // Sort hard blocks before soft ones and secondarily sort blocks
1266 // that disable account creation before those that don't.
1267 usort( $blocks, function ( DatabaseBlock $a, DatabaseBlock $b ) {
1268 $aWeight = (int)$a->isHardblock() . (int)$a->appliesToRight( 'createaccount' );
1269 $bWeight = (int)$b->isHardblock() . (int)$b->appliesToRight( 'createaccount' );
1270 return strcmp( $bWeight, $aWeight ); // highest weight first
1271 } );
1272
1273 $blocksListExact = [
1274 'hard' => false,
1275 'disable_create' => false,
1276 'other' => false,
1277 'auto' => false
1278 ];
1279 $blocksListRange = [
1280 'hard' => false,
1281 'disable_create' => false,
1282 'other' => false,
1283 'auto' => false
1284 ];
1285 $ipChain = array_reverse( $ipChain );
1286
1287 foreach ( $blocks as $block ) {
1288 // Stop searching if we have already have a "better" block. This
1289 // is why the order of the blocks matters
1290 if ( !$block->isHardblock() && $blocksListExact['hard'] ) {
1291 break;
1292 } elseif ( !$block->appliesToRight( 'createaccount' ) && $blocksListExact['disable_create'] ) {
1293 break;
1294 }
1295
1296 foreach ( $ipChain as $checkip ) {
1297 $checkipHex = IP::toHex( $checkip );
1298 if ( (string)$block->getTarget() === $checkip ) {
1299 if ( $block->isHardblock() ) {
1300 $blocksListExact['hard'] = $blocksListExact['hard'] ?: $block;
1301 } elseif ( $block->appliesToRight( 'createaccount' ) ) {
1302 $blocksListExact['disable_create'] = $blocksListExact['disable_create'] ?: $block;
1303 } elseif ( $block->mAuto ) {
1304 $blocksListExact['auto'] = $blocksListExact['auto'] ?: $block;
1305 } else {
1306 $blocksListExact['other'] = $blocksListExact['other'] ?: $block;
1307 }
1308 // We found closest exact match in the ip list, so go to the next block
1309 break;
1310 } elseif ( array_filter( $blocksListExact ) == []
1311 && $block->getRangeStart() <= $checkipHex
1312 && $block->getRangeEnd() >= $checkipHex
1313 ) {
1314 if ( $block->isHardblock() ) {
1315 $blocksListRange['hard'] = $blocksListRange['hard'] ?: $block;
1316 } elseif ( $block->appliesToRight( 'createaccount' ) ) {
1317 $blocksListRange['disable_create'] = $blocksListRange['disable_create'] ?: $block;
1318 } elseif ( $block->mAuto ) {
1319 $blocksListRange['auto'] = $blocksListRange['auto'] ?: $block;
1320 } else {
1321 $blocksListRange['other'] = $blocksListRange['other'] ?: $block;
1322 }
1323 break;
1324 }
1325 }
1326 }
1327
1328 if ( array_filter( $blocksListExact ) == [] ) {
1329 $blocksList = &$blocksListRange;
1330 } else {
1331 $blocksList = &$blocksListExact;
1332 }
1333
1334 $chosenBlock = null;
1335 if ( $blocksList['hard'] ) {
1336 $chosenBlock = $blocksList['hard'];
1337 } elseif ( $blocksList['disable_create'] ) {
1338 $chosenBlock = $blocksList['disable_create'];
1339 } elseif ( $blocksList['other'] ) {
1340 $chosenBlock = $blocksList['other'];
1341 } elseif ( $blocksList['auto'] ) {
1342 $chosenBlock = $blocksList['auto'];
1343 } else {
1344 throw new MWException( "Proxy block found, but couldn't be classified." );
1345 }
1346
1347 return $chosenBlock;
1348 }
1349
1356 public function getType() {
1357 return $this->mAuto
1359 : parent::getType();
1360 }
1361
1370 public function setCookie( WebResponse $response ) {
1371 MediaWikiServices::getInstance()->getBlockManager()->setBlockCookie( $this, $response );
1372 }
1373
1381 public static function clearCookie( WebResponse $response ) {
1382 MediaWikiServices::getInstance()->getBlockManager()->clearBlockCookie( $response );
1383 }
1384
1395 public function getCookieValue() {
1396 return MediaWikiServices::getInstance()->getBlockManager()->getCookieValue( $this );
1397 }
1398
1408 public static function getIdFromCookieValue( $cookieValue ) {
1409 return MediaWikiServices::getInstance()->getBlockManager()->getIdFromCookieValue( $cookieValue );
1410 }
1411
1418 $params = $this->getBlockErrorParams( $context );
1419
1420 $msg = 'blockedtext';
1421 if ( $this->mAuto ) {
1422 $msg = 'autoblockedtext';
1423 } elseif ( !$this->isSitewide() ) {
1424 $msg = 'blockedtext-partial';
1425 }
1426
1427 array_unshift( $params, $msg );
1428
1429 return $params;
1430 }
1431
1441 public function getRestrictions() {
1442 if ( $this->restrictions === null ) {
1443 // If the block id has not been set, then do not attempt to load the
1444 // restrictions.
1445 if ( !$this->mId ) {
1446 return [];
1447 }
1448 $this->restrictions = $this->getBlockRestrictionStore()->loadByBlockId( $this->mId );
1449 }
1450
1451 return $this->restrictions;
1452 }
1453
1461 public function setRestrictions( array $restrictions ) {
1462 $this->restrictions = array_filter( $restrictions, function ( $restriction ) {
1463 return $restriction instanceof Restriction;
1464 } );
1465
1466 return $this;
1467 }
1468
1472 public function appliesToTitle( Title $title ) {
1473 if ( $this->isSitewide() ) {
1474 return true;
1475 }
1476
1477 $restrictions = $this->getRestrictions();
1478 foreach ( $restrictions as $restriction ) {
1479 if ( $restriction->matches( $title ) ) {
1480 return true;
1481 }
1482 }
1483
1484 return false;
1485 }
1486
1490 public function appliesToNamespace( $ns ) {
1491 if ( $this->isSitewide() ) {
1492 return true;
1493 }
1494
1495 // Blocks do not apply to virtual namespaces.
1496 if ( $ns < 0 ) {
1497 return false;
1498 }
1499
1500 $restriction = $this->findRestriction( NamespaceRestriction::TYPE, $ns );
1501
1502 return (bool)$restriction;
1503 }
1504
1508 public function appliesToPage( $pageId ) {
1509 if ( $this->isSitewide() ) {
1510 return true;
1511 }
1512
1513 // If the pageId is not over zero, the block cannot apply to it.
1514 if ( $pageId <= 0 ) {
1515 return false;
1516 }
1517
1518 $restriction = $this->findRestriction( PageRestriction::TYPE, $pageId );
1519
1520 return (bool)$restriction;
1521 }
1522
1530 private function findRestriction( $type, $value ) {
1531 $restrictions = $this->getRestrictions();
1532 foreach ( $restrictions as $restriction ) {
1533 if ( $restriction->getType() !== $type ) {
1534 continue;
1535 }
1536
1537 if ( $restriction->getValue() === $value ) {
1538 return $restriction;
1539 }
1540 }
1541
1542 return null;
1543 }
1544
1550 public function shouldTrackWithCookie( $isAnon ) {
1551 wfDeprecated( __METHOD__, '1.34' );
1552 $config = RequestContext::getMain()->getConfig();
1553 switch ( $this->getType() ) {
1554 case self::TYPE_IP:
1555 case self::TYPE_RANGE:
1556 return $isAnon && $config->get( 'CookieSetOnIpBlock' );
1557 case self::TYPE_USER:
1558 return !$isAnon && $config->get( 'CookieSetOnAutoblock' ) && $this->isAutoblocking();
1559 default:
1560 return false;
1561 }
1562 }
1563
1572}
1573
1577class_alias( DatabaseBlock::class, 'Block' );
$wgAutoblockExpiry
Number of seconds before autoblock entries expire.
$wgBlockCIDRLimit
Limits on the possible sizes of range blocks.
$wgPutIPinRC
Log IP addresses in the recentchanges table; can be accessed only by extensions (e....
$wgBlockDisablesLogin
If true, blocked users will not be allowed to login.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfReadOnly()
Check whether the wiki is in read-only mode.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
$line
Definition cdb.php:59
This class handles the logic for the actor table migration.
static newMigration()
Static constructor.
Deferrable Update for closure/callback updates that should use auto-commit mode.
CommentStore handles storage of comments (edit summaries, log reasons, etc) in the database.
static getStore()
Class for managing the deferred updates.
Hooks class.
Definition Hooks.php:34
This class is a collection of static functions that serve two purposes:
Definition Html.php:49
A collection of public static functions to play with IP address and IP ranges.
Definition IP.php:67
MediaWiki exception.
isUsertalkEditAllowed( $x=null)
Get or set the flag indicating whether this block blocks the target from editing their own user talk ...
getTarget()
Get the target for this particular block.
static parseTarget( $target)
From an existing block, get the target and the type of target.
setTarget( $target)
Set the target for this block, and update $this->type accordingly.
getHideName()
Get whether the block hides the target's username.
getBlocker()
Get the user who implemented this block.
setHideName( $hideName)
Set whether ths block hides the target's username.
setTimestamp( $timestamp)
Set the timestamp indicating when the block was created.
appliesToRight( $right)
Determine whether the block prevents a given right.
isCreateAccountBlocked( $x=null)
Get or set the flag indicating whether this block blocks the target from creating an account.
getTimestamp()
Get the timestamp indicating when the block was created.
getReason()
Get the reason given for creating the block.
int $type
AbstractBlock::TYPE_ constant.
getBlockErrorParams(IContextSource $context)
Get block information used in different block error messages.
isEmailBlocked( $x=null)
Get or set the flag indicating whether this block blocks the target from sending emails.
isSitewide( $x=null)
Indicates that the block is a sitewide block.
getExpiry()
Get the block expiry time.
setReason( $reason)
Set the reason for creating the block.
setBlocker( $user)
Set the user who implemented (or will implement) this block.
setExpiry( $expiry)
Set the block expiry time.
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
initFromRow( $row)
Given a database row from the ipblocks table, initialize member variables.
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new block object.
static getBlocksForIPList(array $ipChain, $isAnon, $fromMaster=false)
Get all blocks that match any IP from an array of IP addresses.
static newFromTarget( $specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target's type, get an existing block object if possible.
int $forcedTargetID
Hack for foreign blocking (CentralAuth)
static newListFromTarget( $specificTarget, $vagueTarget=null, $fromMaster=false)
This is similar to DatabaseBlock::newFromTarget, but it returns all the relevant blocks.
deleteIfExpired()
Check if a block has expired.
equals(DatabaseBlock $block)
Check if two blocks are effectively equal.
updateTimestamp()
Update the timestamp on autoblocks.
static clearCookie(WebResponse $response)
Unset the 'BlockID' cookie.
static purgeExpired()
Purge expired blocks from the ipblocks table.
insert(IDatabase $dbw=null)
Insert a block into the block table.
doRetroactiveAutoblock()
Retroactively autoblocks the last IP used by the user (if it is a user) blocked by this block.
setRestrictions(array $restrictions)
Set Restrictions.
getRangeStart()
Get the IP address at the start of the range in Hex form.
setId( $blockId)
Set the block ID.
update()
Update a block in the DB with new parameters.
static newFromRow( $row)
Create a new DatabaseBlock object from a database row.
__construct(array $options=[])
Create a new block with specified option parameters on a user, IP or IP range.
static getRangeCond( $start, $end=null)
Get a set of SQL conditions which will select rangeblocks encompassing a given range.
getId()
Get the block ID.int|null
static newLoad( $specificTarget, $specificType, $fromMaster, $vagueTarget=null)
Load blocks from the database which target the specific target exactly, or which cover the vague targ...
static getIdFromCookieValue( $cookieValue)
Get the stored ID from the 'BlockID' cookie.
fromMaster( $x=null)
Get/set a flag determining whether the master is used for reads.
isHardblock( $x=null)
Get/set whether the block is a hardblock (affects logged-in users on a given IP/range)
getPermissionsError(IContextSource $context)
Get the key and parameters for the corresponding error message.1.22 array
static chooseBlock(array $blocks, array $ipChain)
From a list of multiple blocks, find the most exact and strongest block.
static isWhitelistedFromAutoblocks( $ip)
Checks whether a given IP is on the autoblock whitelist.
findRestriction( $type, $value)
Find Restriction by type and value.
getRangeEnd()
Get the IP address at the end of the range in Hex form.
getCookieValue()
Get the BlockID cookie's value for this block.
isValid()
Is the block address valid (i.e.
getDatabaseArray(IDatabase $dbw)
Get an array suitable for passing to $dbw->insert() or $dbw->update()
appliesToPage( $pageId)
Checks if a block applies to a particular page.This check does not consider whether $this->isUsertalk...
static getAutoblockExpiry( $timestamp)
Get a timestamp of the expiry for autoblocks.
isExpired()
Has the block expired?
static newFromID( $id)
Load a block from the block id.
getAutoblockUpdateArray(IDatabase $dbw)
static getIpFragment( $hex)
Get the component of an IP address which is certain to be the same between an IP address and a rangeb...
setCookie(WebResponse $response)
Set the 'BlockID' cookie to this block's ID and expiry time.
getRestrictions()
Get Restrictions.
appliesToNamespace( $ns)
Checks if a block applies to a particular namespace.1.33bool
doAutoblock( $autoblockIP)
Autoblocks the given IP, referring to this block.
getRedactedName()
Get the block name, but with autoblocked IPs hidden as per standard privacy policy.
getBlockRestrictionStore()
Get a BlockRestrictionStore instance.
static chooseMostSpecificBlock(array $blocks)
Choose the most specific block from some combination of user, IP and IP range blocks.
static defaultRetroactiveAutoblock(DatabaseBlock $block, array &$blockIds)
Retroactively autoblocks the last IP used by the user (if it is a user) blocked by this block.
getType()
Get the type of target for this particular block.int AbstractBlock::TYPE_ constant,...
appliesToTitle(Title $title)
Checks if a block applies to a particular title.This check does not consider whether $this->isUsertal...
MediaWikiServices is the service locator for the application scope of MediaWiki.
static getInstance()
Returns the global default instance of the top level service locator.
Group all the pieces relevant to the context of a request into one instance.
Represents a title within MediaWiki.
Definition Title.php:42
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:51
getId()
Get the user's ID.
Definition User.php:2335
static newFromAnyId( $userId, $userName, $actorId, $dbDomain=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition User.php:599
Allow programs to request this object from WebRequest::response() and handle all outputting (or lack ...
Relational database abstraction object.
Definition Database.php:49
return[ 'OATHAuth'=> function(MediaWikiServices $services) { return new OATHAuth($services->getMainConfig(), $services->getDBLoadBalancerFactory());}, 'OATHUserRepository'=> function(MediaWikiServices $services) { global $wgOATHAuthDatabase;$auth=$services->getService( 'OATHAuth');return new OATHUserRepository($services->getDBLoadBalancerFactory() ->getMainLB( $wgOATHAuthDatabase), new \HashBagOStuff(['maxKey'=> 5]), $auth);}]
const LIST_OR
Definition Defines.php:51
const LIST_AND
Definition Defines.php:48
Interface for objects which can provide a MediaWiki context on request.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
delete( $table, $conds, $fname=__METHOD__)
DELETE query wrapper.
addQuotes( $s)
Escape and quote a raw value string for use in a SQL query.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
encodeExpiry( $expiry)
Encode an expiry time into the DBMS dependent format.
selectFieldValues( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a list of single field values from result rows.
$context
Definition load.php:45
$cache
Definition mcc.php:33
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:26
$lines
Definition router.php:61
return true
Definition router.php:94