Correct Aphlict websocket URI construction after PHP8 compatibility changes
[phabricator.git] / src / applications / diffusion / controller / DiffusionLintController.php
blob704435882d89b9969e815ceb0a58fde162c27280
1 <?php
3 final class DiffusionLintController extends DiffusionController {
5 public function shouldAllowPublic() {
6 return true;
9 public function handleRequest(AphrontRequest $request) {
10 $viewer = $this->getViewer();
12 if ($this->getRepositoryIdentifierFromRequest($request)) {
13 $response = $this->loadDiffusionContext();
14 if ($response) {
15 return $response;
18 $drequest = $this->getDiffusionRequest();
19 } else {
20 $drequest = null;
23 $code = $request->getStr('lint');
24 if (strlen($code)) {
25 return $this->buildDetailsResponse();
28 $owners = array();
29 if (!$drequest) {
30 if (!$request->getArr('owner')) {
31 $owners = array($viewer->getPHID());
32 } else {
33 $owners = array(head($request->getArr('owner')));
37 $codes = $this->loadLintCodes($drequest, $owners);
39 if ($codes) {
40 $branches = id(new PhabricatorRepositoryBranch())->loadAllWhere(
41 'id IN (%Ld)',
42 array_unique(ipull($codes, 'branchID')));
43 $branches = mpull($branches, null, 'getID');
44 } else {
45 $branches = array();
48 if ($branches) {
49 $repositories = id(new PhabricatorRepositoryQuery())
50 ->setViewer($viewer)
51 ->withIDs(mpull($branches, 'getRepositoryID'))
52 ->execute();
53 $repositories = mpull($repositories, null, 'getID');
54 } else {
55 $repositories = array();
59 $rows = array();
60 $total = 0;
61 foreach ($codes as $code) {
62 $branch = idx($branches, $code['branchID']);
63 if (!$branch) {
64 continue;
67 $repository = idx($repositories, $branch->getRepositoryID());
68 if (!$repository) {
69 continue;
72 $total += $code['n'];
74 if ($drequest) {
75 $href_lint = $drequest->generateURI(
76 array(
77 'action' => 'lint',
78 'lint' => $code['code'],
79 ));
81 $href_browse = $drequest->generateURI(
82 array(
83 'action' => 'browse',
84 'lint' => $code['code'],
85 ));
87 $href_repo = $drequest->generateURI(
88 array(
89 'action' => 'lint',
90 ));
91 } else {
92 $href_lint = $repository->generateURI(
93 array(
94 'action' => 'lint',
95 'lint' => $code['code'],
96 ));
98 $href_browse = $repository->generateURI(
99 array(
100 'action' => 'browse',
101 'lint' => $code['code'],
104 $href_repo = $repository->generateURI(
105 array(
106 'action' => 'lint',
110 $rows[] = array(
111 phutil_tag('a', array('href' => $href_lint), $code['n']),
112 phutil_tag('a', array('href' => $href_browse), $code['files']),
113 phutil_tag(
114 'a',
115 array(
116 'href' => $href_repo,
118 $repository->getDisplayName()),
119 ArcanistLintSeverity::getStringForSeverity($code['maxSeverity']),
120 $code['code'],
121 $code['maxName'],
122 $code['maxDescription'],
126 $table = id(new AphrontTableView($rows))
127 ->setHeaders(array(
128 pht('Problems'),
129 pht('Files'),
130 pht('Repository'),
131 pht('Severity'),
132 pht('Code'),
133 pht('Name'),
134 pht('Example'),
136 ->setColumnVisibility(array(true, true, !$drequest))
137 ->setColumnClasses(array('n', 'n', '', '', 'pri', '', ''));
139 $content = array();
141 if (!$drequest) {
142 $form = id(new AphrontFormView())
143 ->setUser($viewer)
144 ->setMethod('GET')
145 ->appendControl(
146 id(new AphrontFormTokenizerControl())
147 ->setDatasource(new PhabricatorPeopleDatasource())
148 ->setLimit(1)
149 ->setName('owner')
150 ->setLabel(pht('Owner'))
151 ->setValue($owners))
152 ->appendChild(
153 id(new AphrontFormSubmitControl())
154 ->setValue(pht('Filter')));
155 $content[] = id(new AphrontListFilterView())->appendChild($form);
158 $content[] = id(new PHUIObjectBoxView())
159 ->setHeaderText(pht('Lint'))
160 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
161 ->setTable($table);
163 $title = array('Lint');
164 $crumbs = $this->buildCrumbs(
165 array(
166 'branch' => true,
167 'path' => true,
168 'view' => 'lint',
170 $crumbs->setBorder(true);
172 if ($drequest) {
173 $title[] = $drequest->getRepository()->getDisplayName();
174 } else {
175 $crumbs->addTextCrumb(pht('All Lint'));
178 if ($drequest) {
179 $branch = $drequest->loadBranch();
181 $header = id(new PHUIHeaderView())
182 ->setHeader(pht('Lint: %s', $this->renderPathLinks($drequest, 'lint')))
183 ->setUser($viewer)
184 ->setHeaderIcon('fa-code');
185 $actions = $this->buildActionView($drequest);
186 $properties = $this->buildPropertyView(
187 $drequest,
188 $branch,
189 $total,
190 $actions);
192 $object_box = id(new PHUIObjectBoxView())
193 ->setHeader($header)
194 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
195 ->addPropertyList($properties);
196 } else {
197 $object_box = null;
198 $header = id(new PHUIHeaderView())
199 ->setHeader(pht('All Lint'))
200 ->setHeaderIcon('fa-code');
203 $view = id(new PHUITwoColumnView())
204 ->setHeader($header)
205 ->setFooter(array(
206 $object_box,
207 $content,
210 return $this->newPage()
211 ->setTitle($title)
212 ->setCrumbs($crumbs)
213 ->appendChild(
214 array(
215 $view,
219 private function loadLintCodes($drequest, array $owner_phids) {
220 $conn = id(new PhabricatorRepository())->establishConnection('r');
221 $where = array('1 = 1');
223 if ($drequest) {
224 $branch = $drequest->loadBranch();
225 if (!$branch) {
226 return array();
229 $where[] = qsprintf($conn, 'branchID = %d', $branch->getID());
231 if ($drequest->getPath() != '') {
232 $path = '/'.$drequest->getPath();
233 $is_dir = (substr($path, -1) == '/');
234 $where[] = ($is_dir
235 ? qsprintf($conn, 'path LIKE %>', $path)
236 : qsprintf($conn, 'path = %s', $path));
240 if ($owner_phids) {
241 $or = array();
242 $or[] = qsprintf($conn, 'authorPHID IN (%Ls)', $owner_phids);
244 $paths = array();
245 $packages = id(new PhabricatorOwnersOwner())
246 ->loadAllWhere('userPHID IN (%Ls)', $owner_phids);
247 if ($packages) {
248 $paths = id(new PhabricatorOwnersPath())->loadAllWhere(
249 'packageID IN (%Ld)',
250 mpull($packages, 'getPackageID'));
253 if ($paths) {
254 $repositories = id(new PhabricatorRepositoryQuery())
255 ->setViewer($this->getRequest()->getUser())
256 ->withPHIDs(mpull($paths, 'getRepositoryPHID'))
257 ->execute();
258 $repositories = mpull($repositories, 'getID', 'getPHID');
260 $branches = id(new PhabricatorRepositoryBranch())->loadAllWhere(
261 'repositoryID IN (%Ld)',
262 $repositories);
263 $branches = mgroup($branches, 'getRepositoryID');
266 foreach ($paths as $path) {
267 $branch = idx(
268 $branches,
269 idx(
270 $repositories,
271 $path->getRepositoryPHID()));
272 if ($branch) {
273 $condition = qsprintf(
274 $conn,
275 '(branchID IN (%Ld) AND path LIKE %>)',
276 array_keys($branch),
277 $path->getPath());
278 if ($path->getExcluded()) {
279 $where[] = qsprintf($conn, 'NOT %Q', $condition);
280 } else {
281 $or[] = $condition;
285 $where[] = qsprintf($conn, '%LO', $or);
288 return queryfx_all(
289 $conn,
290 'SELECT
291 branchID,
292 code,
293 MAX(severity) AS maxSeverity,
294 MAX(name) AS maxName,
295 MAX(description) AS maxDescription,
296 COUNT(DISTINCT path) AS files,
297 COUNT(*) AS n
298 FROM %T
299 WHERE %LA
300 GROUP BY branchID, code
301 ORDER BY n DESC',
302 PhabricatorRepository::TABLE_LINTMESSAGE,
303 $where);
306 protected function buildActionView(DiffusionRequest $drequest) {
307 $viewer = $this->getRequest()->getUser();
309 $view = id(new PhabricatorActionListView())
310 ->setUser($viewer);
312 $list_uri = $drequest->generateURI(
313 array(
314 'action' => 'lint',
315 'lint' => '',
318 $view->addAction(
319 id(new PhabricatorActionView())
320 ->setName(pht('View As List'))
321 ->setHref($list_uri)
322 ->setIcon('fa-list'));
324 $history_uri = $drequest->generateURI(
325 array(
326 'action' => 'history',
329 $view->addAction(
330 id(new PhabricatorActionView())
331 ->setName(pht('View History'))
332 ->setHref($history_uri)
333 ->setIcon('fa-clock-o'));
335 $browse_uri = $drequest->generateURI(
336 array(
337 'action' => 'browse',
340 $view->addAction(
341 id(new PhabricatorActionView())
342 ->setName(pht('Browse Content'))
343 ->setHref($browse_uri)
344 ->setIcon('fa-files-o'));
346 return $view;
349 protected function buildPropertyView(
350 DiffusionRequest $drequest,
351 PhabricatorRepositoryBranch $branch,
352 $total,
353 PhabricatorActionListView $actions) {
355 $viewer = $this->getRequest()->getUser();
357 $view = id(new PHUIPropertyListView())
358 ->setUser($viewer)
359 ->setActionList($actions);
361 $lint_commit = $branch->getLintCommit();
363 $view->addProperty(
364 pht('Lint Commit'),
365 phutil_tag(
366 'a',
367 array(
368 'href' => $drequest->generateURI(
369 array(
370 'action' => 'commit',
371 'commit' => $lint_commit,
374 $drequest->getRepository()->formatCommitName($lint_commit)));
376 $view->addProperty(
377 pht('Total Messages'),
378 pht('%s', new PhutilNumber($total)));
380 return $view;
384 private function buildDetailsResponse() {
385 $request = $this->getRequest();
387 $limit = 500;
389 $pager = id(new PHUIPagerView())
390 ->readFromRequest($request)
391 ->setPageSize($limit);
393 $offset = $pager->getOffset();
395 $drequest = $this->getDiffusionRequest();
396 $branch = $drequest->loadBranch();
397 $messages = $this->loadLintMessages($branch, $limit, $offset);
398 $is_dir = (substr('/'.$drequest->getPath(), -1) == '/');
400 $pager->setHasMorePages(count($messages) >= $limit);
402 $authors = $this->loadViewerHandles(ipull($messages, 'authorPHID'));
404 $rows = array();
405 foreach ($messages as $message) {
406 $path = phutil_tag(
407 'a',
408 array(
409 'href' => $drequest->generateURI(array(
410 'action' => 'lint',
411 'path' => $message['path'],
414 substr($message['path'], strlen($drequest->getPath()) + 1));
416 $line = phutil_tag(
417 'a',
418 array(
419 'href' => $drequest->generateURI(array(
420 'action' => 'browse',
421 'path' => $message['path'],
422 'line' => $message['line'],
423 'commit' => $branch->getLintCommit(),
426 $message['line']);
428 $author = $message['authorPHID'];
429 if ($author && $authors[$author]) {
430 $author = $authors[$author]->renderLink();
433 $rows[] = array(
434 $path,
435 $line,
436 $author,
437 ArcanistLintSeverity::getStringForSeverity($message['severity']),
438 $message['name'],
439 $message['description'],
443 $table = id(new AphrontTableView($rows))
444 ->setHeaders(array(
445 pht('Path'),
446 pht('Line'),
447 pht('Author'),
448 pht('Severity'),
449 pht('Name'),
450 pht('Description'),
452 ->setColumnClasses(array('', 'n'))
453 ->setColumnVisibility(array($is_dir));
455 $content = array();
457 $content[] = id(new PHUIObjectBoxView())
458 ->setHeaderText(pht('Lint Details'))
459 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
460 ->setTable($table)
461 ->setPager($pager);
463 $crumbs = $this->buildCrumbs(
464 array(
465 'branch' => true,
466 'path' => true,
467 'view' => 'lint',
469 $crumbs->setBorder(true);
471 $header = id(new PHUIHeaderView())
472 ->setHeader(pht('Lint: %s', $drequest->getRepository()->getDisplayName()))
473 ->setHeaderIcon('fa-code');
475 $view = id(new PHUITwoColumnView())
476 ->setHeader($header)
477 ->setFooter(array(
478 $content,
481 return $this->newPage()
482 ->setTitle(
483 array(
484 pht('Lint'),
485 $drequest->getRepository()->getDisplayName(),
487 ->setCrumbs($crumbs)
488 ->appendChild(
489 array(
490 $view,
494 private function loadLintMessages(
495 PhabricatorRepositoryBranch $branch,
496 $limit,
497 $offset) {
499 $drequest = $this->getDiffusionRequest();
500 if (!$branch) {
501 return array();
504 $conn = $branch->establishConnection('r');
506 $where = array(
507 qsprintf($conn, 'branchID = %d', $branch->getID()),
510 if ($drequest->getPath() != '') {
511 $path = '/'.$drequest->getPath();
512 $is_dir = (substr($path, -1) == '/');
513 $where[] = ($is_dir
514 ? qsprintf($conn, 'path LIKE %>', $path)
515 : qsprintf($conn, 'path = %s', $path));
518 if ($drequest->getLint() != '') {
519 $where[] = qsprintf(
520 $conn,
521 'code = %s',
522 $drequest->getLint());
525 return queryfx_all(
526 $conn,
527 'SELECT *
528 FROM %T
529 WHERE %LA
530 ORDER BY path, code, line LIMIT %d OFFSET %d',
531 PhabricatorRepository::TABLE_LINTMESSAGE,
532 $where,
533 $limit,
534 $offset);