Passed
Push — master ( 44236a...cf19b6 )
by MusikAnimal
04:38
created

AutomatedEditsHelper::isAutomated()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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);
0 ignored issues
show
Bug introduced by
$values of type string is incompatible with the type null|array expected by parameter $array2 of array_merge(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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