Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
67.65% |
69 / 102 |
|
50.00% |
1 / 2 |
CRAP | |
0.00% |
0 / 1 |
| QueryAbuseFilters | |
67.65% |
69 / 102 |
|
50.00% |
1 / 2 |
90.51 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| execute | |
67.33% |
68 / 101 |
|
0.00% |
0 / 1 |
81.20 | |||
| getAllowedParams | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
| getExamplesMessages | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
| 1 | <?php |
| 2 | /** |
| 3 | * This program is free software; you can redistribute it and/or modify |
| 4 | * it under the terms of the GNU General Public License as published by |
| 5 | * the Free Software Foundation; either version 2 of the License, or |
| 6 | * (at your option) any later version. |
| 7 | * |
| 8 | * This program is distributed in the hope that it will be useful, |
| 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | * GNU General Public License for more details. |
| 12 | * |
| 13 | * You should have received a copy of the GNU General Public License along |
| 14 | * with this program; if not, write to the Free Software Foundation, Inc., |
| 15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 16 | * http://www.gnu.org/copyleft/gpl.html |
| 17 | */ |
| 18 | |
| 19 | namespace MediaWiki\Extension\AbuseFilter\Api; |
| 20 | |
| 21 | use MediaWiki\Api\ApiBase; |
| 22 | use MediaWiki\Api\ApiQuery; |
| 23 | use MediaWiki\Api\ApiQueryBase; |
| 24 | use MediaWiki\Extension\AbuseFilter\AbuseFilterPermissionManager; |
| 25 | use MediaWiki\Extension\AbuseFilter\Filter\Flags; |
| 26 | use MediaWiki\Extension\AbuseFilter\FilterLookup; |
| 27 | use Wikimedia\ParamValidator\ParamValidator; |
| 28 | use Wikimedia\ParamValidator\TypeDef\IntegerDef; |
| 29 | use Wikimedia\Timestamp\ConvertibleTimestamp; |
| 30 | |
| 31 | /** |
| 32 | * Query module to list abuse filter details. |
| 33 | * |
| 34 | * @copyright 2009 Alex Z. <mrzmanwiki AT gmail DOT com> |
| 35 | * Based mostly on code by Bryan Tong Minh and Roan Kattouw |
| 36 | * |
| 37 | * @ingroup API |
| 38 | * @ingroup Extensions |
| 39 | */ |
| 40 | class QueryAbuseFilters extends ApiQueryBase { |
| 41 | |
| 42 | public function __construct( |
| 43 | ApiQuery $query, |
| 44 | string $moduleName, |
| 45 | private readonly AbuseFilterPermissionManager $afPermManager, |
| 46 | private readonly FilterLookup $filterLookup |
| 47 | ) { |
| 48 | parent::__construct( $query, $moduleName, 'abf' ); |
| 49 | } |
| 50 | |
| 51 | /** |
| 52 | * @inheritDoc |
| 53 | */ |
| 54 | public function execute() { |
| 55 | $this->checkUserRightsAny( 'abusefilter-view' ); |
| 56 | |
| 57 | $params = $this->extractRequestParams(); |
| 58 | |
| 59 | $prop = array_fill_keys( $params['prop'], true ); |
| 60 | $fld_id = isset( $prop['id'] ); |
| 61 | $fld_desc = isset( $prop['description'] ); |
| 62 | $fld_pattern = isset( $prop['pattern'] ); |
| 63 | $fld_actions = isset( $prop['actions'] ); |
| 64 | $fld_hits = isset( $prop['hits'] ); |
| 65 | $fld_comments = isset( $prop['comments'] ); |
| 66 | $fld_user = isset( $prop['lasteditor'] ); |
| 67 | $fld_time = isset( $prop['lastedittime'] ); |
| 68 | $fld_status = isset( $prop['status'] ); |
| 69 | $fld_suppressed = isset( $prop['suppressed'] ); |
| 70 | $fld_private = isset( $prop['private'] ); |
| 71 | $fld_protected = isset( $prop['protected'] ); |
| 72 | |
| 73 | $result = $this->getResult(); |
| 74 | |
| 75 | // Use the SelectQueryBuilder from the FilterLookup service as a base so that we can construct |
| 76 | // Filter objects from the rows got in the query. |
| 77 | $this->getQueryBuilder()->queryInfo( |
| 78 | $this->filterLookup->getAbuseFilterQueryBuilder( $this->getDB() )->getQueryInfo() |
| 79 | ); |
| 80 | |
| 81 | $this->addOption( 'LIMIT', $params['limit'] + 1 ); |
| 82 | |
| 83 | $this->addWhereRange( 'af_id', $params['dir'], $params['startid'], $params['endid'] ); |
| 84 | |
| 85 | if ( $params['show'] !== null ) { |
| 86 | $show = array_fill_keys( $params['show'], true ); |
| 87 | |
| 88 | /* Check for conflicting parameters. */ |
| 89 | if ( ( isset( $show['enabled'] ) && isset( $show['!enabled'] ) ) |
| 90 | || ( isset( $show['deleted'] ) && isset( $show['!deleted'] ) ) |
| 91 | || ( isset( $show['private'] ) && isset( $show['!private'] ) ) |
| 92 | ) { |
| 93 | $this->dieWithError( 'apierror-show' ); |
| 94 | } |
| 95 | |
| 96 | $dbr = $this->getDb(); |
| 97 | $this->addWhereIf( $dbr->expr( 'af_enabled', '=', 0 ), isset( $show['!enabled'] ) ); |
| 98 | $this->addWhereIf( $dbr->expr( 'af_enabled', '!=', 0 ), isset( $show['enabled'] ) ); |
| 99 | $this->addWhereIf( $dbr->expr( 'af_deleted', '=', 0 ), isset( $show['!deleted'] ) ); |
| 100 | $this->addWhereIf( $dbr->expr( 'af_deleted', '!=', 0 ), isset( $show['deleted'] ) ); |
| 101 | $this->addWhereIf( |
| 102 | $dbr->bitAnd( 'af_hidden', Flags::FILTER_HIDDEN ) . ' = 0', |
| 103 | isset( $show['!private'] ) |
| 104 | ); |
| 105 | $this->addWhereIf( |
| 106 | $dbr->bitAnd( 'af_hidden', Flags::FILTER_HIDDEN ) . ' != 0', |
| 107 | isset( $show['private'] ) |
| 108 | ); |
| 109 | $this->addWhereIf( |
| 110 | $dbr->bitAnd( 'af_hidden', Flags::FILTER_USES_PROTECTED_VARS ) . ' != 0', |
| 111 | isset( $show['!protected'] ) |
| 112 | ); |
| 113 | $this->addWhereIf( |
| 114 | $dbr->bitAnd( 'af_hidden', Flags::FILTER_USES_PROTECTED_VARS ) . ' = 0', |
| 115 | isset( $show['!protected'] ) |
| 116 | ); |
| 117 | } |
| 118 | |
| 119 | $res = $this->select( __METHOD__ ); |
| 120 | |
| 121 | $showhidden = $this->afPermManager->canViewPrivateFilters( $this->getAuthority() ); |
| 122 | $showSuppressed = $this->afPermManager->canViewSuppressed( $this->getAuthority() ); |
| 123 | |
| 124 | $count = 0; |
| 125 | foreach ( $res as $row ) { |
| 126 | // FilterLookup::filterFromRow will override af_actions, so we need to define the callback to generate |
| 127 | // the data. We do not need to define anything other than the names because we only call |
| 128 | // AbstractFilter::getActionNames. |
| 129 | $actions = array_flip( explode( ',', $row->af_actions ) ); |
| 130 | $filter = $this->filterLookup->filterFromRow( $row, $actions ); |
| 131 | if ( ++$count > $params['limit'] ) { |
| 132 | // We've had enough |
| 133 | $this->setContinueEnumParameter( 'startid', $filter->getID() ); |
| 134 | break; |
| 135 | } |
| 136 | |
| 137 | // Hide the pattern and non-public comments from the API response if the user would not |
| 138 | // be able to open the editor for the filter. |
| 139 | $canViewExtendedDetailsAboutFilter = |
| 140 | ( !$filter->isHidden() || $showhidden ) |
| 141 | && ( !$filter->isSuppressed() || $showSuppressed ); |
| 142 | |
| 143 | if ( $filter->isProtected() && $canViewExtendedDetailsAboutFilter ) { |
| 144 | $canViewExtendedDetailsAboutFilter = $this->afPermManager |
| 145 | ->canViewProtectedVariablesInFilter( $this->getAuthority(), $filter ) |
| 146 | ->isGood(); |
| 147 | } |
| 148 | |
| 149 | $entry = []; |
| 150 | if ( $fld_id ) { |
| 151 | $entry['id'] = $filter->getID(); |
| 152 | } |
| 153 | if ( $fld_desc ) { |
| 154 | $entry['description'] = $filter->getName(); |
| 155 | } |
| 156 | if ( $fld_pattern && $canViewExtendedDetailsAboutFilter ) { |
| 157 | $entry['pattern'] = $filter->getRules(); |
| 158 | } |
| 159 | if ( $fld_actions ) { |
| 160 | $entry['actions'] = implode( ',', $filter->getActionsNames() ); |
| 161 | } |
| 162 | if ( $fld_hits ) { |
| 163 | $entry['hits'] = $filter->getHitCount(); |
| 164 | } |
| 165 | if ( $fld_comments && $canViewExtendedDetailsAboutFilter ) { |
| 166 | $entry['comments'] = $filter->getComments(); |
| 167 | } |
| 168 | if ( $fld_user ) { |
| 169 | $entry['lasteditor'] = $filter->getLastEditInfo()->getUserName(); |
| 170 | } |
| 171 | if ( $fld_time ) { |
| 172 | $entry['lastedittime'] = ConvertibleTimestamp::convert( |
| 173 | TS_ISO_8601, $filter->getLastEditInfo()->getTimestamp() |
| 174 | ); |
| 175 | } |
| 176 | if ( $fld_suppressed && $filter->isSuppressed() ) { |
| 177 | $entry['suppressed'] = ''; |
| 178 | } |
| 179 | |
| 180 | if ( $fld_private && $filter->isHidden() ) { |
| 181 | $entry['private'] = ''; |
| 182 | } |
| 183 | if ( $fld_protected && $filter->isProtected() ) { |
| 184 | $entry['protected'] = ''; |
| 185 | } |
| 186 | if ( $fld_status ) { |
| 187 | if ( $filter->isEnabled() ) { |
| 188 | $entry['enabled'] = ''; |
| 189 | } |
| 190 | if ( $filter->isDeleted() ) { |
| 191 | $entry['deleted'] = ''; |
| 192 | } |
| 193 | } |
| 194 | if ( $entry ) { |
| 195 | $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $entry ); |
| 196 | if ( !$fit ) { |
| 197 | $this->setContinueEnumParameter( 'startid', $filter->getID() ); |
| 198 | break; |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'filter' ); |
| 203 | } |
| 204 | |
| 205 | /** |
| 206 | * @codeCoverageIgnore Merely declarative |
| 207 | * @inheritDoc |
| 208 | */ |
| 209 | public function getAllowedParams() { |
| 210 | return [ |
| 211 | 'startid' => [ |
| 212 | ParamValidator::PARAM_TYPE => 'integer' |
| 213 | ], |
| 214 | 'endid' => [ |
| 215 | ParamValidator::PARAM_TYPE => 'integer', |
| 216 | ], |
| 217 | 'dir' => [ |
| 218 | ParamValidator::PARAM_TYPE => [ |
| 219 | 'older', |
| 220 | 'newer' |
| 221 | ], |
| 222 | ParamValidator::PARAM_DEFAULT => 'newer', |
| 223 | ApiBase::PARAM_HELP_MSG => 'api-help-param-direction', |
| 224 | ], |
| 225 | 'show' => [ |
| 226 | ParamValidator::PARAM_ISMULTI => true, |
| 227 | ParamValidator::PARAM_TYPE => [ |
| 228 | 'enabled', |
| 229 | '!enabled', |
| 230 | 'deleted', |
| 231 | '!deleted', |
| 232 | 'private', |
| 233 | '!private', |
| 234 | 'protected', |
| 235 | '!protected', |
| 236 | ], |
| 237 | ], |
| 238 | 'limit' => [ |
| 239 | ParamValidator::PARAM_DEFAULT => 10, |
| 240 | ParamValidator::PARAM_TYPE => 'limit', |
| 241 | IntegerDef::PARAM_MIN => 1, |
| 242 | IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1, |
| 243 | IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2 |
| 244 | ], |
| 245 | 'prop' => [ |
| 246 | ParamValidator::PARAM_DEFAULT => 'id|description|actions|status', |
| 247 | ParamValidator::PARAM_TYPE => [ |
| 248 | 'id', |
| 249 | 'description', |
| 250 | 'pattern', |
| 251 | 'actions', |
| 252 | 'hits', |
| 253 | 'comments', |
| 254 | 'lasteditor', |
| 255 | 'lastedittime', |
| 256 | 'status', |
| 257 | 'private', |
| 258 | 'protected', |
| 259 | ], |
| 260 | ParamValidator::PARAM_ISMULTI => true |
| 261 | ] |
| 262 | ]; |
| 263 | } |
| 264 | |
| 265 | /** |
| 266 | * @codeCoverageIgnore Merely declarative |
| 267 | * @inheritDoc |
| 268 | */ |
| 269 | protected function getExamplesMessages() { |
| 270 | return [ |
| 271 | 'action=query&list=abusefilters&abfshow=enabled|!private' |
| 272 | => 'apihelp-query+abusefilters-example-1', |
| 273 | 'action=query&list=abusefilters&abfprop=id|description|pattern' |
| 274 | => 'apihelp-query+abusefilters-example-2', |
| 275 | ]; |
| 276 | } |
| 277 | } |