Completed
Push — master ( 1fa736...d80d19 )
by Sam
03:03
created

ArticleInfoController::resultAction()   C

Complexity

Conditions 7
Paths 18

Size

Total Lines 78
Code Lines 50

Duplication

Lines 4
Ratio 5.13 %

Importance

Changes 0
Metric Value
dl 4
loc 78
rs 6.5702
c 0
b 0
f 0
cc 7
eloc 50
nc 18
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file contains only the ArticleInfoController class.
4
 */
5
6
namespace AppBundle\Controller;
7
8
use AppBundle\Helper\AutomatedEditsHelper;
9
use AppBundle\Helper\PageviewsHelper;
10
use Doctrine\DBAL\Connection;
11
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
12
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
13
use Symfony\Component\HttpFoundation\Request;
14
use Symfony\Component\DependencyInjection\ContainerInterface;
15
use Symfony\Component\HttpFoundation\Response;
16
use Xtools\ProjectRepository;
17
use Xtools\Page;
18
use Xtools\PagesRepository;
19
use Xtools\Edit;
20
use DateTime;
21
22
/**
23
 * This controller serves the search form and results for the ArticleInfo tool
24
 */
25
class ArticleInfoController extends Controller
26
{
27
    /** @var mixed[] Information about the page in question. */
28
    private $pageInfo;
29
    /** @var Edit[] All edits of the page. */
30
    private $pageHistory;
0 ignored issues
show
Unused Code introduced by
The property $pageHistory is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
31
    /** @var ProjectRepository Shared Project repository for use of getting table names, etc. */
32
    private $projectRepo;
33
    /** @var string Database name, for us of getting table names, etc. */
34
    private $dbName;
35
    /** @var Connection The projects' database connection. */
36
    protected $conn;
37
    /** @var AutomatedEditsHelper The semi-automated edits helper. */
38
    protected $aeh;
39
    /** @var PageviewsHelper The page-views helper. */
40
    protected $ph;
41
42
    /**
43
     * Get the tool's shortname.
44
     * @return string
45
     */
46
    public function getToolShortname()
47
    {
48
        return 'articleinfo';
49
    }
50
51
    /**
52
     * Override method to call ArticleInfoController::containerInitialized() when container set.
53
     * @param ContainerInterface|null $container A ContainerInterface instance or null
54
     */
55
    public function setContainer(ContainerInterface $container = null)
56
    {
57
        parent::setContainer($container);
58
        $this->containerInitialized();
59
    }
60
61
    /**
62
     * Perform some operations after controller initialized and container set.
63
     */
64
    private function containerInitialized()
65
    {
66
        $this->conn = $this->getDoctrine()->getManager('replicas')->getConnection();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\Common\Persistence\ObjectManager as the method getConnection() does only exist in the following implementations of said interface: Doctrine\ORM\Decorator\EntityManagerDecorator, Doctrine\ORM\EntityManager.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
67
        $this->ph = $this->get('app.pageviews_helper');
68
        $this->aeh = $this->get('app.automated_edits_helper');
69
    }
70
71
    /**
72
     * The search form.
73
     * @Route("/articleinfo", name="articleinfo")
74
     * @Route("/articleinfo", name="articleInfo")
75
     * @Route("/articleinfo/", name="articleInfoSlash")
76
     * @Route("/articleinfo/index.php", name="articleInfoIndexPhp")
77
     * @Route("/articleinfo/{project}", name="ArticleInfoProject")
78
     * @param Request $request The HTTP request.
79
     * @return Response
80
     */
81
    public function indexAction(Request $request)
82
    {
83
        $projectQuery = $request->query->get('project');
84
        $article = $request->query->get('article');
85
86
        if ($projectQuery != '' && $article != '') {
87
            return $this->redirectToRoute('ArticleInfoResult', [ 'project'=>$projectQuery, 'article' => $article ]);
88
        } elseif ($article != '') {
89
            return $this->redirectToRoute('ArticleInfoProject', [ 'project'=>$projectQuery ]);
90
        }
91
92
        if ($projectQuery == '') {
93
            $projectQuery = $this->container->getParameter('default_project');
94
        }
95
96
        $project = ProjectRepository::getProject($projectQuery, $this->container);
97
98
        return $this->render('articleInfo/index.html.twig', [
99
            'xtPage' => 'articleinfo',
100
            'xtPageTitle' => 'tool-articleinfo',
101
            'xtSubtitle' => 'tool-articleinfo-desc',
102
            'project' => $project,
103
        ]);
104
    }
105
106
    /**
107
     * Display the results.
108
     * @Route("/articleinfo/{project}/{article}", name="ArticleInfoResult", requirements={"article"=".+"})
109
     * @param Request $request The HTTP request.
110
     * @return Response
111
     */
112
    public function resultAction(Request $request)
113
    {
114
        $projectQuery = $request->attributes->get('project');
115
        $project = ProjectRepository::getProject($projectQuery, $this->container);
116
        $this->projectRepo = $project->getRepository();
117 View Code Duplication
        if (!$project->exists()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
118
            $this->addFlash('notice', ['invalid-project', $projectQuery]);
0 ignored issues
show
Documentation introduced by
array('invalid-project', $projectQuery) is of type array<integer,*,{"0":"string","1":"*"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
119
            return $this->redirectToRoute('articleInfo');
120
        }
121
        $this->dbName = $project->getDatabaseName();
122
123
        $pageQuery = $request->attributes->get('article');
124
        $page = new Page($project, $pageQuery);
125
        $pageRepo = new PagesRepository();
126
        $pageRepo->setContainer($this->container);
1 ignored issue
show
Compatibility introduced by
$this->container of type object<Symfony\Component...ion\ContainerInterface> is not a sub-type of object<Symfony\Component...ncyInjection\Container>. It seems like you assume a concrete implementation of the interface Symfony\Component\Depend...tion\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
127
        $page->setRepository($pageRepo);
128
129
        if (!$page->exists()) {
130
            $this->addFlash('notice', ['no-exist', $pageQuery]);
0 ignored issues
show
Documentation introduced by
array('no-exist', $pageQuery) is of type array<integer,*,{"0":"string","1":"*"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
131
            return $this->redirectToRoute('articleInfo');
132
        }
133
134
        $this->pageInfo = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('project' => $proj...=> $project->getLang()) of type array<string,object<Xtoo...age>","lang":"string"}> is incompatible with the declared type array<integer,*> of property $pageInfo.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
135
            'project' => $project,
136
            'page' => $page,
137
            'lang' => $project->getLang(),
138
        ];
139
140
        // TODO: Adapted from legacy code; may be used to indicate how many dead ext links there are
141
        // if ( isset( $basicInfo->extlinks ) ){
1 ignored issue
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
142
        //     foreach ( $basicInfo->extlinks as $i => $link ){
1 ignored issue
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
143
        //         $this->extLinks[] = array("link" => $link->{'*'}, "status" => "unchecked" );
1 ignored issue
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
144
        //     }
145
        // }
146
147
        $this->pageInfo = array_merge($this->pageInfo, $this->parseHistory($page));
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge($this->pageI...s->parseHistory($page)) of type array is incompatible with the declared type array<integer,*> of property $pageInfo.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
148
        $this->pageInfo['bots'] = $this->getBotData();
149
        $this->pageInfo['general']['bot_count'] = count($this->pageInfo['bots']);
150
        $this->pageInfo['general']['top_ten_count'] = $this->getTopTenCount();
151
        $this->pageInfo['general']['top_ten_percentage'] = round(
152
            ($this->pageInfo['general']['top_ten_count'] / $page->getNumRevisions()) * 100,
153
            1
154
        );
155
        $this->pageInfo = array_merge($this->pageInfo, $page->countLinksAndRedirects());
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge($this->pageI...untLinksAndRedirects()) of type array is incompatible with the declared type array<integer,*> of property $pageInfo.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
156
        $this->pageInfo['general']['pageviews_offset'] = 60;
157
        $this->pageInfo['general']['pageviews'] = $this->ph->sumLastDays(
158
            $this->pageInfo['project']->getDomain(),
159
            $this->pageInfo['page']->getTitle(),
160
            $this->pageInfo['general']['pageviews_offset']
161
        );
162
163
        $assessments = $page->getAssessments();
164
        if ($assessments) {
165
            $this->pageInfo['assessments'] = $assessments;
166
        }
167
        $this->setLogsEvents();
168
169
        $bugs = $page->getErrors();
170
        if (!empty($bugs)) {
171
            $this->pageInfo['bugs'] = $bugs;
172
        }
173
174
        $this->pageInfo['xtPage'] = 'articleinfo';
175
        $this->pageInfo['xtTitle'] = $page->getTitle();
176
        $this->pageInfo['editorlimit'] = $request->query->get('editorlimit', 20);
177
178
        // Output the relevant format template.
179
        $format = $request->query->get('format', 'html');
180
        if ($format == '') {
181
            // The default above doesn't work when the 'format' parameter is blank.
182
            $format = 'html';
183
        }
184
        $response = $this->render("articleInfo/result.$format.twig", $this->pageInfo);
185
        if ($format == 'wikitext') {
186
            $response->headers->set('Content-Type', 'text/plain');
187
        }
188
        return $response;
189
    }
190
191
    /**
192
     * Get info about bots that edited the page
193
     * This also sets $this->pageInfo['bot_revision_count'] and $this->pageInfo['bot_percentage']
194
     * @return array Associative array containing the bot's username, edit count to the page
195
     *               and whether or not they are currently a bot
196
     */
197
    private function getBotData()
198
    {
199
        $userGroupsTable = $this->projectRepo->getTableName($this->dbName, 'user_groups');
200
        $userFromerGroupsTable = $this->projectRepo->getTableName($this->dbName, 'user_former_groups');
201
        $query = "SELECT COUNT(rev_user_text) AS count, rev_user_text AS username, ug_group AS current
202
                  FROM " . $this->projectRepo->getTableName($this->dbName, 'revision') . "
203
                  LEFT JOIN $userGroupsTable ON rev_user = ug_user
204
                  LEFT JOIN $userFromerGroupsTable ON rev_user = ufg_user
205
                  WHERE rev_page = " . $this->pageInfo['page']->getId() . " AND (ug_group = 'bot' OR ufg_group = 'bot')
206
                  GROUP BY rev_user_text";
207
        $res = $this->conn->query($query)->fetchAll();
208
209
        // Parse the botedits
210
        $bots = [];
211
        $sum = 0;
212
        foreach ($res as $bot) {
213
            $bots[$bot['username']] = [
214
                'count' => (int) $bot['count'],
215
                'current' => $bot['current'] === 'bot'
216
            ];
217
            $sum += $bot['count'];
218
        }
219
220
        uasort($bots, function ($a, $b) {
221
            return $b['count'] - $a['count'];
222
        });
223
224
        $this->pageInfo['general']['bot_revision_count'] = $sum;
225
        $this->pageInfo['general']['bot_percentage'] = round(
226
            ($sum / $this->pageInfo['page']->getNumRevisions()) * 100,
227
            1
228
        );
229
230
        return $bots;
231
    }
232
233
    /**
234
     * Get the number of edits made to the page by the top 10% of editors
235
     * This is ran *after* parseHistory() since we need the grand totals first.
236
     * Various stats are also set for each editor in $this->pageInfo['editors']
237
     *   and top ten editors are stored in $this->pageInfo['general']['top_ten']
238
     *   to be used in the charts
239
     * @return integer Number of edits
240
     */
241
    private function getTopTenCount()
242
    {
243
        $topTenCount = $counter = 0;
244
        $topTenEditors = [];
245
246
        foreach ($this->pageInfo['editors'] as $editor => $info) {
247
            // Count how many users are in the top 10% by number of edits
248
            if ($counter < 10) {
249
                $topTenCount += $info['all'];
250
                $counter++;
251
252
                // To be used in the Top Ten charts
253
                $topTenEditors[] = [
254
                    'label' => $editor,
255
                    'value' => $info['all'],
256
                    'percentage' => (
257
                        100 * ($info['all'] / $this->pageInfo['page']->getNumRevisions())
258
                    )
259
                ];
260
            }
261
262
            // Compute the percentage of minor edits the user made
263
            $this->pageInfo['editors'][$editor]['minor_percentage'] = $info['all']
264
                ? ($info['minor'] / $info['all']) * 100
265
                : 0;
266
267
            if ($info['all'] > 1) {
268
                // Number of seconds between first and last edit
269
                $secs = $info['last']->getTimestamp() - $info['first']->getTimestamp();
270
271
                // Average time between edits (in days)
272
                $this->pageInfo['editors'][$editor]['atbe'] = $secs / ( 60 * 60 * 24 );
273
            }
274
275
            if (count($info['sizes'])) {
276
                // Average Total KB divided by number of stored sizes (user's edit count to this page)
277
                $this->pageInfo['editors'][$editor]['size'] = array_sum($info['sizes']) / count($info['sizes']);
278
            } else {
279
                $this->pageInfo['editors'][$editor]['size'] = 0;
280
            }
281
        }
282
283
        $this->pageInfo['topTenEditors'] = $topTenEditors;
284
285
        // First sort editors array by the amount of text they added
286
        $topTenEditorsByAdded = $this->pageInfo['editors'];
287
        uasort($topTenEditorsByAdded, function ($a, $b) {
288
            if ($a['added'] === $b['added']) {
289
                return 0;
290
            }
291
            return $a['added'] > $b['added'] ? -1 : 1;
292
        });
293
294
        // Then build a new array of top 10 editors by added text,
295
        //   in the data structure needed for the chart
296
        $this->pageInfo['topTenEditorsByAdded'] = array_map(function ($editor) {
297
            $added = $this->pageInfo['editors'][$editor]['added'];
298
            return [
299
                'label' => $editor,
300
                'value' => $added,
301
                'percentage' => (
302
                    100 * ($added / $this->pageInfo['general']['added'])
303
                )
304
            ];
305
        }, array_keys(array_slice($topTenEditorsByAdded, 0, 10)));
306
307
        return $topTenCount;
308
    }
309
310
    /**
311
     * Query for log events during each year of the article's history,
312
     *   and set the results in $this->pageInfo['year_count']
313
     */
314
    private function setLogsEvents()
315
    {
316
        $loggingTable = $this->projectRepo->getTableName($this->dbName, 'logging', 'logindex');
317
        $title = str_replace(' ', '_', $this->pageInfo['page']->getTitle());
318
        $query = "SELECT log_action, log_type, log_timestamp AS timestamp
319
                  FROM $loggingTable
320
                  WHERE log_namespace = '" . $this->pageInfo['page']->getNamespace() . "'
321
                  AND log_title = '$title' AND log_timestamp > 1
322
                  AND log_type IN ('delete', 'move', 'protect', 'stable')";
323
        $events = $this->conn->query($query)->fetchAll();
324
325
        foreach ($events as $event) {
326
            $time = strtotime($event['timestamp']);
327
            $year = date('Y', $time);
328
            if (isset($this->pageInfo['year_count'][$year])) {
329
                $yearEvents = $this->pageInfo['year_count'][$year]['events'];
330
331
                // Convert log type value to i18n key
332
                switch ($event['log_type']) {
333
                    case 'protect':
334
                        $action = 'protections';
335
                        break;
336
                    case 'delete':
337
                        $action = 'deletions';
338
                        break;
339
                    case 'move':
340
                        $action = 'moves';
341
                        break;
342
                    // count pending-changes protections along with normal protections
343
                    case 'stable':
344
                        $action = 'protections';
345
                        break;
346
                }
347
348
                if (empty($yearEvents[$action])) {
349
                    $yearEvents[$action] = 1;
0 ignored issues
show
Bug introduced by
The variable $action does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
350
                } else {
351
                    $yearEvents[$action]++;
352
                }
353
354
                $this->pageInfo['year_count'][$year]['events'] = $yearEvents;
355
            }
356
        }
357
    }
358
359
    /**
360
     * Parse the revision history. This also sets some $this->pageInfo vars
361
     *   like 'firstEdit' and 'lastEdit'
362
     * @param Page $page Page to parse
363
     * @return array Associative "master" array of metadata about the page
364
     */
365
    private function parseHistory(Page $page)
366
    {
367
        $revStmt = $page->getRevisionsStmt();
368
        $revCount = 0;
369
370
        /** @var string[] Master array containing all the data we need */
371
        $data = [
372
            'general' => [
373
                'max_add' => null, // Edit
374
                'max_del' => null, // Edit
375
                'editor_count' => 0,
376
                'anon_count' => 0,
377
                'minor_count' => 0,
378
                'count_history' => ['day' => 0, 'week' => 0, 'month' => 0, 'year' => 0],
379
                'current_size' => null,
380
                'textshares' => [],
381
                'textshare_total' => 0,
382
                'automated_count' => 0,
383
                'revert_count' => 0,
384
                'added' => 0,
385
            ],
386
            'max_edits_per_month' => 0, // for bar chart in "Month counts" section
387
            'editors' => [],
388
            'anons' => [],
389
            'year_count' => [],
390
            'tools' => [],
391
        ];
392
393
        /** @var Edit|null */
394
        $firstEdit = null;
395
396
        /** @var Edit|null The previous edit, used to discount content that was reverted */
397
        $prevEdit = null;
398
399
        /**
400
         * The edit previously deemed as having the maximum amount of content added.
401
         * This is used to discount content that was reverted.
402
         * @var Edit|null
403
        */
404
        $prevMaxAddEdit = null;
405
406
        /**
407
         * The edit previously deemed as having the maximum amount of content deleted.
408
         * This is used to discount content that was reverted
409
         * @var Edit|null
410
         */
411
        $prevMaxDelEdit = null;
412
413
        /** @var Time|null Time of first revision, used as a comparison for month counts */
414
        $firstEditMonth = null;
415
416
        while ($rev = $revStmt->fetch()) {
417
            $edit = new Edit($this->pageInfo['page'], $rev);
418
            // $edit->setContainer($this->container);
1 ignored issue
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
419
420
            // Some shorthands
421
            $editYear = $edit->getYear();
422
            $editMonth = $edit->getMonth();
423
            $editTimestamp = $edit->getTimestamp();
424
425
            // Don't return actual edit size if last revision had a length of null.
426
            // This happens when the edit follows other edits that were revision-deleted.
427
            // See T148857 for more information.
428
            // @TODO: Remove once T101631 is resolved
429
            if ($prevEdit && $prevEdit->getLength() === null) {
430
                $editSize = 0;
431
            } else {
432
                $editSize = $edit->getSize();
433
            }
434
435
            if ($revCount === 0) {
436
                $firstEdit = $edit;
437
                $firstEditMonth = mktime(0, 0, 0, (int) $firstEdit->getMonth(), 1, $firstEdit->getYear());
438
            }
439
440
            $username = $edit->getUser()->getUsername();
441
442
            // Sometimes, with old revisions (2001 era), the revisions from 2002 come before 2001
443
            if ($editTimestamp < $firstEdit->getTimestamp()) {
444
                $firstEdit = $edit;
445
            }
446
447
            // Fill in the blank arrays for the year and 12 months
448
            if (!isset($data['year_count'][$editYear])) {
449
                $data['year_count'][$editYear] = [
450
                    'all' => 0,
451
                    'minor' => 0,
452
                    'anon' => 0,
453
                    'automated' => 0,
454
                    'size' => 0, // keep track of the size by the end of the year
455
                    'events' => [],
456
                    'months' => [],
457
                ];
458
459
                for ($i = 1; $i <= 12; $i++) {
460
                    $timeObj = mktime(0, 0, 0, $i, 1, $editYear);
461
462
                    // don't show zeros for months before the first edit or after the current month
463
                    if ($timeObj < $firstEditMonth || $timeObj > strtotime('last day of this month')) {
464
                        continue;
465
                    }
466
467
                    $data['year_count'][$editYear]['months'][sprintf('%02d', $i)] = [
468
                        'all' => 0,
469
                        'minor' => 0,
470
                        'anon' => 0,
471
                        'automated' => 0,
472
                    ];
473
                }
474
            }
475
476
            // Increment year and month counts for all edits
477
            $data['year_count'][$editYear]['all']++;
478
            $data['year_count'][$editYear]['months'][$editMonth]['all']++;
479
            // This will ultimately be the size of the page by the end of the year
480
            $data['year_count'][$editYear]['size'] = $edit->getLength();
481
482
            // Keep track of which month had the most edits
483
            $editsThisMonth = $data['year_count'][$editYear]['months'][$editMonth]['all'];
484
            if ($editsThisMonth > $data['max_edits_per_month']) {
485
                $data['max_edits_per_month'] = $editsThisMonth;
486
            }
487
488
            // Initialize various user stats
489
            if (!isset($data['editors'][$username])) {
490
                $data['general']['editor_count']++;
491
                $data['editors'][$username] = [
492
                    'all' => 0,
493
                    'minor' => 0,
494
                    'minor_percentage' => 0,
495
                    'first' => $editTimestamp,
496
                    'first_id' => $edit->getId(),
497
                    'last' => null,
498
                    'atbe' => null,
499
                    'added' => 0,
500
                    'sizes' => [],
501
                ];
502
            }
503
504
            // Increment user counts
505
            $data['editors'][$username]['all']++;
506
            $data['editors'][$username]['last'] = $editTimestamp;
507
            $data['editors'][$username]['last_id'] = $edit->getId();
508
509
            // Store number of KB added with this edit
510
            $data['editors'][$username]['sizes'][] = $edit->getLength() / 1024;
511
512
            // Check if it was a revert
513
            if ($this->aeh->isRevert($edit->getComment())) {
514
                $data['general']['revert_count']++;
515
516
                // Since this was a revert, we don't want to treat the previous
517
                //   edit as legit content addition or removal
518
                if ($prevEdit && $prevEdit->getSize() > 0) {
519
                    $data['general']['added'] -= $prevEdit->getSize();
520
                }
521
522
                // @TODO: Test this against an edit war (use your sandbox)
523
                // Also remove as max added or deleted, if applicable
524
                if ($data['general']['max_add'] &&
525
                    $prevEdit->getId() === $data['general']['max_add']->getId()
526
                ) {
527
                    $data['general']['max_add'] = $prevMaxAddEdit;
528
                    $prevMaxAddEdit = $prevEdit; // in the event of edit wars
529
                } elseif ($data['general']['max_del'] &&
530
                    $prevEdit->getId() === $data['general']['max_del']->getId()
531
                ) {
532
                    $data['general']['max_del'] = $prevMaxDelEdit;
533
                    $prevMaxDelEdit = $prevEdit; // in the event of edit wars
534
                }
535
            } else {
536
                // Edit was not a revert, so treat size > 0 as content added
537
                if ($editSize > 0) {
538
                    $data['general']['added'] += $editSize;
539
                    $data['editors'][$username]['added'] += $editSize;
540
541
                    // Keep track of edit with max addition
542
                    if (!$data['general']['max_add'] || $editSize > $data['general']['max_add']->getSize()) {
543
                        // Keep track of old max_add in case we find out the next $edit was reverted
544
                        //   (and was also a max edit), in which case we'll want to use this one ($edit)
545
                        $prevMaxAddEdit = $data['general']['max_add'];
546
547
                        $data['general']['max_add'] = $edit;
548
                    }
549
                } elseif ($editSize < 0 && (
550
                    !$data['general']['max_del'] || $editSize < $data['general']['max_del']->getSize()
551
                )) {
552
                    $data['general']['max_del'] = $edit;
553
                }
554
            }
555
556
            // If anonymous, increase counts
557
            if ($edit->isAnon()) {
558
                $data['general']['anon_count']++;
559
                $data['year_count'][$editYear]['anon']++;
560
                $data['year_count'][$editYear]['months'][$editMonth]['anon']++;
561
            }
562
563
            // If minor edit, increase counts
564
            if ($edit->isMinor()) {
565
                $data['general']['minor_count']++;
566
                $data['year_count'][$editYear]['minor']++;
567
                $data['year_count'][$editYear]['months'][$editMonth]['minor']++;
568
569
                // Increment minor counts for this user
570
                $data['editors'][$username]['minor']++;
571
            }
572
573
            $automatedTool = $this->aeh->getTool($edit->getComment());
574
            if ($automatedTool) {
575
                $data['general']['automated_count']++;
576
                $data['year_count'][$editYear]['automated']++;
577
                $data['year_count'][$editYear]['months'][$editMonth]['automated']++;
578
579
                if (!isset($data['tools'][$automatedTool])) {
580
                    $data['tools'][$automatedTool] = [
581
                        'count' => 1,
582
                        'link' => $this->aeh->getTools()[$automatedTool]['link'],
583
                    ];
584
                } else {
585
                    $data['tools'][$automatedTool]['count']++;
586
                }
587
            }
588
589
            // Increment "edits per <time>" counts
590
            if ($editTimestamp > new DateTime('-1 day')) {
591
                $data['general']['count_history']['day']++;
592
            }
593
            if ($editTimestamp > new DateTime('-1 week')) {
594
                $data['general']['count_history']['week']++;
595
            }
596
            if ($editTimestamp > new DateTime('-1 month')) {
597
                $data['general']['count_history']['month']++;
598
            }
599
            if ($editTimestamp > new DateTime('-1 year')) {
600
                $data['general']['count_history']['year']++;
601
            }
602
603
            $revCount++;
604
            $prevEdit = $edit;
605
            $lastEdit = $edit;
606
        }
607
608
        // add percentages
609
        $data['general']['minor_percentage'] = round(
610
            ($data['general']['minor_count'] / $revCount) * 100,
611
            1
612
        );
613
        $data['general']['anon_percentage'] = round(
614
            ($data['general']['anon_count'] / $revCount) * 100,
615
            1
616
        );
617
618
        // other general statistics
619
        $dateFirst = $firstEdit->getTimestamp();
620
        $dateLast = $lastEdit->getTimestamp();
0 ignored issues
show
Bug introduced by
The variable $lastEdit does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
621
        $data['general']['datetime_first_edit'] = $dateFirst;
622
        $data['general']['datetime_last_edit'] = $dateLast;
623
        $interval = date_diff($dateLast, $dateFirst, true);
624
625
        $data['totaldays'] = $interval->format('%a');
626
        $data['general']['average_days_per_edit'] = round($data['totaldays'] / $revCount, 1);
627
        $editsPerDay = $data['totaldays']
628
            ? $revCount / ($data['totaldays'] / (365 / 12 / 24))
629
            : 0;
630
        $data['general']['edits_per_day'] = round($editsPerDay, 1);
631
        $editsPerMonth = $data['totaldays']
632
            ? $revCount / ($data['totaldays'] / (365 / 12))
633
            : 0;
634
        $data['general']['edits_per_month'] = round($editsPerMonth, 1);
635
        $editsPerYear = $data['totaldays']
636
            ? $revCount / ($data['totaldays'] / 365)
637
            : 0;
638
        $data['general']['edits_per_year'] = round($editsPerYear, 1);
639
        $data['general']['edits_per_editor'] = round($revCount / count($data['editors']), 1);
640
641
        $data['firstEdit'] = $firstEdit;
642
        $data['lastEdit'] = $lastEdit;
643
644
        // Various sorts
645
        arsort($data['editors']);
646
        arsort($data['tools']);
647
        ksort($data['year_count']);
648
649
        return $data;
650
    }
651
}
652