Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F2750933
GlobalUserPage.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
11 KB
Referenced Files
None
Subscribers
None
GlobalUserPage.php
View Options
<?php
/**
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace
MediaWiki\GlobalUserPage
;
use
Article
;
use
MapCacheLRU
;
use
MediaWiki\Config\Config
;
use
MediaWiki\GlobalUserPage\Hooks\HookRunner
;
use
MediaWiki\Html\Html
;
use
MediaWiki\MediaWikiServices
;
use
MediaWiki\Output\OutputPage
;
use
MediaWiki\Parser\ParserOutput
;
use
MediaWiki\Parser\ParserOutputFlags
;
use
MediaWiki\Title\Title
;
use
MediaWiki\User\User
;
use
MediaWiki\WikiMap\WikiMap
;
use
Wikimedia\IPUtils
;
use
Wikimedia\ObjectCache\WANObjectCache
;
use
Wikimedia\Parsoid\Core\TOCData
;
/**
* @property WikiGlobalUserPage $mPage Set by overwritten newPage() in this class
*/
class
GlobalUserPage
extends
Article
{
/**
* Cache version of action=parse
* output
*/
private
const
PARSED_CACHE_VERSION
=
4
;
/**
* @var Config
*/
private
$config
;
/**
* @var WANObjectCache
*/
private
$cache
;
/**
* @var MapCacheLRU
*/
private
static
$displayCache
;
/**
* @var MapCacheLRU
*/
private
static
$touchedCache
;
public
function
__construct
(
Title
$title
,
Config
$config
)
{
$this
->
config
=
$config
;
$this
->
cache
=
MediaWikiServices
::
getInstance
()->
getMainWANObjectCache
();
parent
::
__construct
(
$title
);
}
public
function
showMissingArticle
()
{
$title
=
$this
->
getTitle
();
if
(
!
self
::
shouldDisplayGlobalPage
(
$title
)
)
{
parent
::
showMissingArticle
();
return
;
}
$user
=
User
::
newFromName
(
$this
->
getUsername
()
);
$out
=
$this
->
getContext
()->
getOutput
();
$parsedOutput
=
$this
->
getRemoteParsedText
(
self
::
getCentralTouched
(
$user
)
);
// If the user page is empty or the API request failed, show the normal
// missing article page
if
(
!
$parsedOutput
||
!
trim
(
$parsedOutput
[
'text'
]
)
)
{
parent
::
showMissingArticle
();
return
;
}
$out
->
addHTML
(
$parsedOutput
[
'text'
]
);
$out
->
addModuleStyles
(
'ext.GlobalUserPage'
);
// Set canonical URL to point to the source
$sourceURL
=
$this
->
mPage
->
getSourceURL
();
$out
->
setCanonicalUrl
(
$sourceURL
);
$footerKey
=
$this
->
config
->
get
(
'GlobalUserPageFooterKey'
);
if
(
$footerKey
)
{
$userLang
=
$this
->
getContext
()->
getLanguage
();
$out
->
addHTML
(
Html
::
rawElement
(
'div'
,
[
'lang'
=>
$userLang
->
getHtmlCode
(),
'dir'
=>
$userLang
->
getDir
(),
'class'
=>
'mw-globaluserpage-footer plainlinks'
],
"
\n
"
.
$out
->
msg
(
$footerKey
)->
params
(
$this
->
getUsername
(),
$sourceURL
)->
parse
()
.
"
\n
"
)
);
}
// Load ParserOutput modules...
$this
->
loadModules
(
$out
,
$parsedOutput
);
// Add indicators (T149286)
$out
->
setIndicators
(
$parsedOutput
[
'indicators'
]
);
$pout
=
new
ParserOutput
;
// Add sections for new style of table of contents (T327942)
$sections
=
$parsedOutput
[
'sections'
]
??
null
;
if
(
$sections
)
{
// FIXME: The action=parse API only outputs sections in the legacy format
$pout
->
setTOCData
(
TOCData
::
fromLegacy
(
$sections
)
);
$pout
->
setOutputFlag
(
ParserOutputFlags
::
SHOW_TOC
,
$parsedOutput
[
'showtoc'
]
??
true
);
}
// Add external links (T334805)
foreach
(
$parsedOutput
[
'externallinks'
]
??
[]
as
$extLink
)
{
$pout
->
addExternalLink
(
$extLink
);
}
$out
->
addParserOutputMetadata
(
$pout
);
// Make sure we set the correct robot policy
$policy
=
$this
->
getRobotPolicy
(
'view'
);
$out
->
setIndexPolicy
(
$policy
[
'index'
]
);
$out
->
setFollowPolicy
(
$policy
[
'follow'
]
);
}
/**
* Override robot policy to always set noindex (T177159)
*
* @param string $action
* @param ParserOutput|null $pOutput
* @return array
*/
public
function
getRobotPolicy
(
$action
,
?
ParserOutput
$pOutput
=
null
)
{
$policy
=
parent
::
getRobotPolicy
(
$action
,
$pOutput
);
if
(
self
::
shouldDisplayGlobalPage
(
$this
->
getTitle
()
)
)
{
// Set noindex if this page is global
$policy
[
'index'
]
=
'noindex'
;
}
return
$policy
;
}
/**
* Attempts to load modules through the
* ParserOutput on the local wiki, if
* they exist.
*
* @param OutputPage $out
* @param array $parsedOutput
*/
private
function
loadModules
(
OutputPage
$out
,
array
$parsedOutput
)
{
$rl
=
$out
->
getResourceLoader
();
foreach
(
$parsedOutput
[
'modules'
]
as
$module
)
{
if
(
$rl
->
isModuleRegistered
(
$module
)
)
{
$out
->
addModules
(
$module
);
}
}
foreach
(
$parsedOutput
[
'modulestyles'
]
as
$module
)
{
if
(
$rl
->
isModuleRegistered
(
$module
)
)
{
$out
->
addModuleStyles
(
$module
);
}
}
$out
->
addJsConfigVars
(
$parsedOutput
[
'jsconfigvars'
]
);
}
/**
* Given a Title, assuming it doesn't exist, should
* we display a global user page on it
*
* @param Title $title
* @return bool
*/
public
static
function
shouldDisplayGlobalPage
(
Title
$title
)
{
global
$wgGlobalUserPageDBname
;
if
(
!
self
::
canBeGlobal
(
$title
)
)
{
return
false
;
}
// Do some instance caching since this can be
// called frequently due do the Linker hook
if
(
!
self
::
$displayCache
)
{
self
::
$displayCache
=
new
MapCacheLRU
(
100
);
}
$text
=
$title
->
getPrefixedText
();
if
(
self
::
$displayCache
->
has
(
$text
)
)
{
return
self
::
$displayCache
->
get
(
$text
);
}
// Normalize the username
$user
=
User
::
newFromName
(
$title
->
getText
()
);
if
(
!
$user
)
{
self
::
$displayCache
->
set
(
$text
,
false
);
return
false
;
}
// Make sure that the username represents the same
// user on both wikis.
$lookup
=
MediaWikiServices
::
getInstance
()->
getCentralIdLookupFactory
()->
getLookup
();
if
(
!
$lookup
->
isAttached
(
$user
)
||
!
$lookup
->
isAttached
(
$user
,
$wgGlobalUserPageDBname
)
)
{
self
::
$displayCache
->
set
(
$text
,
false
);
return
false
;
}
$touched
=
(
bool
)
self
::
getCentralTouched
(
$user
);
self
::
$displayCache
->
set
(
$text
,
$touched
);
return
$touched
;
}
/**
* Get the page_touched of the central user page
*
* @todo this probably shouldn't be static
* @param User $user
* @return string|bool
*/
protected
static
function
getCentralTouched
(
User
$user
)
{
if
(
!
self
::
$touchedCache
)
{
self
::
$touchedCache
=
new
MapCacheLRU
(
100
);
}
if
(
self
::
$touchedCache
->
has
(
$user
->
getName
()
)
)
{
return
self
::
$touchedCache
->
get
(
$user
->
getName
()
);
}
global
$wgGlobalUserPageDBname
;
$factory
=
MediaWikiServices
::
getInstance
()->
getDBLoadBalancerFactory
();
$mainLB
=
$factory
->
getMainLB
(
$wgGlobalUserPageDBname
);
$dbr
=
$mainLB
->
getConnection
(
DB_REPLICA
,
[],
$wgGlobalUserPageDBname
);
$row
=
$dbr
->
newSelectQueryBuilder
()
->
select
(
[
'page_touched'
,
'pp_propname'
]
)
->
from
(
'page'
)
->
leftJoin
(
'page_props'
,
null
,
[
'page_id=pp_page'
,
'pp_propname'
=>
'noglobal'
]
)
->
where
(
[
'page_namespace'
=>
NS_USER
,
'page_title'
=>
$user
->
getUserPage
()->
getDBkey
(),
]
)
->
caller
(
__METHOD__
)
->
fetchRow
();
if
(
$row
)
{
if
(
$row
->
pp_propname
==
'noglobal'
)
{
$touched
=
false
;
}
else
{
$touched
=
$row
->
page_touched
;
}
}
else
{
$touched
=
false
;
}
self
::
$touchedCache
->
set
(
$user
->
getName
(),
$touched
);
return
$touched
;
}
/**
* Given a Title, is it a source page we might
* be "transcluding" on another site
*
* @return bool
*/
public
function
isSourcePage
()
{
if
(
WikiMap
::
getCurrentWikiId
()
!==
$this
->
config
->
get
(
'GlobalUserPageDBname'
)
)
{
return
false
;
}
$title
=
$this
->
getTitle
();
if
(
!
$title
->
inNamespace
(
NS_USER
)
)
{
return
false
;
}
// Root user page
return
$title
->
getRootTitle
()->
equals
(
$title
);
}
/**
* Username for the given global user page
*
* @return string
*/
public
function
getUsername
()
{
return
$this
->
mPage
->
getUsername
();
}
/**
* We trust that the remote wiki has done proper HTML escaping and isn't
* crazy by having raw HTML enabled.
*
* @param string $touched The page_touched for the page
* @return array|bool
* @return-taint escaped
*/
public
function
getRemoteParsedText
(
$touched
)
{
$langCode
=
$this
->
getContext
()->
getLanguage
()->
getCode
();
$skinName
=
$this
->
getContext
()->
getSkin
()->
getSkinName
();
$cache
=
$this
->
cache
;
return
$cache
->
getWithSetCallback
(
// Need language and skin in the key since we pass them to the API
$cache
->
makeGlobalKey
(
'globaluserpage-parsed'
,
$touched
,
$langCode
,
$skinName
,
md5
(
$this
->
getUsername
()
)
),
$this
->
config
->
get
(
'GlobalUserPageCacheExpiry'
),
function
(
$oldValue
,
&
$ttl
)
use
(
$langCode
,
$skinName
,
$cache
)
{
$data
=
$this
->
parseWikiText
(
$this
->
getTitle
(),
$langCode
,
$skinName
);
if
(
!
$data
)
{
// Cache failure for 10 seconds
$ttl
=
$cache
::
TTL_UNCACHEABLE
;
}
return
$data
;
},
[
'version'
=>
self
::
PARSED_CACHE_VERSION
]
);
}
/**
* Checks whether the given page can be global
* doesn't check the actual database
* @param Title $title
* @return bool
*/
protected
static
function
canBeGlobal
(
Title
$title
)
{
global
$wgGlobalUserPageDBname
;
// Don't run this code for Hub.
if
(
WikiMap
::
getCurrentWikiId
()
===
$wgGlobalUserPageDBname
)
{
return
false
;
}
// Must be a user page
if
(
!
$title
->
inNamespace
(
NS_USER
)
)
{
return
false
;
}
// Check it's a root user page
if
(
$title
->
getRootText
()
!==
$title
->
getText
()
)
{
return
false
;
}
// Check valid username
if
(
!
MediaWikiServices
::
getInstance
()->
getUserNameUtils
()->
isValid
(
$title
->
getText
()
)
)
{
return
false
;
}
// IPs don't get global userpages
return
!
IPUtils
::
isIPAddress
(
$title
->
getText
()
);
}
/**
* @param Title $title
* @return WikiGlobalUserPage
*/
public
function
newPage
(
Title
$title
)
{
return
new
WikiGlobalUserPage
(
$title
,
$this
->
config
);
}
/**
* Use action=parse to get rendered HTML of a page
*
* @param Title $title
* @param string $langCode
* @param string $skinName
* @return array|bool
*/
protected
function
parseWikiText
(
Title
$title
,
$langCode
,
$skinName
)
{
$unLocalizedName
=
MediaWikiServices
::
getInstance
()
->
getNamespaceInfo
()
->
getCanonicalName
(
NS_USER
)
.
':'
.
$title
->
getText
();
$wikitext
=
'{{:'
.
$unLocalizedName
.
'}}'
;
$params
=
[
'action'
=>
'parse'
,
'title'
=>
$unLocalizedName
,
'text'
=>
$wikitext
,
'disableeditsection'
=>
1
,
'disablelimitreport'
=>
1
,
'uselang'
=>
$langCode
,
'useskin'
=>
$skinName
,
'prop'
=>
'text|modules|jsconfigvars|indicators|sections|externallinks'
,
'formatversion'
=>
2
,
];
$data
=
$this
->
mPage
->
makeAPIRequest
(
$params
);
// (T328694) Don't read 'parse' key blindly, it might not be set
return
$data
!==
false
?
(
$data
[
'parse'
]
??
false
)
:
false
;
}
/**
* @return array
*/
public
static
function
getEnabledWikis
()
{
static
$list
=
null
;
if
(
$list
===
null
)
{
$list
=
[];
$hookRunner
=
new
HookRunner
(
MediaWikiServices
::
getInstance
()->
getHookContainer
()
);
if
(
$hookRunner
->
onGlobalUserPageWikis
(
$list
)
)
{
// Fallback if no hook override
global
$wgLocalDatabases
;
$list
=
$wgLocalDatabases
;
}
}
return
$list
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Fri, Jul 3, 17:27 (15 h, 34 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
fe/3c/8bc482d6b2d4dbbbb09a118b7e63
Default Alt Text
GlobalUserPage.php (11 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment