Passed
Push — master ( c88656...e5f428 )
by MusikAnimal
08:08
created

AutomatedEditsHelper   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 174
Duplicated Lines 0 %

Test Coverage

Coverage 98.36%

Importance

Changes 0
Metric Value
eloc 56
dl 0
loc 174
ccs 60
cts 61
cp 0.9836
rs 10
c 0
b 0
f 0
wmc 22

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A isAutomated() 0 3 1
A getTool() 0 11 4
A mergeRules() 0 26 5
A getTools() 0 32 5
A isRevert() 0 9 3
A getRevertTools() 0 25 3
1
<?php
2
/**
3
 * This file contains only the AutomatedEditsHelper class.
4
 */
5
6
namespace AppBundle\Helper;
7
8
use Symfony\Component\DependencyInjection\ContainerInterface;
9
10
use Xtools\Project;
11
12
/**
13
 * Helper class for fetching semi-automated definitions.
14
 */
15
class AutomatedEditsHelper extends HelperBase
16
{
17
18
    /** @var string[] The list of tools that are considered reverting. */
19
    protected $revertTools = [];
20
21
    /** @var string[] The list of tool names and their regexes. */
22
    protected $tools = [];
23
24
    /**
25
     * AutomatedEditsHelper constructor.
26
     * @param ContainerInterface $container
27
     */
28 14
    public function __construct(ContainerInterface $container)
29
    {
30 14
        $this->container = $container;
0 ignored issues
show
Documentation Bug introduced by
$container is of type Symfony\Component\Depend...tion\ContainerInterface, but the property $container was declared to be of type Symfony\Component\DependencyInjection\Container. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
31 14
    }
32
33
    /**
34
     * Get the tool that matched the given edit summary.
35
     * This only works for tools defined with regular expressions, not tags.
36
     * @param string $summary Edit summary
37
     * @param Project $project
38
     * @return string|bool Tool entry including key for 'name', or false if nothing was found
39
     */
40 9
    public function getTool($summary, Project $project)
41
    {
42 9
        foreach ($this->getTools($project) as $tool => $values) {
43 9
            if (isset($values['regex']) && preg_match('/'.$values['regex'].'/', $summary)) {
44 9
                return array_merge([
45 9
                    'name' => $tool,
46 9
                ], $values);
47
            }
48
        }
49
50 7
        return false;
51
    }
52
53
    /**
54
     * Was the edit (semi-)automated, based on the edit summary?
55
     * This only works for tools defined with regular expressions, not tags.
56
     * @param string $summary Edit summary
57
     * @param Project $project
58
     * @return bool
59
     */
60 1
    public function isAutomated($summary, Project $project)
61
    {
62 1
        return (bool)$this->getTool($summary, $project);
63
    }
64
65
    /**
66
     * Get list of automated tools and their associated info for the given project.
67
     * This defaults to the 'default_project' if entries for the given project are not found.
68
     * @param Project $project
69
     * @return array Each tool with the tool name as the key, and 'link', 'regex' and/or 'tag' as the subarray keys.
70
     */
71 14
    public function getTools(Project $project)
72
    {
73 14
        $projectDomain = $project->getDomain();
74
75 14
        if (isset($this->tools[$projectDomain])) {
76 8
            return $this->tools[$projectDomain];
77
        }
78
79
        // Load the semi-automated edit types.
80 14
        $tools = $this->container->getParameter('automated_tools');
81
82
        // Default to default project (e.g. en.wikipedia.org) if wiki not configured
83 14
        if (isset($tools[$projectDomain])) {
84 10
            $localRules = $tools[$projectDomain];
85 4
        } elseif (isset($tools[$this->container->getParameter('default_project')])) {
86 4
            $localRules = $tools[$this->container->getParameter('default_project')];
87
        } else {
88
            $localRules = [];
89
        }
90
91 14
        $langRules = isset($tools[$project->getLang()])
92 1
            ? $tools[$project->getLang()]
93 14
            : [];
94
95
        // Per-wiki rules have priority, followed by language-specific and global.
96 14
        $globalWithLangRules = $this->mergeRules($tools['global'], $langRules);
97 14
        $this->tools[$projectDomain] = $this->mergeRules(
98 14
            $globalWithLangRules,
99 14
            $localRules
100
        );
101
102 14
        return $this->tools[$projectDomain];
103
    }
104
105
    /**
106
     * Merges the given rule sets, giving priority to the local set. Regex is concatenated, not overridden.
107
     * @param array $globalRules The global rule set.
108
     * @param array $localRules The rule set for the local wiki.
109
     * @return array Merged rules.
110
     */
111 14
    private function mergeRules($globalRules, $localRules)
112
    {
113
        // Initial set, including just the global rules.
114 14
        $tools = $globalRules;
115
116
        // Loop through local rules and override/merge as necessary.
117 14
        foreach ($localRules as $tool => $rules) {
118 14
            $newRules = $rules;
119
120 14
            if (isset($globalRules[$tool])) {
121
                // Order within array_merge is important, so that local rules get priority.
122 14
                $newRules = array_merge($globalRules[$tool], $rules);
123
            }
124
125
            // Regex should be merged, not overridden.
126 14
            if (isset($rules['regex']) && isset($globalRules[$tool]['regex'])) {
127 1
                $newRules['regex'] = implode('|', [
128 1
                    $rules['regex'],
129 1
                    $globalRules[$tool]['regex']
130
                ]);
131
            }
132
133 14
            $tools[$tool] = $newRules;
134
        }
135
136 14
        return $tools;
137
    }
138
139
    /**
140
     * Get only tools that are used to revert edits.
141
     * Revert detection happens only by testing against a regular expression, and not by checking tags.
142
     * @param Project $project
143
     * @return string[] Each tool with the tool name as the key,
144
     *   and 'link' and 'regex' as the subarray keys.
145
     */
146 7
    public function getRevertTools(Project $project)
147
    {
148 7
        $projectDomain = $project->getDomain();
149
150 7
        if (isset($this->revertTools[$projectDomain])) {
151 7
            return $this->revertTools[$projectDomain];
152
        }
153
154 7
        $revertEntries = array_filter(
155 7
            $this->getTools($project),
156 7
            function ($tool) {
157 7
                return isset($tool['revert']);
158 7
            }
159
        );
160
161
        // If 'revert' is set to `true`, then use 'regex' as the regular expression,
162
        //  otherwise 'revert' is assumed to be the regex string.
163 7
        $this->revertTools[$projectDomain] = array_map(function ($revertTool) {
164
            return [
165 7
                'link' => $revertTool['link'],
166 7
                'regex' => $revertTool['revert'] === true ? $revertTool['regex'] : $revertTool['revert'],
167
            ];
168 7
        }, $revertEntries);
169
170 7
        return $this->revertTools[$projectDomain];
171
    }
172
173
    /**
174
     * Was the edit a revert, based on the edit summary?
175
     * This only works for tools defined with regular expressions, not tags.
176
     * @param string $summary Edit summary
177
     * @param Project $project
178
     * @return bool
179
     */
180 7
    public function isRevert($summary, Project $project)
181
    {
182 7
        foreach ($this->getRevertTools($project) as $tool => $values) {
183 7
            if (preg_match('/'.$values['regex'].'/', $summary)) {
184 7
                return true;
185
            }
186
        }
187
188 7
        return false;
189
    }
190
}
191