Linux anchor 4.4.0-210-generic #242-Ubuntu SMP Fri Apr 16 09:57:56 UTC 2021 x86_64
Apache/2.4.18 (Ubuntu)
Server IP : 10.10.100.4 & Your IP : 216.73.217.150
Domains :
Cant Read [ /etc/named.conf ]
User : www-data
Terminal
Auto Root
Create File
Create Folder
Localroot Suggester
Backdoor Destroyer
Readme
/
var /
www /
wiki.bernardino.org /
includes /
Delete
Unzip
Name
Size
Permission
Date
Action
actions
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
api
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
auth
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
cache
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
changes
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
changetags
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
clientpool
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
collation
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
compat
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
composer
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
config
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
content
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
context
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
dao
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
db
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
debug
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
deferred
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
diff
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
exception
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
export
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
extauth
[ DIR ]
drwxr-xr-x
2012-03-22 16:23
externalstore
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
filebackend
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
filerepo
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
gallery
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
htmlform
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
http
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
import
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
installer
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
interwiki
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
job
[ DIR ]
drwxr-xr-x
2012-03-22 16:23
jobqueue
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
json
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
libs
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
linker
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
logging
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
mail
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
media
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
normal
[ DIR ]
drwxr-xr-x
2012-03-22 16:23
objectcache
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
page
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
pager
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
parser
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
password
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
poolcounter
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
profiler
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
rcfeed
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
registration
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
resourceloader
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
revisiondelete
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
search
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
services
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
session
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
site
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
skins
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
specialpage
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
specials
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
templates
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
tidy
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
title
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
upload
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
user
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
utils
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
widget
[ DIR ]
drwxr-xr-x
2016-11-28 20:20
zhtable
[ DIR ]
drwxr-xr-x
2012-03-22 16:23
.htaccess
197
B
-r--r--r--
2026-08-22 08:29
Action.php
12.43
KB
-rw-r--r--
2011-09-16 17:01
AjaxDispatcher.php
4.03
KB
-rw-r--r--
2016-11-28 20:20
AjaxResponse.php
8.15
KB
-rw-r--r--
2016-11-28 20:20
Article.php
58.64
KB
-rw-r--r--
2011-11-15 20:46
AuthPlugin.php
9.2
KB
-rw-r--r--
2016-11-28 20:20
AutoLoader.php
3
KB
-rw-r--r--
2016-11-28 20:20
Autopromote.php
6.77
KB
-rw-r--r--
2016-11-28 20:20
BacklinkCache.php
9.03
KB
-rw-r--r--
2011-06-30 01:09
Block.php
41.97
KB
-rw-r--r--
2016-11-28 20:20
Category.php
10.34
KB
-rw-r--r--
2016-11-28 20:20
CategoryFinder.php
6.2
KB
-rw-r--r--
2016-11-28 20:20
CategoryPage.php
21.84
KB
-rw-r--r--
2011-10-28 23:20
CategoryViewer.php
22.5
KB
-rw-r--r--
2016-11-28 20:20
Categoryfinder.php
5.13
KB
-rw-r--r--
2011-05-19 02:03
Cdb.php
3.57
KB
-rw-r--r--
2011-05-28 20:58
Cdb_PHP.php
10.11
KB
-rw-r--r--
2011-05-28 19:51
ChangeTags.php
6.66
KB
-rw-r--r--
2011-07-09 11:01
ChangesFeed.php
5.82
KB
-rw-r--r--
2011-06-19 07:45
ChangesList.php
35.52
KB
-rw-r--r--
2011-10-25 23:51
Collation.php
9.86
KB
-rw-r--r--
2011-09-21 00:36
ConfEditor.php
29.16
KB
-rw-r--r--
2011-08-27 21:28
Cookie.php
6.17
KB
-rw-r--r--
2011-06-02 00:35
CryptRand.php
16.96
KB
-rw-r--r--
2012-03-21 11:43
DefaultSettings.php
265.47
KB
-rw-r--r--
2016-11-28 20:20
Defines.php
7.85
KB
-rw-r--r--
2016-11-28 20:20
DeprecatedGlobal.php
1.9
KB
-rw-r--r--
2016-11-28 20:20
DerivativeRequest.php
2.46
KB
-rw-r--r--
2016-11-28 20:20
DjVuImage.php
10.58
KB
-rw-r--r--
2011-05-18 00:03
DummyLinker.php
9.01
KB
-rw-r--r--
2016-11-28 20:20
EditPage.php
136.53
KB
-rw-r--r--
2016-11-28 20:20
EventRelayerGroup.php
1.27
KB
-rw-r--r--
2016-11-28 20:20
Exception.php
11.5
KB
-rw-r--r--
2011-10-22 04:34
Export.php
32.27
KB
-rw-r--r--
2011-11-24 02:29
ExternalEdit.php
2.45
KB
-rw-r--r--
2011-09-07 22:14
ExternalStore.php
4.29
KB
-rw-r--r--
2011-05-29 16:24
ExternalStoreDB.php
4.44
KB
-rw-r--r--
2011-10-17 00:24
ExternalStoreHttp.php
463
B
-rw-r--r--
2010-01-08 22:35
ExternalUser.php
8.96
KB
-rw-r--r--
2011-05-26 21:52
FakeTitle.php
6.6
KB
-rw-r--r--
2011-05-26 22:38
Fallback.php
5.49
KB
-rw-r--r--
2011-05-28 20:58
FauxRequest.php
5.66
KB
-rw-r--r--
2016-11-28 20:20
Feed.php
10.58
KB
-rw-r--r--
2016-11-28 20:20
FeedUtils.php
8.64
KB
-rw-r--r--
2016-11-28 20:20
FileDeleteForm.php
11.7
KB
-rw-r--r--
2016-11-28 20:20
ForkController.php
5.4
KB
-rw-r--r--
2016-11-28 20:20
FormOptions.php
11.08
KB
-rw-r--r--
2016-11-28 20:20
GenderCache.php
3.35
KB
-rw-r--r--
2011-09-19 14:58
GitInfo.php
10.74
KB
-rw-r--r--
2016-11-28 20:20
GlobalFunctions.php
106.76
KB
-rw-r--r--
2016-11-28 20:20
HTMLForm.php
49.55
KB
-rw-r--r--
2011-11-09 18:04
HistoryBlob.php
16.15
KB
-rw-r--r--
2016-11-28 20:20
HistoryPage.php
21.66
KB
-rw-r--r--
2011-09-16 16:12
Hooks.php
6.47
KB
-rw-r--r--
2016-11-28 20:20
Html.php
31.91
KB
-rw-r--r--
2016-11-28 20:20
HtmlFormatter.php
1015
B
-rw-r--r--
2016-11-28 20:20
HttpFunctions.old.php
319
B
-rw-r--r--
2011-05-28 17:59
HttpFunctions.php
21.28
KB
-rw-r--r--
2011-09-16 15:19
IP.php
21.58
KB
-rw-r--r--
2011-11-09 16:00
ImageFunctions.php
2.58
KB
-rw-r--r--
2011-05-29 16:24
ImageGallery.php
10.46
KB
-rw-r--r--
2011-07-16 01:18
ImagePage.php
36
KB
-rw-r--r--
2011-07-11 02:39
ImageQueryPage.php
1.5
KB
-rw-r--r--
2011-05-26 21:21
Import.php
33.63
KB
-rw-r--r--
2011-10-28 23:20
Init.php
4.67
KB
-rw-r--r--
2011-06-17 01:40
Licenses.php
4.89
KB
-rw-r--r--
2016-11-28 20:20
LinkFilter.php
6.08
KB
-rw-r--r--
2016-11-28 20:20
Linker.php
72.71
KB
-rw-r--r--
2016-11-28 20:20
LinksUpdate.php
23.7
KB
-rw-r--r--
2011-11-14 21:58
ListToggle.php
2.02
KB
-rw-r--r--
2016-11-28 20:20
LocalisationCache.php
29.31
KB
-rw-r--r--
2011-09-12 17:03
LogEventsList.php
32.56
KB
-rw-r--r--
2011-07-10 20:12
LogPage.php
16.09
KB
-rw-r--r--
2011-07-10 20:12
MWFunction.php
1.96
KB
-rw-r--r--
2011-03-22 21:23
MWGrants.php
5.85
KB
-rw-r--r--
2016-11-28 20:20
MWNamespace.php
13.76
KB
-rw-r--r--
2016-11-28 20:20
MWTimestamp.php
6.1
KB
-rw-r--r--
2016-11-28 20:20
MagicWord.php
15.1
KB
-rw-r--r--
2016-11-28 20:20
MagicWordArray.php
7.86
KB
-rw-r--r--
2016-11-28 20:20
MediaWiki.php
35.29
KB
-rw-r--r--
2016-11-28 20:20
MediaWikiServices.php
18.8
KB
-rw-r--r--
2016-11-28 20:20
MediaWikiVersionFetcher.php
746
B
-rw-r--r--
2016-11-28 20:20
MergeHistory.php
10.41
KB
-rw-r--r--
2016-11-28 20:20
Message.php
32.49
KB
-rw-r--r--
2016-11-28 20:20
MessageBlobStore.php
10.5
KB
-rw-r--r--
2011-05-28 00:42
Metadata.php
5.53
KB
-rw-r--r--
2011-06-30 00:09
MimeMagic.php
2.87
KB
-rw-r--r--
2016-11-28 20:20
MovePage.php
18.08
KB
-rw-r--r--
2016-11-28 20:20
Namespace.php
7.78
KB
-rw-r--r--
2011-05-21 21:07
NoLocalSettings.php
2.03
KB
-rw-r--r--
2016-11-28 20:20
OutputHandler.php
6.19
KB
-rw-r--r--
2016-11-28 20:20
OutputPage.php
113
KB
-rw-r--r--
2016-11-28 20:20
PHPVersionCheck.php
8.52
KB
-rw-r--r--
2016-11-28 20:20
PHPVersionError.php
2.96
KB
-rw-r--r--
2011-07-19 01:06
PageProps.php
8.52
KB
-rw-r--r--
2016-11-28 20:20
PageQueryPage.php
680
B
-rw-r--r--
2010-12-22 15:16
Pager.php
30.89
KB
-rw-r--r--
2011-07-06 04:26
PathRouter.php
12.43
KB
-rw-r--r--
2016-11-28 20:20
PatrolLog.php
2.53
KB
-rw-r--r--
2011-07-10 20:12
Pingback.php
7.31
KB
-rw-r--r--
2016-11-28 20:20
PoolCounter.php
5.65
KB
-rw-r--r--
2011-05-28 21:00
Preferences.php
51.21
KB
-rw-r--r--
2016-11-28 20:20
PrefixSearch.php
12.36
KB
-rw-r--r--
2016-11-28 20:20
ProtectionForm.php
19.56
KB
-rw-r--r--
2016-11-28 20:20
ProxyLookup.php
2.12
KB
-rw-r--r--
2016-11-28 20:20
ProxyTools.php
4.6
KB
-rw-r--r--
2011-09-07 22:14
QueryPage.php
22.76
KB
-rw-r--r--
2011-10-17 00:24
RawPage.php
6.2
KB
-rw-r--r--
2011-07-05 23:28
RecentChange.php
23.52
KB
-rw-r--r--
2011-08-27 21:28
RequestContext.php
7.85
KB
-rw-r--r--
2012-01-04 02:07
Revision.php
54.61
KB
-rw-r--r--
2016-11-28 20:20
RevisionList.php
9.25
KB
-rw-r--r--
2016-11-28 20:20
Sanitizer.php
58.04
KB
-rw-r--r--
2016-11-28 20:20
SeleniumWebSettings.php
6.3
KB
-rw-r--r--
2011-06-26 01:27
ServiceWiring.php
11.72
KB
-rw-r--r--
2016-11-28 20:20
Setup.php
27.89
KB
-rw-r--r--
2016-11-28 20:20
SiteConfiguration.php
18.34
KB
-rw-r--r--
2016-11-28 20:20
SiteStats.php
9.85
KB
-rw-r--r--
2016-11-28 20:20
Skin.php
42.13
KB
-rw-r--r--
2012-02-25 16:23
SkinLegacy.php
22.82
KB
-rw-r--r--
2011-10-20 22:50
SkinTemplate.php
64.93
KB
-rw-r--r--
2011-12-09 21:31
SpecialPage.php
31.23
KB
-rw-r--r--
2012-01-04 02:07
SpecialPageFactory.php
17.68
KB
-rw-r--r--
2011-10-28 23:20
SquidPurgeClient.php
10.02
KB
-rw-r--r--
2011-05-21 21:54
Status.php
12.63
KB
-rw-r--r--
2016-11-28 20:20
StreamFile.php
4.66
KB
-rw-r--r--
2016-11-28 20:20
StringUtils.php
11.32
KB
-rw-r--r--
2011-05-21 21:54
StubObject.php
6.4
KB
-rw-r--r--
2016-11-28 20:20
TemplateParser.php
6.46
KB
-rw-r--r--
2016-11-28 20:20
TemplatesOnThisPageFormatter.php
5.36
KB
-rw-r--r--
2016-11-28 20:20
Title.php
140.81
KB
-rw-r--r--
2016-11-28 20:20
TitleArray.php
1.83
KB
-rw-r--r--
2016-11-28 20:20
TitleArrayFromResult.php
1.95
KB
-rw-r--r--
2016-11-28 20:20
User.php
114.26
KB
-rw-r--r--
2012-03-21 11:43
UserArray.php
1.9
KB
-rw-r--r--
2011-05-28 17:59
UserMailer.php
20.52
KB
-rw-r--r--
2012-02-02 15:15
UserRightsProxy.php
5.38
KB
-rw-r--r--
2010-10-14 01:11
ViewCountUpdate.php
3.4
KB
-rw-r--r--
2011-06-25 21:13
WatchedItem.php
5.04
KB
-rw-r--r--
2016-11-28 20:20
WatchedItemQueryService.php
20.4
KB
-rw-r--r--
2016-11-28 20:20
WatchedItemStore.php
25.57
KB
-rw-r--r--
2016-11-28 20:20
WebRequest.php
37.68
KB
-rw-r--r--
2016-11-28 20:20
WebRequestUpload.php
3.4
KB
-rw-r--r--
2016-11-28 20:20
WebResponse.php
9.2
KB
-rw-r--r--
2016-11-28 20:20
WebStart.php
5.54
KB
-rw-r--r--
2016-11-28 20:20
Wiki.php
20.05
KB
-rw-r--r--
2012-01-03 18:34
WikiCategoryPage.php
898
B
-rw-r--r--
2011-08-27 21:28
WikiError.php
3.84
KB
-rw-r--r--
2011-05-06 23:09
WikiFilePage.php
3.34
KB
-rw-r--r--
2011-07-01 22:07
WikiMap.php
8.19
KB
-rw-r--r--
2016-11-28 20:20
WikiPage.php
78.59
KB
-rw-r--r--
2012-01-12 23:09
Xml.php
26.47
KB
-rw-r--r--
2016-11-28 20:20
XmlSelect.php
3.46
KB
-rw-r--r--
2016-11-28 20:20
XmlTypeCheck.php
2.45
KB
-rw-r--r--
2011-05-21 21:36
ZhClient.php
2.92
KB
-rw-r--r--
2011-05-29 16:25
ZhConversion.php
415
KB
-rw-r--r--
2011-10-27 23:40
ZipDirectoryReader.php
20.35
KB
-rw-r--r--
2011-05-13 18:16
limit.sh
2.86
KB
-rwxr-xr-x
2016-11-28 20:20
mime.info
3.82
KB
-rw-r--r--
2011-06-29 22:31
mime.types
5.18
KB
-rw-r--r--
2011-06-29 22:31
tidy.conf
413
B
-rw-r--r--
2009-10-25 14:43
wp-blog-header.php
2.74
KB
-r--r--r--
2026-08-22 08:29
wp-cron.php
2.74
KB
-rw-r--r--
2026-08-22 08:29
Save
Rename
<?php use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface; use MediaWiki\Linker\LinkTarget; use Wikimedia\Assert\Assert; use Wikimedia\ScopedCallback; /** * Storage layer class for WatchedItems. * Database interaction. * * @author Addshore * * @since 1.27 */ class WatchedItemStore implements StatsdAwareInterface { const SORT_DESC = 'DESC'; const SORT_ASC = 'ASC'; /** * @var LoadBalancer */ private $loadBalancer; /** * @var HashBagOStuff */ private $cache; /** * @var array[] Looks like $cacheIndex[Namespace ID][Target DB Key][User Id] => 'key' * The index is needed so that on mass changes all relevant items can be un-cached. * For example: Clearing a users watchlist of all items or updating notification timestamps * for all users watching a single target. */ private $cacheIndex = []; /** * @var callable|null */ private $deferredUpdatesAddCallableUpdateCallback; /** * @var callable|null */ private $revisionGetTimestampFromIdCallback; /** * @var StatsdDataFactoryInterface */ private $stats; /** * @param LoadBalancer $loadBalancer * @param HashBagOStuff $cache */ public function __construct( LoadBalancer $loadBalancer, HashBagOStuff $cache ) { $this->loadBalancer = $loadBalancer; $this->cache = $cache; $this->stats = new NullStatsdDataFactory(); $this->deferredUpdatesAddCallableUpdateCallback = [ 'DeferredUpdates', 'addCallableUpdate' ]; $this->revisionGetTimestampFromIdCallback = [ 'Revision', 'getTimestampFromId' ]; } public function setStatsdDataFactory( StatsdDataFactoryInterface $stats ) { $this->stats = $stats; } /** * Overrides the DeferredUpdates::addCallableUpdate callback * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined. * * @param callable $callback * * @see DeferredUpdates::addCallableUpdate for callback signiture * * @return ScopedCallback to reset the overridden value * @throws MWException */ public function overrideDeferredUpdatesAddCallableUpdateCallback( callable $callback ) { if ( !defined( 'MW_PHPUNIT_TEST' ) ) { throw new MWException( 'Cannot override DeferredUpdates::addCallableUpdate callback in operation.' ); } $previousValue = $this->deferredUpdatesAddCallableUpdateCallback; $this->deferredUpdatesAddCallableUpdateCallback = $callback; return new ScopedCallback( function() use ( $previousValue ) { $this->deferredUpdatesAddCallableUpdateCallback = $previousValue; } ); } /** * Overrides the Revision::getTimestampFromId callback * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined. * * @param callable $callback * @see Revision::getTimestampFromId for callback signiture * * @return ScopedCallback to reset the overridden value * @throws MWException */ public function overrideRevisionGetTimestampFromIdCallback( callable $callback ) { if ( !defined( 'MW_PHPUNIT_TEST' ) ) { throw new MWException( 'Cannot override Revision::getTimestampFromId callback in operation.' ); } $previousValue = $this->revisionGetTimestampFromIdCallback; $this->revisionGetTimestampFromIdCallback = $callback; return new ScopedCallback( function() use ( $previousValue ) { $this->revisionGetTimestampFromIdCallback = $previousValue; } ); } private function getCacheKey( User $user, LinkTarget $target ) { return $this->cache->makeKey( (string)$target->getNamespace(), $target->getDBkey(), (string)$user->getId() ); } private function cache( WatchedItem $item ) { $user = $item->getUser(); $target = $item->getLinkTarget(); $key = $this->getCacheKey( $user, $target ); $this->cache->set( $key, $item ); $this->cacheIndex[$target->getNamespace()][$target->getDBkey()][$user->getId()] = $key; $this->stats->increment( 'WatchedItemStore.cache' ); } private function uncache( User $user, LinkTarget $target ) { $this->cache->delete( $this->getCacheKey( $user, $target ) ); unset( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()][$user->getId()] ); $this->stats->increment( 'WatchedItemStore.uncache' ); } private function uncacheLinkTarget( LinkTarget $target ) { $this->stats->increment( 'WatchedItemStore.uncacheLinkTarget' ); if ( !isset( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()] ) ) { return; } foreach ( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()] as $key ) { $this->stats->increment( 'WatchedItemStore.uncacheLinkTarget.items' ); $this->cache->delete( $key ); } } private function uncacheUser( User $user ) { $this->stats->increment( 'WatchedItemStore.uncacheUser' ); foreach ( $this->cacheIndex as $ns => $dbKeyArray ) { foreach ( $dbKeyArray as $dbKey => $userArray ) { if ( isset( $userArray[$user->getId()] ) ) { $this->stats->increment( 'WatchedItemStore.uncacheUser.items' ); $this->cache->delete( $userArray[$user->getId()] ); } } } } /** * @param User $user * @param LinkTarget $target * * @return WatchedItem|null */ private function getCached( User $user, LinkTarget $target ) { return $this->cache->get( $this->getCacheKey( $user, $target ) ); } /** * Return an array of conditions to select or update the appropriate database * row. * * @param User $user * @param LinkTarget $target * * @return array */ private function dbCond( User $user, LinkTarget $target ) { return [ 'wl_user' => $user->getId(), 'wl_namespace' => $target->getNamespace(), 'wl_title' => $target->getDBkey(), ]; } /** * @param int $dbIndex DB_MASTER or DB_REPLICA * * @return IDatabase * @throws MWException */ private function getConnectionRef( $dbIndex ) { return $this->loadBalancer->getConnectionRef( $dbIndex, [ 'watchlist' ] ); } /** * Count the number of individual items that are watched by the user. * If a subject and corresponding talk page are watched this will return 2. * * @param User $user * * @return int */ public function countWatchedItems( User $user ) { $dbr = $this->getConnectionRef( DB_REPLICA ); $return = (int)$dbr->selectField( 'watchlist', 'COUNT(*)', [ 'wl_user' => $user->getId() ], __METHOD__ ); return $return; } /** * @param LinkTarget $target * * @return int */ public function countWatchers( LinkTarget $target ) { $dbr = $this->getConnectionRef( DB_REPLICA ); $return = (int)$dbr->selectField( 'watchlist', 'COUNT(*)', [ 'wl_namespace' => $target->getNamespace(), 'wl_title' => $target->getDBkey(), ], __METHOD__ ); return $return; } /** * Number of page watchers who also visited a "recent" edit * * @param LinkTarget $target * @param mixed $threshold timestamp accepted by wfTimestamp * * @return int * @throws DBUnexpectedError * @throws MWException */ public function countVisitingWatchers( LinkTarget $target, $threshold ) { $dbr = $this->getConnectionRef( DB_REPLICA ); $visitingWatchers = (int)$dbr->selectField( 'watchlist', 'COUNT(*)', [ 'wl_namespace' => $target->getNamespace(), 'wl_title' => $target->getDBkey(), 'wl_notificationtimestamp >= ' . $dbr->addQuotes( $dbr->timestamp( $threshold ) ) . ' OR wl_notificationtimestamp IS NULL' ], __METHOD__ ); return $visitingWatchers; } /** * @param LinkTarget[] $targets * @param array $options Allowed keys: * 'minimumWatchers' => int * * @return array multi dimensional like $return[$namespaceId][$titleString] = int $watchers * All targets will be present in the result. 0 either means no watchers or the number * of watchers was below the minimumWatchers option if passed. */ public function countWatchersMultiple( array $targets, array $options = [] ) { $dbOptions = [ 'GROUP BY' => [ 'wl_namespace', 'wl_title' ] ]; $dbr = $this->getConnectionRef( DB_REPLICA ); if ( array_key_exists( 'minimumWatchers', $options ) ) { $dbOptions['HAVING'] = 'COUNT(*) >= ' . (int)$options['minimumWatchers']; } $lb = new LinkBatch( $targets ); $res = $dbr->select( 'watchlist', [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ], [ $lb->constructSet( 'wl', $dbr ) ], __METHOD__, $dbOptions ); $watchCounts = []; foreach ( $targets as $linkTarget ) { $watchCounts[$linkTarget->getNamespace()][$linkTarget->getDBkey()] = 0; } foreach ( $res as $row ) { $watchCounts[$row->wl_namespace][$row->wl_title] = (int)$row->watchers; } return $watchCounts; } /** * Number of watchers of each page who have visited recent edits to that page * * @param array $targetsWithVisitThresholds array of pairs (LinkTarget $target, mixed $threshold), * $threshold is: * - a timestamp of the recent edit if $target exists (format accepted by wfTimestamp) * - null if $target doesn't exist * @param int|null $minimumWatchers * @return array multi-dimensional like $return[$namespaceId][$titleString] = $watchers, * where $watchers is an int: * - if the page exists, number of users watching who have visited the page recently * - if the page doesn't exist, number of users that have the page on their watchlist * - 0 means there are no visiting watchers or their number is below the minimumWatchers * option (if passed). */ public function countVisitingWatchersMultiple( array $targetsWithVisitThresholds, $minimumWatchers = null ) { $dbr = $this->getConnectionRef( DB_REPLICA ); $conds = $this->getVisitingWatchersCondition( $dbr, $targetsWithVisitThresholds ); $dbOptions = [ 'GROUP BY' => [ 'wl_namespace', 'wl_title' ] ]; if ( $minimumWatchers !== null ) { $dbOptions['HAVING'] = 'COUNT(*) >= ' . (int)$minimumWatchers; } $res = $dbr->select( 'watchlist', [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ], $conds, __METHOD__, $dbOptions ); $watcherCounts = []; foreach ( $targetsWithVisitThresholds as list( $target ) ) { /* @var LinkTarget $target */ $watcherCounts[$target->getNamespace()][$target->getDBkey()] = 0; } foreach ( $res as $row ) { $watcherCounts[$row->wl_namespace][$row->wl_title] = (int)$row->watchers; } return $watcherCounts; } /** * Generates condition for the query used in a batch count visiting watchers. * * @param IDatabase $db * @param array $targetsWithVisitThresholds array of pairs (LinkTarget, last visit threshold) * @return string */ private function getVisitingWatchersCondition( IDatabase $db, array $targetsWithVisitThresholds ) { $missingTargets = []; $namespaceConds = []; foreach ( $targetsWithVisitThresholds as list( $target, $threshold ) ) { if ( $threshold === null ) { $missingTargets[] = $target; continue; } /* @var LinkTarget $target */ $namespaceConds[$target->getNamespace()][] = $db->makeList( [ 'wl_title = ' . $db->addQuotes( $target->getDBkey() ), $db->makeList( [ 'wl_notificationtimestamp >= ' . $db->addQuotes( $db->timestamp( $threshold ) ), 'wl_notificationtimestamp IS NULL' ], LIST_OR ) ], LIST_AND ); } $conds = []; foreach ( $namespaceConds as $namespace => $pageConds ) { $conds[] = $db->makeList( [ 'wl_namespace = ' . $namespace, '(' . $db->makeList( $pageConds, LIST_OR ) . ')' ], LIST_AND ); } if ( $missingTargets ) { $lb = new LinkBatch( $missingTargets ); $conds[] = $lb->constructSet( 'wl', $db ); } return $db->makeList( $conds, LIST_OR ); } /** * Get an item (may be cached) * * @param User $user * @param LinkTarget $target * * @return WatchedItem|false */ public function getWatchedItem( User $user, LinkTarget $target ) { if ( $user->isAnon() ) { return false; } $cached = $this->getCached( $user, $target ); if ( $cached ) { $this->stats->increment( 'WatchedItemStore.getWatchedItem.cached' ); return $cached; } $this->stats->increment( 'WatchedItemStore.getWatchedItem.load' ); return $this->loadWatchedItem( $user, $target ); } /** * Loads an item from the db * * @param User $user * @param LinkTarget $target * * @return WatchedItem|false */ public function loadWatchedItem( User $user, LinkTarget $target ) { // Only loggedin user can have a watchlist if ( $user->isAnon() ) { return false; } $dbr = $this->getConnectionRef( DB_REPLICA ); $row = $dbr->selectRow( 'watchlist', 'wl_notificationtimestamp', $this->dbCond( $user, $target ), __METHOD__ ); if ( !$row ) { return false; } $item = new WatchedItem( $user, $target, $row->wl_notificationtimestamp ); $this->cache( $item ); return $item; } /** * @param User $user * @param array $options Allowed keys: * 'forWrite' => bool defaults to false * 'sort' => string optional sorting by namespace ID and title * one of the self::SORT_* constants * * @return WatchedItem[] */ public function getWatchedItemsForUser( User $user, array $options = [] ) { $options += [ 'forWrite' => false ]; $dbOptions = []; if ( array_key_exists( 'sort', $options ) ) { Assert::parameter( ( in_array( $options['sort'], [ self::SORT_ASC, self::SORT_DESC ] ) ), '$options[\'sort\']', 'must be SORT_ASC or SORT_DESC' ); $dbOptions['ORDER BY'] = [ "wl_namespace {$options['sort']}", "wl_title {$options['sort']}" ]; } $db = $this->getConnectionRef( $options['forWrite'] ? DB_MASTER : DB_REPLICA ); $res = $db->select( 'watchlist', [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ], [ 'wl_user' => $user->getId() ], __METHOD__, $dbOptions ); $watchedItems = []; foreach ( $res as $row ) { // todo these could all be cached at some point? $watchedItems[] = new WatchedItem( $user, new TitleValue( (int)$row->wl_namespace, $row->wl_title ), $row->wl_notificationtimestamp ); } return $watchedItems; } /** * Must be called separately for Subject & Talk namespaces * * @param User $user * @param LinkTarget $target * * @return bool */ public function isWatched( User $user, LinkTarget $target ) { return (bool)$this->getWatchedItem( $user, $target ); } /** * @param User $user * @param LinkTarget[] $targets * * @return array multi-dimensional like $return[$namespaceId][$titleString] = $timestamp, * where $timestamp is: * - string|null value of wl_notificationtimestamp, * - false if $target is not watched by $user. */ public function getNotificationTimestampsBatch( User $user, array $targets ) { $timestamps = []; foreach ( $targets as $target ) { $timestamps[$target->getNamespace()][$target->getDBkey()] = false; } if ( $user->isAnon() ) { return $timestamps; } $targetsToLoad = []; foreach ( $targets as $target ) { $cachedItem = $this->getCached( $user, $target ); if ( $cachedItem ) { $timestamps[$target->getNamespace()][$target->getDBkey()] = $cachedItem->getNotificationTimestamp(); } else { $targetsToLoad[] = $target; } } if ( !$targetsToLoad ) { return $timestamps; } $dbr = $this->getConnectionRef( DB_REPLICA ); $lb = new LinkBatch( $targetsToLoad ); $res = $dbr->select( 'watchlist', [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ], [ $lb->constructSet( 'wl', $dbr ), 'wl_user' => $user->getId(), ], __METHOD__ ); foreach ( $res as $row ) { $timestamps[$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp; } return $timestamps; } /** * Must be called separately for Subject & Talk namespaces * * @param User $user * @param LinkTarget $target */ public function addWatch( User $user, LinkTarget $target ) { $this->addWatchBatchForUser( $user, [ $target ] ); } /** * @param User $user * @param LinkTarget[] $targets * * @return bool success */ public function addWatchBatchForUser( User $user, array $targets ) { if ( $this->loadBalancer->getReadOnlyReason() !== false ) { return false; } // Only loggedin user can have a watchlist if ( $user->isAnon() ) { return false; } if ( !$targets ) { return true; } $rows = []; foreach ( $targets as $target ) { $rows[] = [ 'wl_user' => $user->getId(), 'wl_namespace' => $target->getNamespace(), 'wl_title' => $target->getDBkey(), 'wl_notificationtimestamp' => null, ]; $this->uncache( $user, $target ); } $dbw = $this->getConnectionRef( DB_MASTER ); foreach ( array_chunk( $rows, 100 ) as $toInsert ) { // Use INSERT IGNORE to avoid overwriting the notification timestamp // if there's already an entry for this page $dbw->insert( 'watchlist', $toInsert, __METHOD__, 'IGNORE' ); } return true; } /** * Removes the an entry for the User watching the LinkTarget * Must be called separately for Subject & Talk namespaces * * @param User $user * @param LinkTarget $target * * @return bool success * @throws DBUnexpectedError * @throws MWException */ public function removeWatch( User $user, LinkTarget $target ) { // Only logged in user can have a watchlist if ( $this->loadBalancer->getReadOnlyReason() !== false || $user->isAnon() ) { return false; } $this->uncache( $user, $target ); $dbw = $this->getConnectionRef( DB_MASTER ); $dbw->delete( 'watchlist', [ 'wl_user' => $user->getId(), 'wl_namespace' => $target->getNamespace(), 'wl_title' => $target->getDBkey(), ], __METHOD__ ); $success = (bool)$dbw->affectedRows(); return $success; } /** * @param User $user The user to set the timestamp for * @param string $timestamp Set the update timestamp to this value * @param LinkTarget[] $targets List of targets to update. Default to all targets * * @return bool success */ public function setNotificationTimestampsForUser( User $user, $timestamp, array $targets = [] ) { // Only loggedin user can have a watchlist if ( $user->isAnon() ) { return false; } $dbw = $this->getConnectionRef( DB_MASTER ); $conds = [ 'wl_user' => $user->getId() ]; if ( $targets ) { $batch = new LinkBatch( $targets ); $conds[] = $batch->constructSet( 'wl', $dbw ); } $success = $dbw->update( 'watchlist', [ 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp ) ], $conds, __METHOD__ ); $this->uncacheUser( $user ); return $success; } /** * @param User $editor The editor that triggered the update. Their notification * timestamp will not be updated(they have already seen it) * @param LinkTarget $target The target to update timestamps for * @param string $timestamp Set the update timestamp to this value * * @return int[] Array of user IDs the timestamp has been updated for */ public function updateNotificationTimestamp( User $editor, LinkTarget $target, $timestamp ) { $dbw = $this->getConnectionRef( DB_MASTER ); $uids = $dbw->selectFieldValues( 'watchlist', 'wl_user', [ 'wl_user != ' . intval( $editor->getId() ), 'wl_namespace' => $target->getNamespace(), 'wl_title' => $target->getDBkey(), 'wl_notificationtimestamp IS NULL', ], __METHOD__ ); $watchers = array_map( 'intval', $uids ); if ( $watchers ) { // Update wl_notificationtimestamp for all watching users except the editor $fname = __METHOD__; DeferredUpdates::addCallableUpdate( function () use ( $timestamp, $watchers, $target, $fname ) { global $wgUpdateRowsPerQuery; $dbw = $this->getConnectionRef( DB_MASTER ); $factory = wfGetLBFactory(); $ticket = $factory->getEmptyTransactionTicket( __METHOD__ ); $watchersChunks = array_chunk( $watchers, $wgUpdateRowsPerQuery ); foreach ( $watchersChunks as $watchersChunk ) { $dbw->update( 'watchlist', [ /* SET */ 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp ) ], [ /* WHERE - TODO Use wl_id T130067 */ 'wl_user' => $watchersChunk, 'wl_namespace' => $target->getNamespace(), 'wl_title' => $target->getDBkey(), ], $fname ); if ( count( $watchersChunks ) > 1 ) { $factory->commitAndWaitForReplication( __METHOD__, $ticket, [ 'wiki' => $dbw->getWikiID() ] ); } } $this->uncacheLinkTarget( $target ); }, DeferredUpdates::POSTSEND, $dbw ); } return $watchers; } /** * Reset the notification timestamp of this entry * * @param User $user * @param Title $title * @param string $force Whether to force the write query to be executed even if the * page is not watched or the notification timestamp is already NULL. * 'force' in order to force * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed. * * @return bool success */ public function resetNotificationTimestamp( User $user, Title $title, $force = '', $oldid = 0 ) { // Only loggedin user can have a watchlist if ( $this->loadBalancer->getReadOnlyReason() !== false || $user->isAnon() ) { return false; } $item = null; if ( $force != 'force' ) { $item = $this->loadWatchedItem( $user, $title ); if ( !$item || $item->getNotificationTimestamp() === null ) { return false; } } // If the page is watched by the user (or may be watched), update the timestamp $job = new ActivityUpdateJob( $title, [ 'type' => 'updateWatchlistNotification', 'userid' => $user->getId(), 'notifTime' => $this->getNotificationTimestamp( $user, $title, $item, $force, $oldid ), 'curTime' => time() ] ); // Try to run this post-send // Calls DeferredUpdates::addCallableUpdate in normal operation call_user_func( $this->deferredUpdatesAddCallableUpdateCallback, function() use ( $job ) { $job->run(); } ); $this->uncache( $user, $title ); return true; } private function getNotificationTimestamp( User $user, Title $title, $item, $force, $oldid ) { if ( !$oldid ) { // No oldid given, assuming latest revision; clear the timestamp. return null; } if ( !$title->getNextRevisionID( $oldid ) ) { // Oldid given and is the latest revision for this title; clear the timestamp. return null; } if ( $item === null ) { $item = $this->loadWatchedItem( $user, $title ); } if ( !$item ) { // This can only happen if $force is enabled. return null; } // Oldid given and isn't the latest; update the timestamp. // This will result in no further notification emails being sent! // Calls Revision::getTimestampFromId in normal operation $notificationTimestamp = call_user_func( $this->revisionGetTimestampFromIdCallback, $title, $oldid ); // We need to go one second to the future because of various strict comparisons // throughout the codebase $ts = new MWTimestamp( $notificationTimestamp ); $ts->timestamp->add( new DateInterval( 'PT1S' ) ); $notificationTimestamp = $ts->getTimestamp( TS_MW ); if ( $notificationTimestamp < $item->getNotificationTimestamp() ) { if ( $force != 'force' ) { return false; } else { // This is a little silly… return $item->getNotificationTimestamp(); } } return $notificationTimestamp; } /** * @param User $user * @param int $unreadLimit * * @return int|bool The number of unread notifications * true if greater than or equal to $unreadLimit */ public function countUnreadNotifications( User $user, $unreadLimit = null ) { $queryOptions = []; if ( $unreadLimit !== null ) { $unreadLimit = (int)$unreadLimit; $queryOptions['LIMIT'] = $unreadLimit; } $dbr = $this->getConnectionRef( DB_REPLICA ); $rowCount = $dbr->selectRowCount( 'watchlist', '1', [ 'wl_user' => $user->getId(), 'wl_notificationtimestamp IS NOT NULL', ], __METHOD__, $queryOptions ); if ( !isset( $unreadLimit ) ) { return $rowCount; } if ( $rowCount >= $unreadLimit ) { return true; } return $rowCount; } /** * Check if the given title already is watched by the user, and if so * add a watch for the new title. * * To be used for page renames and such. * * @param LinkTarget $oldTarget * @param LinkTarget $newTarget */ public function duplicateAllAssociatedEntries( LinkTarget $oldTarget, LinkTarget $newTarget ) { $oldTarget = Title::newFromLinkTarget( $oldTarget ); $newTarget = Title::newFromLinkTarget( $newTarget ); $this->duplicateEntry( $oldTarget->getSubjectPage(), $newTarget->getSubjectPage() ); $this->duplicateEntry( $oldTarget->getTalkPage(), $newTarget->getTalkPage() ); } /** * Check if the given title already is watched by the user, and if so * add a watch for the new title. * * To be used for page renames and such. * This must be called separately for Subject and Talk pages * * @param LinkTarget $oldTarget * @param LinkTarget $newTarget */ public function duplicateEntry( LinkTarget $oldTarget, LinkTarget $newTarget ) { $dbw = $this->getConnectionRef( DB_MASTER ); $result = $dbw->select( 'watchlist', [ 'wl_user', 'wl_notificationtimestamp' ], [ 'wl_namespace' => $oldTarget->getNamespace(), 'wl_title' => $oldTarget->getDBkey(), ], __METHOD__, [ 'FOR UPDATE' ] ); $newNamespace = $newTarget->getNamespace(); $newDBkey = $newTarget->getDBkey(); # Construct array to replace into the watchlist $values = []; foreach ( $result as $row ) { $values[] = [ 'wl_user' => $row->wl_user, 'wl_namespace' => $newNamespace, 'wl_title' => $newDBkey, 'wl_notificationtimestamp' => $row->wl_notificationtimestamp, ]; } if ( !empty( $values ) ) { # Perform replace # Note that multi-row replace is very efficient for MySQL but may be inefficient for # some other DBMSes, mostly due to poor simulation by us $dbw->replace( 'watchlist', [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ], $values, __METHOD__ ); } } }