Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
46 / 46
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
ApiQueryTrait
100.00% covered (success)
100.00%
46 / 46
100.00% covered (success)
100.00%
3 / 3
11
100.00% covered (success)
100.00%
1 / 1
 encodeContinuationParameter
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 decodeContinuationParameter
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
4
 getAllowedSortParams
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace MediaWiki\Extension\ReadingLists\Api;
4
5use ApiUsageException;
6use MediaWiki\Extension\ReadingLists\ReadingListRepository;
7use Wikimedia\ParamValidator\ParamValidator;
8
9/**
10 * Shared sorting / paging for the query APIs.
11 *
12 * Issue with phan and traits - https://github.com/phan/phan/issues/1067
13 */
14trait ApiQueryTrait {
15
16    // Mode constants, to support different sorting / paging / deleted item behavior for different
17    // parameter combinations. For no particular reason, PHP does not allow constants in traits,
18    // so we'll use statics instead.
19
20    /**
21     * Return all lists, or all entries of the specified list(s).
22     * Intended for initial copy of data to a new device, or for devices which have information
23     * that's too outdated for normal sync. Might also be useful for devices with limited storage
24     * capacity, such as web clients.
25     *
26     * @var string
27     */
28    private static $MODE_ALL = 'all';
29
30    /**
31     * Return lists/entries which have been changed (or deleted) recently.
32     * Intended for syncing updates to a device which has an older snapshot of the data.
33     * "Recently" is defined by the changedsince parameter.
34     *
35     * @var string
36     */
37    private static $MODE_CHANGES = 'changes';
38
39    /**
40     * Return lists which include a given page.
41     * Intended for status indicators and such (e.g. showing a star on the current page if it's
42     * included in some list).
43     *
44     * @var string
45     */
46    private static $MODE_PAGE = 'page';
47
48    /**
49     * Return lists/entries by ID.
50     * Intended for clients which have a limited local caching ability.
51     *
52     * @var string
53     */
54    private static $MODE_ID = 'id';
55
56    /** @var string[] Map of sort keywords used by the API to sort keywords used by the repo. */
57    private static $sortParamMap = [
58        'name' => ReadingListRepository::SORT_BY_NAME,
59        'updated' => ReadingListRepository::SORT_BY_UPDATED,
60        'ascending' => ReadingListRepository::SORT_DIR_ASC,
61        'descending' => ReadingListRepository::SORT_DIR_DESC,
62    ];
63
64    /**
65     * Extract continuation data from item position and serialize it into a string.
66     * @param array $item Result item to continue from.
67     * @param string $mode One of the MODE_* constants.
68     * @param string $sort One of the SORT_BY_* constants.
69     * @return string
70     * @suppress PhanUndeclaredStaticProperty
71     */
72    private function encodeContinuationParameter( array $item, $mode, $sort ) {
73        if ( $mode === self::$MODE_PAGE ) {
74            return $item['id'];
75        } elseif ( $sort === ReadingListRepository::SORT_BY_NAME ) {
76            if ( self::$prefix === 'rl' ) {
77                $name = $item['name'];
78            } else {
79                $name = $item['title'];
80            }
81            return $name . '|' . $item['id'];
82        } else {
83            return $item['updated'] . '|' . $item['id'];
84        }
85    }
86
87    /**
88     * Recover continuation data after it has been roundtripped to the client.
89     * @param string|null $continue Continuation parameter returned by the client.
90     * @param string $mode One of the MODE_* constants.
91     * @param string $sort One of the SORT_BY_* constants.
92     * @return null|int|string[]
93     *   - null if there was no continuation parameter;
94     *   - [ rl(e)_name, rl(e)_id ] for MODE_ALL/MODE_CHANGES when sorting by name;
95     *   - [ rl(e)_date_updated, rl(e)_id ] for MODE_ALL/MODE_CHANGES when sorting by updated time;
96     *   - rle_id for MODE_PAGE.
97     * @throws ApiUsageException
98     * @suppress PhanUndeclaredMethod
99     */
100    private function decodeContinuationParameter( $continue, $mode, $sort ) {
101        if ( $continue === null ) {
102            return null;
103        }
104
105        if ( $mode === self::$MODE_PAGE ) {
106            $this->dieContinueUsageIf( $continue !== (string)(int)$continue );
107            return (int)$continue;
108        } else {
109            // Continue token format is '<name|timestamp>|<id>'; name can contain '|'.
110            $separatorPosition = strrpos( $continue, '|' );
111            $this->dieContinueUsageIf( $separatorPosition === false );
112            $continue = [
113                substr( $continue, 0, $separatorPosition ),
114                substr( $continue, $separatorPosition + 1 ),
115            ];
116            $this->dieContinueUsageIf( $continue[1] !== (string)(int)$continue[1] );
117            $continue[1] = (int)$continue[1];
118            if ( $sort === ReadingListRepository::SORT_BY_UPDATED ) {
119                $this->dieContinueUsageIf( wfTimestamp( TS_MW, $continue[0] ) === false );
120            }
121            return $continue;
122        }
123    }
124
125    /**
126     * Get common sorting/paging related params for getAllowedParams().
127     * @return array
128     * @suppress PhanUndeclaredStaticProperty, PhanUndeclaredConstantOfClass
129     */
130    private function getAllowedSortParams() {
131        return [
132            'sort' => [
133                ParamValidator::PARAM_TYPE => [ 'name', 'updated' ],
134                self::PARAM_HELP_MSG_PER_VALUE => [],
135            ],
136            'dir' => [
137                ParamValidator::PARAM_DEFAULT => 'ascending',
138                ParamValidator::PARAM_TYPE => [ 'ascending', 'descending' ],
139            ],
140            'limit' => [
141                ParamValidator::PARAM_DEFAULT => 10,
142                ParamValidator::PARAM_TYPE => 'limit',
143                self::PARAM_MIN => 1,
144                // Temporarily limit paging sizes per T164990#3264314 / T168984#3659998
145                self::PARAM_MAX => self::$prefix === 'rl' ? 10 : 100,
146                self::PARAM_MAX2 => self::$prefix === 'rl' ? 10 : 100,
147            ],
148            'continue' => [
149                ParamValidator::PARAM_TYPE => 'string',
150                ParamValidator::PARAM_DEFAULT => null,
151                self::PARAM_HELP_MSG => 'api-help-param-continue',
152            ],
153        ];
154    }
155
156}