Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 98
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 / 98
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 / 10
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 / 5
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 ApiBase;
6use ApiModuleManager;
7use ApiQuerySiteinfo;
8use ExtensionRegistry;
9use FormatJson;
10use IContextSource;
11use MediaWiki\Api\Hook\ApiQuery__moduleManagerHook;
12use MediaWiki\Api\Hook\APIQuerySiteInfoGeneralInfoHook;
13use MediaWiki\Hook\InfoActionHook;
14use MediaWiki\Html\Html;
15use MediaWiki\MediaWikiServices;
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            'pageviews' => [ 'pageviews', 'prop', ApiQueryPageViews::class ],
97            'siteviews' => [ 'siteviews', 'meta', ApiQuerySiteViews::class ],
98            'mostviewed' => [ 'mostviewed', 'list', ApiQueryMostViewed::class ],
99        ];
100        foreach ( self::getApiScopeMap() as $apiModuleName => $serviceScopeConstant ) {
101            foreach ( self::getApiMetricsMap() as $serviceMetricConstant ) {
102                if ( $this->pageViewService->supports( $serviceMetricConstant, $serviceScopeConstant ) ) {
103                    call_user_func_array( [ $moduleManager, 'addModule' ], $moduleMap[$apiModuleName] );
104                    continue 2;
105                }
106            }
107        }
108    }
109
110    /**
111     * Add information to the siteinfo API output about which metrics are supported.
112     * @param ApiQuerySiteinfo $module
113     * @param array &$result
114     */
115    public function onAPIQuerySiteInfoGeneralInfo( $module, &$result ) {
116        $supportedMetrics = [];
117        foreach ( self::getApiScopeMap() as $apiModuleName => $serviceScopeConstant ) {
118            foreach ( self::getApiMetricsMap() as $apiMetricsName => $serviceMetricConstant ) {
119                $supportedMetrics[$apiModuleName][$apiMetricsName] =
120                    $this->pageViewService->supports( $serviceMetricConstant, $serviceScopeConstant );
121            }
122        }
123        $result['pageviewservice-supported-metrics'] = $supportedMetrics;
124    }
125
126    /**
127     * Maps allowed values of the 'metric' parameter of the pageview-related APIs to service constants.
128     * @return array
129     */
130    public static function getApiMetricsMap() {
131        return [
132            'pageviews' => PageViewService::METRIC_VIEW,
133            'uniques' => PageViewService::METRIC_UNIQUE,
134        ];
135    }
136
137    /**
138     * Maps API module names to service constants.
139     * @return array
140     */
141    public static function getApiScopeMap() {
142        return [
143            'pageviews' => PageViewService::SCOPE_ARTICLE,
144            'siteviews' => PageViewService::SCOPE_SITE,
145            'mostviewed' => PageViewService::SCOPE_TOP,
146        ];
147    }
148
149    /**
150     * Returns an array suitable for merging into getAllowedParams()
151     * @param string $scope One of the PageViewService::SCOPE_* constants
152     * @return array
153     */
154    public static function getApiMetricsHelp( $scope ) {
155        /** @var PageViewService $service */
156        $service = MediaWikiServices::getInstance()->getService( 'PageViewService' );
157        $metrics = array_keys( array_filter( self::getApiMetricsMap(),
158            static function ( $metric ) use ( $scope, $service ) {
159                return $service->supports( $metric, $scope );
160            } ) );
161        $reverseMap = array_flip( self::getApiMetricsMap() );
162        $default = $reverseMap[PageViewService::METRIC_VIEW] ?? reset( $reverseMap );
163
164        return $default ? [
165            'metric' => [
166                ParamValidator::PARAM_TYPE => $metrics,
167                ParamValidator::PARAM_DEFAULT => $default,
168                ApiBase::PARAM_HELP_MSG => 'apihelp-pageviewinfo-param-metric',
169                ApiBase::PARAM_HELP_MSG_PER_VALUE => array_map( static function ( $metric ) {
170                    return 'apihelp-pageviewinfo-paramvalue-metric-' . $metric;
171                }, array_combine( $metrics, $metrics ) ),
172            ],
173        ] : [];
174    }
175
176    /**
177     * Returns an array suitable for merging into getAllowedParams()
178     * @return array
179     */
180    public static function getApiDaysHelp() {
181        $days = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'PageViewInfo' )
182            ->get( 'PageViewApiMaxDays' );
183        return [
184            'days' => [
185                ParamValidator::PARAM_TYPE => 'integer',
186                ParamValidator::PARAM_DEFAULT => $days,
187                IntegerDef::PARAM_MAX => $days,
188                IntegerDef::PARAM_MIN => 1,
189                ApiBase::PARAM_HELP_MSG => 'apihelp-pageviewinfo-param-days',
190            ],
191        ];
192    }
193
194    /**
195     * Transform into a status with errors replaced with warnings
196     * @param StatusValue $status
197     * @return StatusValue
198     */
199    public static function makeWarningsOnlyStatus( StatusValue $status ) {
200        [ $errors, $warnings ] = $status->splitByErrorType();
201        foreach ( $errors->getErrors() as $error ) {
202            call_user_func_array( [ $warnings, 'warning' ],
203                array_merge( [ $error['message'] ], $error['params'] ) );
204        }
205        return $warnings;
206    }
207
208    /**
209     * Convert YYYY-MM-DD to YYYYMMDD
210     * @param string $date
211     * @return string
212     */
213    protected static function toYmd( $date ) {
214        return substr( $date, 0, 4 ) . substr( $date, 5, 2 ) . substr( $date, 8, 2 );
215    }
216
217    /**
218     * Convert YYYY-MM-DD to TS_MW
219     * @param string $date
220     * @return string
221     */
222    protected static function toYmdHis( $date ) {
223        return substr( $date, 0, 4 ) . substr( $date, 5, 2 ) . substr( $date, 8, 2 ) . '000000';
224    }
225}