1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
<?php
namespace MediaWiki\Extension\AbuseFilter;
use LogicException;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesRegistry;
use MediaWiki\Extension\AbuseFilter\Filter\Filter;
use MediaWiki\Extension\AbuseFilter\Filter\Flags;
use MediaWiki\Extension\AbuseFilter\Filter\LastEditInfo;
use MediaWiki\Extension\AbuseFilter\Filter\MutableFilter;
use MediaWiki\Extension\AbuseFilter\Filter\Specs;
use MediaWiki\Json\FormatJson;
/**
* This class allows encoding filters to (and decoding from) a string format that can be used
* to export them to another wiki.
*
* @internal
* @note Callers should NOT rely on the output format, as it may vary
*/
class FilterImporter {
public const SERVICE_NAME = 'AbuseFilterFilterImporter';
public const CONSTRUCTOR_OPTIONS = [
'AbuseFilterValidGroups',
'AbuseFilterIsCentral',
];
private const TEMPLATE_KEYS = [
'rules',
'name',
'comments',
'group',
'actions',
'enabled',
'deleted',
'privacylevel',
'global'
];
/** @var ServiceOptions */
private $options;
/** @var ConsequencesRegistry */
private $consequencesRegistry;
/**
* @param ServiceOptions $options
* @param ConsequencesRegistry $consequencesRegistry
*/
public function __construct( ServiceOptions $options, ConsequencesRegistry $consequencesRegistry ) {
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
$this->options = $options;
$this->consequencesRegistry = $consequencesRegistry;
}
/**
* @param Filter $filter
* @param array $actions
* @return string
*/
public function encodeData( Filter $filter, array $actions ): string {
$data = [
'rules' => $filter->getRules(),
'name' => $filter->getName(),
'comments' => $filter->getComments(),
'group' => $filter->getGroup(),
'actions' => $filter->getActions(),
'enabled' => $filter->isEnabled(),
'deleted' => $filter->isDeleted(),
'privacylevel' => $filter->getPrivacyLevel(),
'global' => $filter->isGlobal()
];
// @codeCoverageIgnoreStart
if ( array_keys( $data ) !== self::TEMPLATE_KEYS ) {
// Sanity
throw new LogicException( 'Bad keys' );
}
// @codeCoverageIgnoreEnd
return FormatJson::encode( [ 'data' => $data, 'actions' => $actions ] );
}
/**
* @param string $rawData
* @return Filter
* @throws InvalidImportDataException
*/
public function decodeData( string $rawData ): Filter {
$validGroups = $this->options->get( 'AbuseFilterValidGroups' );
$globalFiltersEnabled = $this->options->get( 'AbuseFilterIsCentral' );
$data = FormatJson::decode( $rawData );
if ( !$this->isValidImportData( $data ) ) {
throw new InvalidImportDataException( $rawData );
}
[ 'data' => $filterData, 'actions' => $actions ] = wfObjectToArray( $data );
return new MutableFilter(
new Specs(
$filterData['rules'],
$filterData['comments'],
$filterData['name'],
array_keys( $actions ),
// Keep the group only if it exists on this wiki
in_array( $filterData['group'], $validGroups, true ) ? $filterData['group'] : 'default'
),
new Flags(
(bool)$filterData['enabled'],
(bool)$filterData['deleted'],
(int)$filterData['privacylevel'],
// And also make it global only if global filters are enabled here
$filterData['global'] && $globalFiltersEnabled
),
$actions,
new LastEditInfo(
0,
'',
''
)
);
}
/**
* Note: this doesn't check if parameters are valid etc., but only if the shape of the object is right.
*
* @param mixed $data Already decoded
* @return bool
*/
private function isValidImportData( $data ): bool {
if ( !is_object( $data ) ) {
return false;
}
$arr = get_object_vars( $data );
$expectedKeys = [ 'data' => true, 'actions' => true ];
if ( count( $arr ) !== count( $expectedKeys ) || array_diff_key( $arr, $expectedKeys ) ) {
return false;
}
if ( !is_object( $arr['data'] ) || !( is_object( $arr['actions'] ) || $arr['actions'] === [] ) ) {
return false;
}
if ( array_keys( get_object_vars( $arr['data'] ) ) !== self::TEMPLATE_KEYS ) {
return false;
}
$allActions = $this->consequencesRegistry->getAllActionNames();
foreach ( $arr['actions'] as $action => $params ) {
if ( !in_array( $action, $allActions, true ) || !is_array( $params ) ) {
return false;
}
}
return true;
}
}
|