Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.77% covered (success)
96.77%
60 / 62
84.62% covered (warning)
84.62%
11 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
WikitextConversions
96.77% covered (success)
96.77%
60 / 62
84.62% covered (warning)
84.62%
11 / 13
28
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
9
 swapHeading
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isTemplateGood
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 hasGoodTemplates
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isTemplateBad
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isCategoryBad
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isObsoleteTemplate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 swapTemplate
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getTemplateParameters
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
5.01
 getRequiredTemplateParameters
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
4.02
 lowercasePageName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 normalizePageName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 removeNamespace
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace FileImporter\Data;
4
5/**
6 * Class holding validation and cleanup rules for the file description wikitext. This class is not
7 * aware of the source of these rules. They can be extracted from CommonsHelper2-compatible
8 * configuration files or other, yet to be defined sources.
9 *
10 * @license GPL-2.0-or-later
11 */
12class WikitextConversions {
13
14    public const REQUIRED_TEMPLATES = 'requiredTemplates';
15    public const FORBIDDEN_TEMPLATES = 'forbiddenTemplates';
16    public const OBSOLETE_TEMPLATES = 'obsoleteTemplates';
17    public const TEMPLATE_TRANSFORMATIONS = 'templateTransformations';
18    public const FORBIDDEN_CATEGORIES = 'forbiddenCategories';
19    public const HEADING_REPLACEMENTS = 'headingReplacements';
20
21    /** @var string[] */
22    private $headingReplacements = [];
23    /** @var true[] A string => true map for performance reasons */
24    private $goodTemplates = [];
25    /** @var true[] A string => true map for performance reasons */
26    private $badTemplates = [];
27    /** @var true[] A string => true map for performance reasons */
28    private $badCategories = [];
29    /** @var true[] A string => true map for performance reasons */
30    private $obsoleteTemplates = [];
31    /** @var array[] */
32    private $transferTemplates = [];
33
34    /**
35     * @param array[] $conversions A nested array structure in the following format:
36     * [
37     *     self::REQUIRED_TEMPLATES => string[] List of case-insensitive page names without
38     *         namespace prefix
39     *     self::FORBIDDEN_TEMPLATES => string[] List of case-insensitive page names without
40     *         namespace prefix
41     *     self::OBSOLETE_TEMPLATES => string[] List of case-insensitive page names without
42     *         namespace prefix
43     *     self::TEMPLATE_TRANSFORMATIONS => array[] List mapping source template names without
44     *         namespace prefix to replacement rules in the following format:
45     *         string $sourceTemplate => [
46     *             'targetTemplate' => string
47     *             'parameters' => [
48     *                 string $targetParameter => [
49     *                     'addIfMissing' => bool
50     *                     'addLanguageTemplate' => bool
51     *                     'sourceParameters' => string[]|string
52     *                 ],
53     *                 …
54     *             ]
55     *         ],
56     *         …
57     *     self::FORBIDDEN_CATEGORIES => string[] List of case-insensitive page names without
58     *         namespace prefix
59     *     self::HEADING_REPLACEMENTS => string[] Straight 1:1 mapping of source to target headings
60     *         without any `==` syntax
61     * ]
62     *
63     * @throws \InvalidArgumentException if the input format misses expected fields. This should be
64     *  unreachable, as the only provider is the CommonsHelperConfigParser.
65     */
66    public function __construct( array $conversions ) {
67        $goodTemplates = $conversions[self::REQUIRED_TEMPLATES] ?? [];
68        $badTemplates = $conversions[self::FORBIDDEN_TEMPLATES] ?? [];
69        $obsoleteTemplates = $conversions[self::OBSOLETE_TEMPLATES] ?? [];
70        $transferTemplates = $conversions[self::TEMPLATE_TRANSFORMATIONS] ?? [];
71        $badCategories = $conversions[self::FORBIDDEN_CATEGORIES] ?? [];
72        $this->headingReplacements = $conversions[self::HEADING_REPLACEMENTS] ?? [];
73
74        foreach ( $goodTemplates as $pageName ) {
75            $this->goodTemplates[$this->lowercasePageName( $pageName )] = true;
76        }
77
78        foreach ( $badTemplates as $pageName ) {
79            $this->badTemplates[$this->lowercasePageName( $pageName )] = true;
80        }
81
82        foreach ( $badCategories as $pageName ) {
83            $this->badCategories[$this->lowercasePageName( $pageName )] = true;
84        }
85
86        foreach ( $obsoleteTemplates as $pageName ) {
87            $this->obsoleteTemplates[$this->lowercasePageName( $pageName )] = true;
88        }
89
90        foreach ( $transferTemplates as $from => $to ) {
91            // TODO: Accepts strings for backwards-compatibility; remove if not needed any more
92            if ( is_string( $to ) ) {
93                $to = [ 'targetTemplate' => $to, 'parameters' => [] ];
94            }
95
96            if ( empty( $to['targetTemplate'] ) ) {
97                throw new \InvalidArgumentException( "$from transfer rule misses targetTemplate" );
98            }
99            if ( !isset( $to['parameters'] ) ) {
100                throw new \InvalidArgumentException( "$from transfer rule misses parameters" );
101            }
102
103            $from = $this->lowercasePageName( $from );
104            $to['targetTemplate'] = $this->normalizePageName( $to['targetTemplate'] );
105            $this->transferTemplates[$from] = $to;
106        }
107    }
108
109    public function swapHeading( string $heading ): string {
110        return $this->headingReplacements[$heading] ?? $heading;
111    }
112
113    /**
114     * @param string $pageName Case-insensitive page name. The namespace is ignored. Titles like
115     *  "Template:A" and "User:A" are considered equal.
116     */
117    public function isTemplateGood( string $pageName ): bool {
118        $pageName = $this->removeNamespace( $pageName );
119        return array_key_exists( $this->lowercasePageName( $pageName ), $this->goodTemplates );
120    }
121
122    public function hasGoodTemplates(): bool {
123        return $this->goodTemplates !== [];
124    }
125
126    /**
127     * @param string $pageName Case-insensitive page name. The namespace is ignored. Titles like
128     *  "Template:A" and "User:A" are considered equal.
129     */
130    public function isTemplateBad( string $pageName ): bool {
131        $pageName = $this->removeNamespace( $pageName );
132        return array_key_exists( $this->lowercasePageName( $pageName ), $this->badTemplates );
133    }
134
135    /**
136     * @param string $pageName Case-insensitive page name. The namespace is ignored. Titles like
137     *  "Category:A" and "User:A" are considered equal.
138     */
139    public function isCategoryBad( string $pageName ): bool {
140        $pageName = $this->removeNamespace( $pageName );
141        return array_key_exists( $this->lowercasePageName( $pageName ), $this->badCategories );
142    }
143
144    /**
145     * @param string $pageName Case-insensitive page name. Prefixes are significant.
146     */
147    public function isObsoleteTemplate( string $pageName ): bool {
148        return array_key_exists( $this->lowercasePageName( $pageName ), $this->obsoleteTemplates );
149    }
150
151    /**
152     * @param string $templateName Case-insensitive page name. Prefixes are significant.
153     *
154     * @return string|false
155     */
156    public function swapTemplate( string $templateName ) {
157        $templateName = $this->lowercasePageName( $templateName );
158        return $this->transferTemplates[$templateName]['targetTemplate'] ?? false;
159    }
160
161    /**
162     * @param string $templateName Case-insensitive page name. Prefixes are significant.
163     *
164     * @return array[] Array mapping source to target parameters:
165     * [
166     *     string $source => [
167     *          'target' => string Target parameter name
168     *          'addLanguageTemplate' => bool Whether or not to add a template like {{de|…}}
169     *     ],
170     *     …
171     * ]
172     */
173    public function getTemplateParameters( string $templateName ): array {
174        $templateName = $this->lowercasePageName( $templateName );
175        if ( !isset( $this->transferTemplates[$templateName] ) ) {
176            return [];
177        }
178
179        $replacements = [];
180        foreach ( $this->transferTemplates[$templateName]['parameters'] as $targetParameter => $opt ) {
181            $sourceParameters = (array)( $opt['sourceParameters'] ?? [] );
182            $addLanguageTemplate = (bool)( $opt['addLanguageTemplate'] ?? false );
183
184            foreach ( $sourceParameters as $sourceParameter ) {
185                if ( $sourceParameter !== '' ) {
186                    $replacements[$sourceParameter] = [
187                        'target' => $targetParameter,
188                        'addLanguageTemplate' => $addLanguageTemplate
189                    ];
190                }
191            }
192        }
193        return $replacements;
194    }
195
196    /**
197     * @param string $templateName Case-insensitive page name. Prefixes are significant.
198     *
199     * @return string[] Array mapping required target parameter names to static string values.
200     */
201    public function getRequiredTemplateParameters( string $templateName ): array {
202        $templateName = $this->lowercasePageName( $templateName );
203        if ( !isset( $this->transferTemplates[$templateName] ) ) {
204            return [];
205        }
206
207        $additions = [];
208        foreach ( $this->transferTemplates[$templateName]['parameters'] as $targetParameter => $opt ) {
209            $addIfMissing = $opt['addIfMissing'] ?? false;
210            if ( $addIfMissing ) {
211                $additions[$targetParameter] = $opt['value'] ?? '';
212            }
213        }
214        return $additions;
215    }
216
217    private function lowercasePageName( string $pageName ): string {
218        return mb_convert_case( $this->normalizePageName( $pageName ), MB_CASE_LOWER );
219    }
220
221    private function normalizePageName( string $pageName ): string {
222        return trim( str_replace( '_', ' ', $pageName ) );
223    }
224
225    private function removeNamespace( string $title ): string {
226        $splitTitle = explode( ':', $title, 2 );
227        return end( $splitTitle );
228    }
229
230}