Passed
Push — master ( bb3b01...ed8c75 )
by MusikAnimal
06:02
created

TopEditsController   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 242
Duplicated Lines 0 %

Test Coverage

Coverage 87.5%

Importance

Changes 0
Metric Value
dl 0
loc 242
ccs 7
cts 8
cp 0.875
rs 10
c 0
b 0
f 0
wmc 23

6 Methods

Rating   Name   Duplication   Size   Complexity  
A resultAction() 0 15 4
A getToolShortname() 0 3 1
A indexAction() 0 21 3
B singlePageTopEdits() 0 27 3
B namespaceTopEdits() 0 48 4
B topEditsUserApiAction() 0 55 8
1
<?php
2
/**
3
 * This file contains only the TopEditsController 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\HttpFoundation\Response;
12
use Symfony\Component\HttpFoundation\RedirectResponse;
13
use Symfony\Component\HttpFoundation\JsonResponse;
14
use Xtools\Project;
15
use Xtools\User;
16
use Xtools\TopEdits;
17
use Xtools\TopEditsRepository;
18
use Xtools\Edit;
19
20
/**
21
 * The Top Edits tool.
22
 */
23
class TopEditsController extends XtoolsController
24
{
25
26
    /**
27
     * Get the tool's shortname.
28
     * @return string
29
     * @codeCoverageIgnore
30
     */
31
    public function getToolShortname()
32
    {
33
        return 'topedits';
34
    }
35
36
    /**
37
     * Display the form.
38
     * @Route("/topedits", name="topedits")
39
     * @Route("/topedits", name="TopEdits")
40
     * @Route("/topedits/", name="topEditsSlash")
41
     * @Route("/topedits/index.php", name="TopEditsIndex")
42
     * @Route("/topedits/{project}", name="TopEditsProject")
43
     * @param Request $request
44
     * @return Response
45
     */
46 1
    public function indexAction(Request $request)
47
    {
48 1
        $params = $this->parseQueryParams($request);
49
50
        // Redirect if at minimum project and username are provided.
51 1
        if (isset($params['project']) && isset($params['username'])) {
52
            return $this->redirectToRoute('TopEditsResults', $params);
53
        }
54
55
        // Convert the given project (or default project) into a Project instance.
56 1
        $params['project'] = $this->getProjectFromQuery($params);
57
58 1
        return $this->render('topedits/index.html.twig', array_merge([
59 1
            'xtPageTitle' => 'tool-topedits',
60
            'xtSubtitle' => 'tool-topedits-desc',
61
            'xtPage' => 'topedits',
62
63
            // Defaults that will get overriden if in $params.
64
            'namespace' => 0,
65
            'article' => '',
66 1
        ], $params));
67
    }
68
69
    /**
70
     * Display the results.
71
     * @Route("/topedits/{project}/{username}/{namespace}/{article}", name="TopEditsResults",
72
     *     requirements={"article"=".+"})
73
     * @param Request $request The HTTP request.
74
     * @param int $namespace
75
     * @param string $article
76
     * @return RedirectResponse|Response
77
     * @codeCoverageIgnore
78
     */
79
    public function resultAction(Request $request, $namespace = 0, $article = '')
80
    {
81
        // Second parameter causes it return a Redirect to the index if the user has too many edits.
82
        // We only want to do this when looking at the user's overall edits, not just to a specific article.
83
        $ret = $this->validateProjectAndUser($request, $article !== '' ?  null : 'topedits');
84
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
introduced by
The condition $ret instanceof Symfony\...dation\RedirectResponse can never be false since $ret is always a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
85
            return $ret;
86
        } else {
87
            list($projectData, $user) = $ret;
88
        }
89
90
        if ($article === '') {
91
            return $this->namespaceTopEdits($request, $user, $projectData, $namespace);
92
        } else {
93
            return $this->singlePageTopEdits($user, $projectData, $namespace, $article);
94
        }
95
    }
96
97
    /**
98
     * List top edits by this user for all pages in a particular namespace.
99
     * @param Request $request The HTTP request.
100
     * @param User $user The User.
101
     * @param Project $project The project.
102
     * @param integer|string $namespace The namespace ID or 'all'
103
     * @return \Symfony\Component\HttpFoundation\Response
104
     * @codeCoverageIgnore
105
     */
106
    public function namespaceTopEdits(Request $request, User $user, Project $project, $namespace)
107
    {
108
        $isSubRequest = $request->get('htmlonly')
109
            || $this->container->get('request_stack')->getParentRequest() !== null;
110
111
        // Make sure they've opted in to see this data.
112
        if (!$project->userHasOptedIn($user)) {
113
            $optedInPage = $project
114
                ->getRepository()
115
                ->getPage($project, $project->userOptInPage($user));
116
117
            return $this->render('topedits/result_namespace.html.twig', [
118
                'xtPage' => 'topedits',
119
                'xtTitle' => $user->getUsername(),
120
                'project' => $project,
121
                'user' => $user,
122
                'namespace' => $namespace,
123
                'opted_in_page' => $optedInPage,
124
                'is_sub_request' => $isSubRequest,
125
            ]);
126
        }
127
128
        /**
129
         * Max number of rows per namespace to show. `null` here will cause to
130
         * use the TopEdits default.
131
         * @var int
132
         */
133
        $limit = $isSubRequest ? 10 : null;
134
135
        $topEdits = new TopEdits($project, $user, null, $namespace, $limit);
136
        $topEditsRepo = new TopEditsRepository();
137
        $topEditsRepo->setContainer($this->container);
138
        $topEdits->setRepository($topEditsRepo);
139
140
        $topEdits->prepareData();
141
142
        $ret = [
143
            'xtPage' => 'topedits',
144
            'xtTitle' => $user->getUsername(),
145
            'project' => $project,
146
            'user' => $user,
147
            'namespace' => $namespace,
148
            'te' => $topEdits,
149
            'is_sub_request' => $isSubRequest,
150
        ];
151
152
        // Output the relevant format template.
153
        return $this->getFormattedReponse($request, 'topedits/result_namespace', $ret);
154
    }
155
156
    /**
157
     * List top edits by this user for a particular page.
158
     * @param User $user The user.
159
     * @param Project $project The project.
160
     * @param int $namespaceId The ID of the namespace of the page.
161
     * @param string $pageName The title (without namespace) of the page.
162
     * @return RedirectResponse|\Symfony\Component\HttpFoundation\Response
163
     * @codeCoverageIgnore
164
     */
165
    protected function singlePageTopEdits(User $user, Project $project, $namespaceId, $pageName)
166
    {
167
        // Get the full page name (i.e. no namespace prefix if NS 0).
168
        $namespaces = $project->getNamespaces();
169
        $fullPageName = $namespaceId ? $namespaces[$namespaceId].':'.$pageName : $pageName;
170
171
        $page = $this->getAndValidatePage($project, $fullPageName);
172
        if (is_a($page, 'Symfony\Component\HttpFoundation\RedirectResponse')) {
173
            return $page;
174
        }
175
176
        // FIXME: add pagination.
177
        $topEdits = new TopEdits($project, $user, $page);
178
        $topEditsRepo = new TopEditsRepository();
179
        $topEditsRepo->setContainer($this->container);
180
        $topEdits->setRepository($topEditsRepo);
181
182
        $topEdits->prepareData();
183
184
        // Send all to the template.
185
        return $this->render('topedits/result_article.html.twig', [
186
            'xtPage' => 'topedits',
187
            'xtTitle' => $user->getUsername() . ' - ' . $page->getTitle(),
188
            'project' => $project,
189
            'user' => $user,
190
            'page' => $page,
191
            'te' => $topEdits,
192
        ]);
193
    }
194
195
    /************************ API endpoints ************************/
196
197
    /**
198
     * Get the all edits of a user to a specific page, maximum 1000.
199
     * @Route("/api/user/topedits/{project}/{username}/{namespace}/{article}", name="UserApiTopEditsArticle",
200
     *     requirements={"article"=".+", "namespace"="|\d+|all"})
201
     * @Route("/api/user/top_edits/{project}/{username}/{namespace}/{article}", name="UserApiTopEditsArticleUnderscored",
202
     *     requirements={"article"=".+", "namespace"="|\d+|all"})
203
     * @param Request $request
204
     * @param int|string $namespace The ID of the namespace of the page, or 'all' for all namespaces.
205
     * @param string $article The title of the page. A full title can be used if the $namespace is blank.
206
     * @return Response
207
     * TopEdits and its Repo cannot be stubbed here :(
208
     * @codeCoverageIgnore
209
     */
210
    public function topEditsUserApiAction(Request $request, $namespace = 0, $article = '')
211
    {
212
        $this->recordApiUsage('user/topedits');
213
214
        // Second parameter causes it return a Redirect to the index if the user has too many edits.
215
        // We only want to do this when looking at the user's overall edits, not just to a specific article.
216
        $ret = $this->validateProjectAndUser($request, $article !== '' ?  null : 'topedits');
217
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
introduced by
The condition $ret instanceof Symfony\...dation\RedirectResponse can never be false since $ret is always a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
218
            return $ret;
219
        } else {
220
            list($project, $user) = $ret;
221
        }
222
223
        if (!$project->userHasOptedIn($user)) {
224
            return new JsonResponse(
225
                [
226
                    'error' => 'User:'.$user->getUsername().' has not opted in to detailed statistics.'
227
                ],
228
                Response::HTTP_FORBIDDEN
229
            );
230
        }
231
232
        $limit = $article === '' ? 100 : 1000;
233
        $topEdits = new TopEdits($project, $user, null, $namespace, $limit);
234
        $topEditsRepo = new TopEditsRepository();
235
        $topEditsRepo->setContainer($this->container);
236
        $topEdits->setRepository($topEditsRepo);
237
238
        $response = new JsonResponse();
239
        $response->setEncodingOptions(JSON_NUMERIC_CHECK);
240
241
        if ($article === '') {
242
            // Do format the results.
243
            $topEdits->prepareData();
244
        } else {
245
            $namespaces = $project->getNamespaces();
246
            $fullPageName = is_numeric($namespace) ? $namespaces[$namespace].':'.$article : $article;
247
248
            $page = $this->getAndValidatePage($project, $fullPageName);
249
            if (is_a($page, 'Symfony\Component\HttpFoundation\RedirectResponse')) {
250
                $response->setData([
251
                    'error' => 'Page "'.$article.'" does not exist.',
252
                ]);
253
                $response->setStatusCode(Response::HTTP_NOT_FOUND);
254
                return $response;
255
            }
256
257
            $topEdits->setPage($page);
258
            $topEdits->prepareData(false);
259
        }
260
261
        $response->setData($topEdits->getTopEdits());
262
        $response->setStatusCode(Response::HTTP_OK);
263
264
        return $response;
265
    }
266
}
267