Completed
Push — master ( 7febf5...57c0a7 )
by MusikAnimal
15s
created

ArticleInfoController::resultAction()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 51
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 51
rs 8.6588
c 0
b 0
f 0
cc 6
eloc 31
nc 10
nop 2

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 Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
9
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
10
use Symfony\Component\HttpFoundation\Request;
11
use Symfony\Component\DependencyInjection\ContainerInterface;
12
use Symfony\Component\HttpFoundation\Response;
13
use Symfony\Component\HttpFoundation\JsonResponse;
14
use Symfony\Component\HttpFoundation\RedirectResponse;
15
use Symfony\Component\Process\Process;
16
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
17
use Xtools\ProjectRepository;
18
use Xtools\Page;
19
use Xtools\PagesRepository;
20
use Xtools\ArticleInfo;
21
use DateTime;
22
23
/**
24
 * This controller serves the search form and results for the ArticleInfo tool
25
 */
26
class ArticleInfoController extends XtoolsController
27
{
28
    /**
29
     * Get the tool's shortname.
30
     * @return string
31
     * @codeCoverageIgnore
32
     */
33
    public function getToolShortname()
34
    {
35
        return 'articleinfo';
36
    }
37
38
    /**
39
     * The search form.
40
     * @Route("/articleinfo", name="articleinfo")
41
     * @Route("/articleinfo", name="articleInfo")
42
     * @Route("/articleinfo/", name="articleInfoSlash")
43
     * @Route("/articleinfo/index.php", name="articleInfoIndexPhp")
44
     * @Route("/articleinfo/{project}", name="ArticleInfoProject")
45
     * @param Request $request The HTTP request.
46
     * @return Response
47
     */
48
    public function indexAction(Request $request)
49
    {
50
        $params = $this->parseQueryParams($request);
51
52
        if (isset($params['project']) && isset($params['article'])) {
53
            return $this->redirectToRoute('ArticleInfoResult', $params);
54
        }
55
56
        // Convert the given project (or default project) into a Project instance.
57
        $params['project'] = $this->getProjectFromQuery($params);
58
59
        return $this->render('articleInfo/index.html.twig', [
60
            'xtPage' => 'articleinfo',
61
            'xtPageTitle' => 'tool-articleinfo',
62
            'xtSubtitle' => 'tool-articleinfo-desc',
63
            'project' => $params['project'],
64
        ]);
65
    }
66
67
    /**
68
     * Generate ArticleInfo gadget script for use on-wiki. This automatically points the
69
     * script to this installation's API. Pass ?uglify=1 to uglify the code.
70
     *
71
     * @Route("/articleinfo-gadget.js", name="ArticleInfoGadget")
72
     * @link https://www.mediawiki.org/wiki/XTools#ArticleInfo_gadget
73
     *
74
     * @param Request $request The HTTP request
75
     * @return Response
76
     * @codeCoverageIgnore
77
     */
78
    public function gadgetAction(Request $request)
79
    {
80
        $rendered = $this->renderView('articleInfo/articleinfo.js.twig');
81
82
        // SUPER hacky, but it works and is safe.
83
        if ($request->query->get('uglify') != '') {
84
            // $ and " need to be escaped.
85
            $rendered = str_replace('$', '\$', trim($rendered));
86
            $rendered = str_replace('"', '\"', trim($rendered));
87
88
            // Uglify temporary file.
89
            $tmpFile = sys_get_temp_dir() . '/xtools_articleinfo_gadget.js';
90
            $script = "echo \"$rendered\" | tee $tmpFile >/dev/null && ";
91
            $script .= $this->get('kernel')->getRootDir() .
92
                "/Resources/node_modules/uglify-es/bin/uglifyjs $tmpFile --mangle " .
93
                "&& rm $tmpFile >/dev/null";
94
            $process = new Process($script);
95
            $process->run();
96
97
            // Check for errors.
98
            $errorOutput = $process->getErrorOutput();
99
            if ($errorOutput != '') {
100
                $response = new \Symfony\Component\HttpFoundation\Response(
101
                    "Error generating uglified JS. The server said:\n\n$errorOutput"
102
                );
103
                return $response;
104
            }
105
106
            // Remove escaping.
107
            $rendered = str_replace('\$', '$', trim($process->getOutput()));
108
            $rendered = str_replace('\"', '"', trim($rendered));
109
110
            // Add comment after uglifying since it removes comments.
111
            $rendered = "/**\n * This code was automatically generated and should not " .
112
                "be manually edited.\n * For updates, please copy and paste from " .
113
                $this->generateUrl('ArticleInfoGadget', ['uglify' => 1], UrlGeneratorInterface::ABSOLUTE_URL) .
114
                "\n * Released under GPL v3 license.\n */\n" . $rendered;
115
        }
116
117
        $response = new \Symfony\Component\HttpFoundation\Response($rendered);
118
        $response->headers->set('Content-Type', 'text/javascript');
119
        return $response;
120
    }
121
122
    /**
123
     * Display the results.
124
     * @Route("/articleinfo/{project}/{article}", name="ArticleInfoResult", requirements={"article"=".+"})
125
     * @param Request $request The HTTP request.
126
     * @param string $article
127
     * @return Response
128
     * @codeCoverageIgnore
129
     */
130
    public function resultAction(Request $request, $article)
131
    {
132
        // In this case only the project is validated.
133
        $ret = $this->validateProjectAndUser($request);
134
        if ($ret instanceof RedirectResponse) {
135
            return $ret;
136
        } else {
137
            $project = $ret[0];
138
        }
139
140
        $page = $this->getAndValidatePage($project, $article);
141
        if ($page instanceof RedirectResponse) {
142
            return $page;
143
        }
144
145
        $articleInfo = new ArticleInfo($page, $this->container);
146
        $articleInfo->prepareData();
147
148
        $numRevisions = $articleInfo->getNumRevisions();
0 ignored issues
show
Unused Code introduced by
$numRevisions is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
149
        $maxRevisions = $this->container->getParameter('app.max_page_revisions');
150
151
        // Show message if we hit the max revisions.
152
        if ($articleInfo->tooManyRevisions()) {
153
            // FIXME: i18n number_format?
154
            $this->addFlash('notice', ['too-many-revisions', number_format($maxRevisions), $maxRevisions]);
0 ignored issues
show
Documentation introduced by
array('too-many-revision...isions), $maxRevisions) is of type array<integer,*,{"0":"st...,"1":"string","2":"*"}>, 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...
155
        }
156
157
        $ret = [
158
            'xtPage' => 'articleinfo',
159
            'xtTitle' => $page->getTitle(),
160
            'project' => $project,
161
            'editorlimit' => $request->query->get('editorlimit', 20),
162
            'botlimit' => $request->query->get('botlimit', 10),
163
            'pageviewsOffset' => 60,
164
            'ai' => $articleInfo,
165
            'page' => $page,
166
        ];
167
168
        // Output the relevant format template.
169
        $format = $request->query->get('format', 'html');
170
        if ($format == '') {
171
            // The default above doesn't work when the 'format' parameter is blank.
172
            $format = 'html';
173
        }
174
        $response = $this->render("articleInfo/result.$format.twig", $ret);
175
        if ($format == 'wikitext') {
176
            $response->headers->set('Content-Type', 'text/plain');
177
        }
178
179
        return $response;
180
    }
181
182
    /************************ API endpoints ************************/
183
184
    /**
185
     * Get basic info on a given article.
186
     * @Route("/api/articleinfo/{project}/{article}", requirements={"article"=".+"})
187
     * @Route("/api/page/articleinfo/{project}/{article}", requirements={"article"=".+"})
188
     * @param Request $request The HTTP request.
189
     * @param string $project
190
     * @param string $article
191
     * @return View
192
     * See ArticleInfoControllerTest::testArticleInfoApi()
193
     * @codeCoverageIgnore
194
     */
195
    public function articleInfoApiAction(Request $request, $project, $article)
196
    {
197
        /** @var integer Number of days to query for pageviews */
198
        $pageviewsOffset = 30;
199
200
        $project = ProjectRepository::getProject($project, $this->container);
201
        if (!$project->exists()) {
202
            return new JsonResponse(
203
                ['error' => "$project is not a valid project"],
204
                Response::HTTP_NOT_FOUND
205
            );
206
        }
207
208
        $page = $this->getAndValidatePage($project, $article);
209
        if ($page instanceof RedirectResponse) {
210
            return new JsonResponse(
211
                ['error' => "$article was not found"],
212
                Response::HTTP_NOT_FOUND
213
            );
214
        }
215
216
        $info = $page->getBasicEditingInfo();
217
        $creationDateTime = DateTime::createFromFormat('YmdHis', $info['created_at']);
218
        $modifiedDateTime = DateTime::createFromFormat('YmdHis', $info['modified_at']);
219
        $secsSinceLastEdit = (new DateTime)->getTimestamp() - $modifiedDateTime->getTimestamp();
220
221
        $data = [
222
            'revisions' => (int) $info['num_edits'],
223
            'editors' => (int) $info['num_editors'],
224
            'author' => $info['author'],
225
            'author_editcount' => (int) $info['author_editcount'],
226
            'created_at' => $creationDateTime->format('Y-m-d'),
227
            'created_rev_id' => $info['created_rev_id'],
228
            'modified_at' => $modifiedDateTime->format('Y-m-d H:i'),
229
            'secs_since_last_edit' => $secsSinceLastEdit,
230
            'last_edit_id' => (int) $info['modified_rev_id'],
231
            'watchers' => (int) $page->getWatchers(),
232
            'pageviews' => $page->getLastPageviews($pageviewsOffset),
233
            'pageviews_offset' => $pageviewsOffset,
234
        ];
235
236
        if ($request->query->get('format') === 'html') {
237
            $response = $this->render('articleInfo/api.html.twig', [
238
                'data' => $data,
239
                'project' => $project,
240
                'page' => $page,
241
            ]);
242
243
            // All /api routes by default respond with a JSON content type.
244
            $response->headers->set('Content-Type', 'text/html');
245
246
            // This endpoint is hit constantly and user could be browsing the same page over
247
            // and over (popular noticeboard, for instance), so offload brief caching to browser.
248
            $response->setClientTtl(350);
249
250
            return $response;
251
        }
252
253
        $body = array_merge([
254
            'project' => $project->getDomain(),
255
            'page' => $page->getTitle(),
256
        ], $data);
257
258
        return new JsonResponse(
259
            $body,
260
            Response::HTTP_OK
261
        );
262
    }
263
}
264