Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 125
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
Hooks
0.00% covered (danger)
0.00%
0 / 125
0.00% covered (danger)
0.00%
0 / 11
506
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onInfoAction
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
30
 onApiQuery__ModuleManager
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
20
 onAPIQuerySiteInfoGeneralInfo
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getApiMetricsMap
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getApiScopeMap
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getApiMetricsHelp
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 getApiDaysHelp
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 makeWarningsOnlyStatus
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 toYmd
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 toYmdHis
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\PageViewInfo;
4
5use MediaWiki\Api\ApiBase;
6use MediaWiki\Api\ApiModuleManager;
7use MediaWiki\Api\ApiQuerySiteinfo;
8use MediaWiki\Api\Hook\ApiQuery__moduleManagerHook;
9use MediaWiki\Api\Hook\APIQuerySiteInfoGeneralInfoHook;
10use MediaWiki\Context\IContextSource;
11use MediaWiki\Hook\InfoActionHook;
12use MediaWiki\Html\Html;
13use MediaWiki\Json\FormatJson;
14use MediaWiki\MediaWikiServices;
15use MediaWiki\Registration\ExtensionRegistry;
16use StatusValue;
17use Wikimedia\ParamValidator\ParamValidator;
18use Wikimedia\ParamValidator\TypeDef\IntegerDef;
19
20/**
21 * @phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName
22 */
23class Hooks implements
24    ApiQuery__moduleManagerHook,
25    APIQuerySiteInfoGeneralInfoHook,
26    InfoActionHook
27{
28
29    private PageViewService $pageViewService;
30
31    public function __construct( PageViewService $pageViewService ) {
32        $this->pageViewService = $pageViewService;
33    }
34
35    /**
36     * Display total pageviews in the last 30 days and show a graph with details when clicked.
37     * @param IContextSource $ctx
38     * @param array &$pageInfo
39     */
40    public function onInfoAction( $ctx, &$pageInfo ) {
41        if ( !$this->pageViewService->supports( PageViewService::METRIC_VIEW,
42            PageViewService::SCOPE_ARTICLE )
43        ) {
44            return;
45        }
46        $title = $ctx->getTitle();
47        $status = $this->pageViewService->getPageData( [ $title ], 30, PageViewService::METRIC_VIEW );
48        $data = $status->getValue();
49        if ( !$status->isOK() ) {
50            return;
51        }
52        $views = $data[$title->getPrefixedDBkey()];
53
54        $total = array_sum( $views );
55        $start = self::toYmdHis( array_key_first( $views ) );
56        $end = self::toYmdHis( array_key_last( $views ) );
57
58        $lang = $ctx->getLanguage();
59        $formatted = $lang->formatNum( $total );
60        $pageInfo['header-basic'][] = [
61            $ctx->msg( 'pvi-month-count' ),
62            Html::rawElement( 'div',
63                [ 'class' => 'mw-pvi-month' ],
64                $ctx->msg( 'pvi-month-count-value', $formatted, $title->getPrefixedDBkey() )->parse()
65            )
66        ];
67
68        if ( ExtensionRegistry::getInstance()->isLoaded( 'Graph' ) ) {
69            $info = FormatJson::decode(
70                file_get_contents( __DIR__ . '/../graphs/month.json' ),
71                true
72            );
73            foreach ( $views as $day => $count ) {
74                $info['data'][0]['values'][] = [ 'timestamp' => self::toYmd( $day ), 'views' => $count ];
75            }
76
77            $ctx->getOutput()->addModules( 'ext.pageviewinfo' );
78            // Ymd -> YmdHis
79            $user = $ctx->getUser();
80            $ctx->getOutput()->addJsConfigVars( [
81                'wgPageViewInfo' => [
82                    'graph' => $info,
83                    'start' => $lang->userDate( $start, $user ),
84                    'end' => $lang->userDate( $end, $user ),
85                ],
86            ] );
87        }
88    }
89
90    /**
91     * Limit enabled PageViewInfo API modules to those which are supported by the service.
92     * @param ApiModuleManager $moduleManager
93     */
94    public function onApiQuery__ModuleManager( $moduleManager ) {
95        $moduleMap = [
96            // Order is: module name, module group, objectFactory spec
97            'pageviews' => [
98                'pageviews',
99                'prop',
100                [
101                    'class' => ApiQueryPageViews::class,
102                    'services' => [
103                        'PageViewService',
104                        'TitleFormatter',
105                    ]
106                ]
107            ],
108            'siteviews' => [
109                'siteviews',
110                'meta',
111                [
112                    'class' => ApiQuerySiteViews::class,
113                    'services' => [
114                        'PageViewService',
115                    ]
116                ]
117            ],
118            'mostviewed' => [
119                'mostviewed',
120                'list',
121                [
122                    'class' => ApiQueryMostViewed::class,
123                    'services' => [
124                        'PageViewService',
125                    ]
126                ]
127            ],
128        ];
129        foreach ( self::getApiScopeMap() as $apiModuleName => $serviceScopeConstant ) {
130            foreach ( self::getApiMetricsMap() as $serviceMetricConstant ) {
131                if ( $this->pageViewService->supports( $serviceMetricConstant, $serviceScopeConstant ) ) {
132                    $moduleManager->addModule( ...$moduleMap[$apiModuleName] );
133                    continue 2;
134                }
135            }
136        }
137    }
138
139    /**
140     * Add information to the siteinfo API output about which metrics are supported.
141     * @param ApiQuerySiteinfo $module
142     * @param array &$result
143     */
144    public function onAPIQuerySiteInfoGeneralInfo( $module, &$result ) {
145        $supportedMetrics = [];
146        foreach ( self::getApiScopeMap() as $apiModuleName => $serviceScopeConstant ) {
147            foreach ( self::getApiMetricsMap() as $apiMetricsName => $serviceMetricConstant ) {
148                $supportedMetrics[$apiModuleName][$apiMetricsName] =
149                    $this->pageViewService->supports( $serviceMetricConstant, $serviceScopeConstant );
150            }
151        }
152        $result['pageviewservice-supported-metrics'] = $supportedMetrics;
153    }
154
155    /**
156     * Maps allowed values of the 'metric' parameter of the pageview-related APIs to service constants.
157     * @return array
158     */
159    public static function getApiMetricsMap() {
160        return [
161            'pageviews' => PageViewService::METRIC_VIEW,
162            'uniques' => PageViewService::METRIC_UNIQUE,
163        ];
164    }
165
166    /**
167     * Maps API module names to service constants.
168     * @return array
169     */
170    public static function getApiScopeMap() {
171        return [
172            'pageviews' => PageViewService::SCOPE_ARTICLE,
173            'siteviews' => PageViewService::SCOPE_SITE,
174            'mostviewed' => PageViewService::SCOPE_TOP,
175        ];
176    }
177
178    /**
179     * Returns an array suitable for merging into getAllowedParams()
180     * @param string $scope One of the PageViewService::SCOPE_* constants
181     * @return array
182     */
183    public static function getApiMetricsHelp( $scope ) {
184        /** @var PageViewService $service */
185        $service = MediaWikiServices::getInstance()->getService( 'PageViewService' );
186        $metrics = array_keys( array_filter( self::getApiMetricsMap(),
187            static function ( $metric ) use ( $scope, $service ) {
188                return $service->supports( $metric, $scope );
189            } ) );
190        $reverseMap = array_flip( self::getApiMetricsMap() );
191        $default = $reverseMap[PageViewService::METRIC_VIEW] ?? reset( $reverseMap );
192
193        return $default ? [
194            'metric' => [
195                ParamValidator::PARAM_TYPE => $metrics,
196                ParamValidator::PARAM_DEFAULT => $default,
197                ApiBase::PARAM_HELP_MSG => 'apihelp-pageviewinfo-param-metric',
198                ApiBase::PARAM_HELP_MSG_PER_VALUE => array_map( static function ( $metric ) {
199                    return 'apihelp-pageviewinfo-paramvalue-metric-' . $metric;
200                }, array_combine( $metrics, $metrics ) ),
201            ],
202        ] : [];
203    }
204
205    /**
206     * Returns an array suitable for merging into getAllowedParams()
207     * @return array
208     */
209    public static function getApiDaysHelp() {
210        $days = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'PageViewInfo' )
211            ->get( 'PageViewApiMaxDays' );
212        return [
213            'days' => [
214                ParamValidator::PARAM_TYPE => 'integer',
215                ParamValidator::PARAM_DEFAULT => $days,
216                IntegerDef::PARAM_MAX => $days,
217                IntegerDef::PARAM_MIN => 1,
218                ApiBase::PARAM_HELP_MSG => 'apihelp-pageviewinfo-param-days',
219            ],
220        ];
221    }
222
223    /**
224     * Transform into a status with errors replaced with warnings
225     * @param StatusValue $status
226     * @return StatusValue
227     */
228    public static function makeWarningsOnlyStatus( StatusValue $status ) {
229        [ $errors, $warnings ] = $status->splitByErrorType();
230        foreach ( $errors->getErrors() as $error ) {
231            $warnings->warning( $error['message'], ...$error['params'] );
232        }
233        return $warnings;
234    }
235
236    /**
237     * Convert YYYY-MM-DD to YYYYMMDD
238     * @param string $date
239     * @return string
240     */
241    protected static function toYmd( $date ) {
242        return substr( $date, 0, 4 ) . substr( $date, 5, 2 ) . substr( $date, 8, 2 );
243    }
244
245    /**
246     * Convert YYYY-MM-DD to TS_MW
247     * @param string $date
248     * @return string
249     */
250    protected static function toYmdHis( $date ) {
251        return substr( $date, 0, 4 ) . substr( $date, 5, 2 ) . substr( $date, 8, 2 ) . '000000';
252    }
253}