Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4118238
TranslationStatsDataProvider.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
7 KB
Referenced Files
None
Subscribers
None
TranslationStatsDataProvider.php
View Options
<?php
declare
(
strict_types
=
1
);
namespace
MediaWiki\Extension\Translate\Statistics
;
use
MediaWiki\Config\ServiceOptions
;
use
MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroups
;
use
MediaWiki\Extension\Translate\Utilities\Utilities
;
use
MediaWiki\Language\Language
;
use
Wikimedia\ObjectFactory\ObjectFactory
;
use
Wikimedia\Rdbms\IConnectionProvider
;
use
Wikimedia\Rdbms\IReadableDatabase
;
use
const
TS_MW
;
/**
* Provides translation stats data
* @author Abijeet Patro
* @license GPL-2.0-or-later
* @since 2020.09
*/
class
TranslationStatsDataProvider
{
public
const
CONSTRUCTOR_OPTIONS
=
[
'TranslateStatsProviders'
];
private
ObjectFactory
$objectFactory
;
private
ServiceOptions
$options
;
private
IConnectionProvider
$dbProvider
;
public
function
__construct
(
ServiceOptions
$options
,
ObjectFactory
$objectFactory
,
IConnectionProvider
$dbProvider
)
{
$this
->
options
=
$options
;
$this
->
objectFactory
=
$objectFactory
;
$this
->
dbProvider
=
$dbProvider
;
}
private
function
getGraphSpecifications
():
array
{
return
array_filter
(
$this
->
options
->
get
(
'TranslateStatsProviders'
)
);
}
public
function
getGraphTypes
():
array
{
return
array_keys
(
$this
->
getGraphSpecifications
()
);
}
/**
* Fetches and preprocesses graph data that can be fed to graph drawer.
* @param TranslationStatsGraphOptions $opts
* @param Language $language
* @return array ( string => array ) Data indexed by their date labels.
*/
public
function
getGraphData
(
TranslationStatsGraphOptions
$opts
,
Language
$language
):
array
{
$dbr
=
$this
->
dbProvider
->
getReplicaDatabase
();
$so
=
$this
->
getStatsProvider
(
$opts
->
getValue
(
'count'
),
$opts
);
$fixedStart
=
$opts
->
getValue
(
'start'
)
!==
''
;
$now
=
time
();
$period
=
3600
*
24
*
$opts
->
getValue
(
'days'
);
if
(
$fixedStart
)
{
$cutoff
=
(
int
)
wfTimestamp
(
TS_UNIX
,
$opts
->
getValue
(
'start'
)
);
}
else
{
$cutoff
=
$now
-
$period
;
}
$cutoff
=
self
::
roundTimestampToCutoff
(
$opts
->
getValue
(
'scale'
),
$cutoff
,
'earlier'
);
$start
=
$cutoff
;
if
(
$fixedStart
)
{
$end
=
self
::
roundTimestampToCutoff
(
$opts
->
getValue
(
'scale'
),
$start
+
$period
,
'later'
)
-
1
;
}
else
{
$end
=
null
;
}
$timestampColumn
=
$so
->
getTimestampColumn
();
$selectQueryBuilder
=
$so
->
createQueryBuilder
(
$dbr
,
__METHOD__
);
$selectQueryBuilder
->
andWhere
(
$this
->
makeTimeCondition
(
$dbr
,
$timestampColumn
,
wfTimestamp
(
TS_MW
,
$start
),
wfTimestampOrNull
(
TS_MW
,
$end
)
)
);
$res
=
$selectQueryBuilder
->
fetchResultSet
();
wfDebug
(
__METHOD__
.
"-queryend
\n
"
);
// Start processing the data
$dateFormat
=
$so
->
getDateFormat
();
$increment
=
self
::
getIncrement
(
$opts
->
getValue
(
'scale'
)
);
$labels
=
$so
->
labels
();
$keys
=
array_keys
(
$labels
);
$values
=
array_pad
(
[],
count
(
$labels
),
0
);
$defaults
=
array_combine
(
$keys
,
$values
);
$data
=
[];
// Allow 10 seconds in the future for processing time
$lastValue
=
$end
??
$now
+
10
;
while
(
$cutoff
<=
$lastValue
)
{
$date
=
$language
->
sprintfDate
(
$dateFormat
,
wfTimestamp
(
TS_MW
,
$cutoff
)
);
$cutoff
+=
$increment
;
$data
[
$date
]
=
$defaults
;
}
// Ensure $lastValue is within range, in case the loop above jumped over it
$data
[
$language
->
sprintfDate
(
$dateFormat
,
wfTimestamp
(
TS_MW
,
$lastValue
)
)]
=
$defaults
;
// Processing
$labelToIndex
=
array_flip
(
$labels
);
foreach
(
$res
as
$row
)
{
$indexLabels
=
$so
->
indexOf
(
$row
);
if
(
$indexLabels
===
null
)
{
continue
;
}
foreach
(
$indexLabels
as
$i
)
{
if
(
!
isset
(
$labelToIndex
[
$i
]
)
)
{
continue
;
}
$date
=
$language
->
sprintfDate
(
$dateFormat
,
$row
->
$timestampColumn
);
// Ignore values outside range
if
(
!
isset
(
$data
[
$date
]
)
)
{
continue
;
}
$data
[
$date
][
$labelToIndex
[
$i
]]++;
}
}
// Don't display dummy label
if
(
count
(
$labels
)
===
1
&&
$labels
[
0
]
===
'all'
)
{
$labels
=
[];
}
foreach
(
$labels
as
&
$label
)
{
if
(
!
str_contains
(
$label
,
'@'
)
)
{
continue
;
}
[
$groupId
,
$code
]
=
explode
(
'@'
,
$label
,
2
);
if
(
$code
&&
$groupId
)
{
$code
=
Utilities
::
getLanguageName
(
$code
,
$language
->
getCode
()
)
.
" ($code)"
;
$group
=
MessageGroups
::
getGroup
(
$groupId
);
$group
=
$group
?
$group
->
getLabel
()
:
$groupId
;
$label
=
"$group @ $code"
;
}
elseif
(
$code
)
{
$label
=
Utilities
::
getLanguageName
(
$code
,
$language
->
getCode
()
)
.
" ($code)"
;
}
elseif
(
$groupId
)
{
$group
=
MessageGroups
::
getGroup
(
$groupId
);
$label
=
$group
?
$group
->
getLabel
()
:
$groupId
;
}
}
// Indicator that the last value is not full
if
(
$end
===
null
)
{
// Warning: do not user array_splice, which does not preserve numerical keys
$last
=
end
(
$data
);
$key
=
key
(
$data
);
unset
(
$data
[
$key
]
);
$data
[
"$key*"
]
=
$last
;
}
return
[
$labels
,
$data
];
}
/** @noinspection PhpIncompatibleReturnTypeInspection */
private
function
getStatsProvider
(
string
$type
,
TranslationStatsGraphOptions
$opts
):
TranslationStatsInterface
{
$specs
=
$this
->
getGraphSpecifications
();
return
$this
->
objectFactory
->
createObject
(
$specs
[
$type
],
[
'allowClassName'
=>
true
,
'extraArgs'
=>
[
$opts
],
]
);
}
/**
* Gets the closest earliest timestamp that corresponds to start of a
* period in given scale, like, midnight, monday or first day of the month.
*/
private
static
function
roundTimestampToCutoff
(
string
$scale
,
int
$cutoff
,
string
$direction
=
'earlier'
):
int
{
$dir
=
$direction
===
'earlier'
?
-
1
:
1
;
/* Ensure that the first item in the graph has full data even
* if it doesn't align with the given 'days' boundary */
if
(
$scale
===
'hours'
)
{
$cutoff
+=
self
::
roundingAddition
(
$cutoff
,
3600
,
$dir
);
}
elseif
(
$scale
===
'days'
)
{
$cutoff
+=
self
::
roundingAddition
(
$cutoff
,
86400
,
$dir
);
}
elseif
(
$scale
===
'weeks'
)
{
/* Here we assume that week starts on monday, which does not
* always hold true. Go Xwards day by day until we are on monday */
while
(
date
(
'D'
,
$cutoff
)
!==
'Mon'
)
{
$cutoff
+=
$dir
*
86400
;
}
// Round to nearest day
$cutoff
-=
(
$cutoff
%
86400
);
}
elseif
(
$scale
===
'months'
)
{
// Go Xwards/ day by day until we are on the first day of the month
while
(
date
(
'j'
,
$cutoff
)
!==
'1'
)
{
$cutoff
+=
$dir
*
86400
;
}
// Round to nearest day
$cutoff
-=
(
$cutoff
%
86400
);
}
elseif
(
$scale
===
'years'
)
{
// Go Xwards/ day by day until we are on the first day of the year
while
(
date
(
'z'
,
$cutoff
)
!==
'0'
)
{
$cutoff
+=
$dir
*
86400
;
}
// Round to nearest day
$cutoff
-=
(
$cutoff
%
86400
);
}
return
$cutoff
;
}
private
static
function
roundingAddition
(
int
$ts
,
int
$amount
,
int
$dir
):
int
{
if
(
$dir
===
-
1
)
{
return
-
1
*
(
$ts
%
$amount
);
}
else
{
return
$amount
-
(
$ts
%
$amount
);
}
}
/**
* Returns an increment in seconds for a given scale.
* The increment must be small enough that we will hit every item in the
* scale when using different multiples of the increment. It should be
* large enough to avoid hitting the same item multiple times.
*/
private
static
function
getIncrement
(
string
$scale
):
int
{
$increment
=
3600
*
24
;
if
(
$scale
===
'years'
)
{
$increment
=
3600
*
24
*
350
;
}
elseif
(
$scale
===
'months'
)
{
/* We use increment to fill up the values. Use number small enough
* to ensure we hit each month */
$increment
=
3600
*
24
*
15
;
}
elseif
(
$scale
===
'weeks'
)
{
$increment
=
3600
*
24
*
7
;
}
elseif
(
$scale
===
'hours'
)
{
$increment
=
3600
;
}
return
$increment
;
}
/** @return string[] */
private
function
makeTimeCondition
(
IReadableDatabase
$database
,
string
$field
,
?
string
$start
,
?
string
$end
):
array
{
$conditions
=
[];
if
(
$start
!==
null
)
{
$conditions
[]
=
"$field >= "
.
$database
->
addQuotes
(
$database
->
timestamp
(
$start
)
);
}
if
(
$end
!==
null
)
{
$conditions
[]
=
"$field <= "
.
$database
->
addQuotes
(
$database
->
timestamp
(
$end
)
);
}
return
$conditions
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Wed, Aug 19, 02:40 (2 w, 2 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
4f/7f/0a61df1daf1eb4578d5fab7db9d7
Default Alt Text
TranslationStatsDataProvider.php (7 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment