@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3/**
4 * This is a standard Phabricator page with menus, Javelin, DarkConsole, and
5 * basic styles.
6 */
7final class PhabricatorStandardPageView extends PhabricatorBarePageView
8 implements AphrontResponseProducerInterface {
9
10 private $baseURI;
11 private $applicationName;
12 private $glyph;
13 private $menuContent;
14 private $showChrome = true;
15 private $classes = array();
16 private $disableConsole;
17 private $pageObjects = array();
18 private $applicationMenu;
19 private $showFooter = true;
20 private $showDurableColumn = true;
21 private $quicksandConfig = array();
22 private $tabs;
23 private $crumbs;
24 private $navigation;
25 private $footer;
26 private $headItems = array();
27
28 public function setShowFooter($show_footer) {
29 $this->showFooter = $show_footer;
30 return $this;
31 }
32
33 public function getShowFooter() {
34 return $this->showFooter;
35 }
36
37 public function setApplicationName($application_name) {
38 $this->applicationName = $application_name;
39 return $this;
40 }
41
42 public function setDisableConsole($disable) {
43 $this->disableConsole = $disable;
44 return $this;
45 }
46
47 public function getApplicationName() {
48 return $this->applicationName;
49 }
50
51 public function setBaseURI($base_uri) {
52 $this->baseURI = $base_uri;
53 return $this;
54 }
55
56 public function getBaseURI() {
57 return $this->baseURI;
58 }
59
60 public function setShowChrome($show_chrome) {
61 $this->showChrome = $show_chrome;
62 return $this;
63 }
64
65 public function getShowChrome() {
66 return $this->showChrome;
67 }
68
69 public function addClass($class) {
70 $this->classes[] = $class;
71 return $this;
72 }
73
74 public function setPageObjectPHIDs(array $phids) {
75 $this->pageObjects = $phids;
76 return $this;
77 }
78
79 public function setShowDurableColumn($show) {
80 $this->showDurableColumn = $show;
81 return $this;
82 }
83
84 public function getShowDurableColumn() {
85 $request = $this->getRequest();
86 if (!$request) {
87 return false;
88 }
89
90 $viewer = $request->getUser();
91 if (!$viewer->isLoggedIn()) {
92 return false;
93 }
94
95 $conpherence_installed = PhabricatorApplication::isClassInstalledForViewer(
96 PhabricatorConpherenceApplication::class,
97 $viewer);
98 if (!$conpherence_installed) {
99 return false;
100 }
101
102 if ($this->isQuicksandBlacklistURI()) {
103 return false;
104 }
105
106 return true;
107 }
108
109 private function isQuicksandBlacklistURI() {
110 $request = $this->getRequest();
111 if (!$request) {
112 return false;
113 }
114
115 $patterns = $this->getQuicksandURIPatternBlacklist();
116 $path = $request->getRequestURI()->getPath();
117 foreach ($patterns as $pattern) {
118 if (preg_match('(^'.$pattern.'$)', $path)) {
119 return true;
120 }
121 }
122 return false;
123 }
124
125 public function getDurableColumnVisible() {
126 $column_key = PhabricatorConpherenceColumnVisibleSetting::SETTINGKEY;
127 return (bool)$this->getUserPreference($column_key, false);
128 }
129
130 public function getDurableColumnMinimize() {
131 $column_key = PhabricatorConpherenceColumnMinimizeSetting::SETTINGKEY;
132 return (bool)$this->getUserPreference($column_key, false);
133 }
134
135 public function addQuicksandConfig(array $config) {
136 $this->quicksandConfig = $config + $this->quicksandConfig;
137 return $this;
138 }
139
140 public function getQuicksandConfig() {
141 return $this->quicksandConfig;
142 }
143
144 public function setCrumbs(PHUICrumbsView $crumbs) {
145 $this->crumbs = $crumbs;
146 return $this;
147 }
148
149 public function getCrumbs() {
150 return $this->crumbs;
151 }
152
153 public function setTabs(PHUIListView $tabs) {
154 $tabs->setType(PHUIListView::TABBAR_LIST);
155 $tabs->addClass('phabricator-standard-page-tabs');
156 $this->tabs = $tabs;
157 return $this;
158 }
159
160 public function getTabs() {
161 return $this->tabs;
162 }
163
164 public function setNavigation(AphrontSideNavFilterView $navigation) {
165 $this->navigation = $navigation;
166 return $this;
167 }
168
169 public function getNavigation() {
170 return $this->navigation;
171 }
172
173 public function getTitle() {
174 $glyph_key = PhabricatorTitleGlyphsSetting::SETTINGKEY;
175 $glyph_on = PhabricatorTitleGlyphsSetting::VALUE_TITLE_GLYPHS;
176 $glyph_setting = $this->getUserPreference($glyph_key, $glyph_on);
177
178 $use_glyph = ($glyph_setting == $glyph_on);
179
180 $title = parent::getTitle();
181
182 $prefix = null;
183 if ($use_glyph) {
184 $prefix = $this->getGlyph();
185 } else {
186 $application_name = $this->getApplicationName();
187 if (strlen($application_name)) {
188 $prefix = '['.$application_name.']';
189 }
190 }
191
192 if (phutil_nonempty_string($prefix)) {
193 $title = $prefix.' '.$title;
194 }
195
196 return $title;
197 }
198
199
200 protected function willRenderPage() {
201 $footer = $this->renderFooter();
202
203 // NOTE: A cleaner solution would be to let body layout elements implement
204 // some kind of "LayoutInterface" so content can be embedded inside frames,
205 // but there's only really one use case for this for now.
206 $children = $this->renderChildren();
207 if ($children) {
208 $layout = head($children);
209 if ($layout instanceof PHUIFormationView) {
210 $layout->setFooter($footer);
211 $footer = null;
212 }
213 }
214
215 $this->footer = $footer;
216
217 parent::willRenderPage();
218
219 $request = $this->getRequest();
220 if (!$request) {
221 throw new Exception(
222 pht(
223 'You must set the %s to render a %s.',
224 'Request',
225 self::class));
226 }
227
228 $console = $this->getConsole();
229
230 require_celerity_resource('phabricator-core-css');
231 require_celerity_resource('phabricator-zindex-css');
232 require_celerity_resource('phui-button-css');
233 require_celerity_resource('phui-spacing-css');
234 require_celerity_resource('phui-form-css');
235 require_celerity_resource('phabricator-standard-page-view');
236 require_celerity_resource('conpherence-durable-column-view');
237 require_celerity_resource('font-lato');
238
239 Javelin::initBehavior('workflow', array());
240
241 $user = $request->getUser();
242 if ($user) {
243 if ($user->isUserActivated()) {
244 // Only bother user about timezone offset if they have not set UTC
245 if ($user->getTimezoneIdentifier() !== 'UTC') {
246 $offset = $user->getTimeZoneOffset();
247
248 $ignore_key = PhabricatorTimezoneIgnoreOffsetSetting::SETTINGKEY;
249 $ignore = $user->getUserSetting($ignore_key);
250
251 Javelin::initBehavior(
252 'detect-timezone',
253 array(
254 'offset' => $offset,
255 'uri' => '/settings/timezone/',
256 'message' => pht(
257 'Your browser timezone setting differs from the timezone '.
258 'setting in your profile, click to reconcile.'),
259 'ignoreKey' => $ignore_key,
260 'ignore' => $ignore,
261 ));
262 }
263
264 if ($user->getIsAdmin()) {
265 $server_https = $request->isHTTPS();
266 $server_protocol = $server_https ? 'HTTPS' : 'HTTP';
267 $client_protocol = $server_https ? 'HTTP' : 'HTTPS';
268
269 $doc_name = 'Configuring a Preamble Script';
270 $doc_href = PhabricatorEnv::getDoclink($doc_name);
271
272 Javelin::initBehavior(
273 'setup-check-https',
274 array(
275 'server_https' => $server_https,
276 'doc_name' => pht('See Documentation'),
277 'doc_href' => $doc_href,
278 'message' => pht(
279 'This server thinks you are using %s, but your '.
280 'client is convinced that it is using %s. This is a serious '.
281 'misconfiguration with subtle, but significant, consequences.',
282 $server_protocol, $client_protocol),
283 ));
284 }
285 }
286
287 Javelin::initBehavior('lightbox-attachments');
288 }
289
290 Javelin::initBehavior('aphront-form-disable-on-submit');
291 Javelin::initBehavior('toggle-class', array());
292 Javelin::initBehavior('history-install');
293 Javelin::initBehavior('phabricator-gesture');
294
295 $current_token = null;
296 if ($user) {
297 $current_token = $user->getCSRFToken();
298 }
299
300 Javelin::initBehavior(
301 'refresh-csrf',
302 array(
303 'tokenName' => AphrontRequest::getCSRFTokenName(),
304 'header' => AphrontRequest::getCSRFHeaderName(),
305 'viaHeader' => AphrontRequest::getViaHeaderName(),
306 'current' => $current_token,
307 ));
308
309 Javelin::initBehavior('device');
310
311 Javelin::initBehavior(
312 'high-security-warning',
313 $this->getHighSecurityWarningConfig());
314
315 if (PhabricatorEnv::isReadOnly()) {
316 Javelin::initBehavior(
317 'read-only-warning',
318 array(
319 'message' => PhabricatorEnv::getReadOnlyMessage(),
320 'uri' => PhabricatorEnv::getReadOnlyURI(),
321 ));
322 }
323
324 // If we aren't showing the page chrome, skip rendering DarkConsole and the
325 // main menu, since they won't be visible on the page.
326 if (!$this->getShowChrome()) {
327 return;
328 }
329
330 if ($console) {
331 require_celerity_resource('aphront-dark-console-css');
332
333 $headers = array();
334 if (DarkConsoleXHProfPluginAPI::isProfilerStarted()) {
335 $headers[DarkConsoleXHProfPluginAPI::getProfilerHeader()] = 'page';
336 }
337 if (DarkConsoleServicesPlugin::isQueryAnalyzerRequested()) {
338 $headers[DarkConsoleServicesPlugin::getQueryAnalyzerHeader()] = true;
339 }
340
341 Javelin::initBehavior(
342 'dark-console',
343 $this->getConsoleConfig());
344 }
345
346 if ($user) {
347 $viewer = $user;
348 } else {
349 $viewer = new PhabricatorUser();
350 }
351
352 $menu = id(new PhabricatorMainMenuView())
353 ->setViewer($viewer);
354
355 if ($this->getController()) {
356 $menu->setController($this->getController());
357 }
358
359 $application_menu = $this->applicationMenu;
360 if ($application_menu) {
361 if ($application_menu instanceof PHUIApplicationMenuView) {
362 $crumbs = $this->getCrumbs();
363 if ($crumbs) {
364 $application_menu->setCrumbs($crumbs);
365 }
366
367 $application_menu = $application_menu->buildListView();
368 }
369
370 $menu->setApplicationMenu($application_menu);
371 }
372
373
374 $this->menuContent = $menu->render();
375 }
376
377
378 /**
379 * Insert a HTML element into <head> of the page to render.
380 *
381 * @param PhutilSafeHTML $html HTML header to add
382 */
383 public function addHeadItem($html) {
384 if ($html instanceof PhutilSafeHTML) {
385 $this->headItems[] = $html;
386 }
387 }
388
389 protected function getHead() {
390 $monospaced = null;
391
392 $request = $this->getRequest();
393 if ($request) {
394 $user = $request->getUser();
395 if ($user) {
396 $monospaced = $user->getUserSetting(
397 PhabricatorMonospacedFontSetting::SETTINGKEY);
398 }
399 }
400
401 $response = CelerityAPI::getStaticResourceResponse();
402
403 $font_css = null;
404 if (!empty($monospaced)) {
405 // We can't print this normally because escaping quotation marks will
406 // break the CSS. Instead, filter it strictly and then mark it as safe.
407 $monospaced = new PhutilSafeHTML(
408 PhabricatorMonospacedFontSetting::filterMonospacedCSSRule(
409 $monospaced));
410
411 $font_css = hsprintf(
412 '<style type="text/css">'.
413 '.PhabricatorMonospaced, '.
414 '.phabricator-remarkup .remarkup-code-block .remarkup-code, '.
415 '.phabricator-remarkup .remarkup-monospaced '.
416 '{ font: %s !important; } '.
417 '</style>',
418 $monospaced);
419 }
420
421 return hsprintf(
422 '%s%s%s%s',
423 parent::getHead(),
424 $font_css,
425 phutil_implode_html('', $this->headItems),
426 $response->renderSingleResource('javelin-magical-init', 'phabricator'));
427 }
428
429 public function setGlyph($glyph) {
430 $this->glyph = $glyph;
431 return $this;
432 }
433
434 public function getGlyph() {
435 return $this->glyph;
436 }
437
438 protected function willSendResponse($response) {
439 $request = $this->getRequest();
440 $response = parent::willSendResponse($response);
441
442 $console = $request->getApplicationConfiguration()->getConsole();
443
444 if ($console) {
445 $response = PhutilSafeHTML::applyFunction(
446 'str_replace',
447 hsprintf('<darkconsole />'),
448 $console->render($request),
449 $response);
450 }
451
452 return $response;
453 }
454
455 protected function getBody() {
456 $user = null;
457 $request = $this->getRequest();
458 if ($request) {
459 $user = $request->getUser();
460 }
461
462 $header_chrome = null;
463 if ($this->getShowChrome()) {
464 $header_chrome = $this->menuContent;
465 }
466
467 $classes = array();
468 $classes[] = 'main-page-frame';
469 $developer_warning = null;
470 if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode') &&
471 DarkConsoleErrorLogPluginAPI::getErrors()) {
472 $developer_warning = phutil_tag_div(
473 'aphront-developer-error-callout',
474 pht(
475 'This page raised PHP errors. Find them in DarkConsole '.
476 'or the error log.'));
477 }
478
479 $main_page = phutil_tag(
480 'div',
481 array(
482 'id' => 'phabricator-standard-page',
483 'class' => 'phabricator-standard-page',
484 ),
485 array(
486 $developer_warning,
487 $header_chrome,
488 phutil_tag(
489 'div',
490 array(
491 'id' => 'phabricator-standard-page-body',
492 'class' => 'phabricator-standard-page-body',
493 ),
494 $this->renderPageBodyContent()),
495 ));
496
497 $durable_column = null;
498 if ($this->getShowDurableColumn()) {
499 $is_visible = $this->getDurableColumnVisible();
500 $is_minimize = $this->getDurableColumnMinimize();
501 $durable_column = id(new ConpherenceDurableColumnView())
502 ->setSelectedConpherence(null)
503 ->setViewer($user)
504 ->setQuicksandConfig($this->buildQuicksandConfig())
505 ->setVisible($is_visible)
506 ->setMinimize($is_minimize)
507 ->setInitialLoad(true);
508 if ($is_minimize) {
509 $this->classes[] = 'minimize-column';
510 }
511 }
512
513 Javelin::initBehavior('quicksand-blacklist', array(
514 'patterns' => $this->getQuicksandURIPatternBlacklist(),
515 ));
516
517 return phutil_tag(
518 'div',
519 array(
520 'class' => implode(' ', $classes),
521 'id' => 'main-page-frame',
522 ),
523 array(
524 $main_page,
525 $durable_column,
526 ));
527 }
528
529 private function renderPageBodyContent() {
530 $console = $this->getConsole();
531
532 $body = parent::getBody();
533
534 $nav = $this->getNavigation();
535 $tabs = $this->getTabs();
536 if ($nav) {
537 $crumbs = $this->getCrumbs();
538 if ($crumbs) {
539 $nav->setCrumbs($crumbs);
540 }
541 $nav->appendChild($body);
542 $nav->appendFooter($this->footer);
543 $content = phutil_implode_html('', array($nav->render()));
544 } else {
545 $content = array();
546
547 $crumbs = $this->getCrumbs();
548 if ($crumbs) {
549 if ($this->getTabs()) {
550 $crumbs->setBorder(true);
551 }
552 $content[] = $crumbs;
553 }
554
555 $tabs = $this->getTabs();
556 if ($tabs) {
557 $content[] = $tabs;
558 }
559
560 $content[] = $body;
561 $content[] = $this->footer;
562
563 $content = phutil_implode_html('', $content);
564 }
565
566 return array(
567 ($console ? hsprintf('<darkconsole />') : null),
568 $content,
569 );
570 }
571
572 protected function getTail() {
573 $request = $this->getRequest();
574 $user = $request->getUser();
575
576 $tail = array(
577 parent::getTail(),
578 );
579
580 $response = CelerityAPI::getStaticResourceResponse();
581
582 if ($request->isHTTPS()) {
583 $with_protocol = 'https';
584 } else {
585 $with_protocol = 'http';
586 }
587
588 $servers = PhabricatorNotificationServerRef::getEnabledClientServers(
589 $with_protocol);
590
591 if ($servers) {
592 if ($user && $user->isLoggedIn()) {
593 // TODO: We could tell the browser about all the servers and let it
594 // do random reconnects to improve reliability.
595 shuffle($servers);
596 $server = head($servers);
597
598 $client_uri = $server->getWebsocketURI();
599
600 Javelin::initBehavior(
601 'aphlict-listen',
602 array(
603 'websocketURI' => (string)$client_uri,
604 ) + $this->buildAphlictListenConfigData());
605
606 CelerityAPI::getStaticResourceResponse()
607 ->addContentSecurityPolicyURI('connect-src', $client_uri);
608 }
609 }
610
611 $tail[] = $response->renderHTMLFooter($this->getFrameable());
612
613 return $tail;
614 }
615
616 protected function getBodyClasses() {
617 $classes = array();
618
619 if (!$this->getShowChrome()) {
620 $classes[] = 'phabricator-chromeless-page';
621 }
622
623 $agent = AphrontRequest::getHTTPHeader('User-Agent');
624
625 // Try to guess the device resolution based on UA strings to avoid a flash
626 // of incorrectly-styled content.
627 $device_guess = 'device-desktop';
628 if (phutil_nonempty_string($agent)) {
629 if (preg_match('@iPhone|iPod|Android.*(Chrome/[.0-9]* Mobile|'.
630 'Mobile.*Firefox/[.0-9]*)@', $agent)) {
631 $device_guess = 'device-phone device';
632 } else if (preg_match('@iPad|(Android.*Chrome/)@', $agent)) {
633 $device_guess = 'device-tablet device';
634 }
635 }
636
637 $classes[] = $device_guess;
638
639 if (phutil_nonempty_string($agent)) {
640 if (preg_match('@Windows@', $agent)) {
641 $classes[] = 'platform-windows';
642 } else if (preg_match('@Macintosh@', $agent)) {
643 $classes[] = 'platform-mac';
644 } else if (preg_match('@X11@', $agent)) {
645 $classes[] = 'platform-linux';
646 }
647 }
648
649 if ($this->getRequest()->getStr('__print__')) {
650 $classes[] = 'printable';
651 }
652
653 if ($this->getRequest()->getStr('__aural__')) {
654 $classes[] = 'audible';
655 }
656
657 $classes[] = 'phui-theme-'.PhabricatorEnv::getEnvConfig('ui.header-color');
658 foreach ($this->classes as $class) {
659 $classes[] = $class;
660 }
661
662 return implode(' ', $classes);
663 }
664
665 private function getConsole() {
666 if ($this->disableConsole) {
667 return null;
668 }
669 return $this->getRequest()->getApplicationConfiguration()->getConsole();
670 }
671
672 private function getConsoleConfig() {
673 $user = $this->getRequest()->getUser();
674
675 $headers = array();
676 if (DarkConsoleXHProfPluginAPI::isProfilerStarted()) {
677 $headers[DarkConsoleXHProfPluginAPI::getProfilerHeader()] = 'page';
678 }
679 if (DarkConsoleServicesPlugin::isQueryAnalyzerRequested()) {
680 $headers[DarkConsoleServicesPlugin::getQueryAnalyzerHeader()] = true;
681 }
682
683 if ($user) {
684 $setting_tab = PhabricatorDarkConsoleTabSetting::SETTINGKEY;
685 $setting_visible = PhabricatorDarkConsoleVisibleSetting::SETTINGKEY;
686 $tab = $user->getUserSetting($setting_tab);
687 $visible = $user->getUserSetting($setting_visible);
688 } else {
689 $tab = null;
690 $visible = true;
691 }
692
693 return array(
694 // NOTE: We use a generic label here to prevent input reflection
695 // and mitigate compression attacks like BREACH. See discussion in
696 // T3684.
697 'uri' => pht('Main Request'),
698 'selected' => $tab,
699 'visible' => $visible,
700 'headers' => $headers,
701 );
702 }
703
704 private function getHighSecurityWarningConfig() {
705 $user = $this->getRequest()->getUser();
706
707 $show = false;
708 if ($user->hasSession()) {
709 $hisec = ($user->getSession()->getHighSecurityUntil() - time());
710 if ($hisec > 0) {
711 $show = true;
712 }
713 }
714
715 return array(
716 'show' => $show,
717 'uri' => '/auth/session/downgrade/',
718 'message' => pht(
719 'Your session is in high security mode. When you '.
720 'finish using it, click here to leave.'),
721 );
722 }
723
724 private function renderFooter() {
725 if (!$this->getShowChrome()) {
726 return null;
727 }
728
729 if (!$this->getShowFooter()) {
730 return null;
731 }
732
733 $items = PhabricatorEnv::getEnvConfig('ui.footer-items');
734 if (!$items) {
735 return null;
736 }
737
738 $foot = array();
739 foreach ($items as $item) {
740 $name = idx($item, 'name', pht('Unnamed Footer Item'));
741
742 $href = idx($item, 'href');
743 if (!PhabricatorEnv::isValidURIForLink($href)) {
744 $href = null;
745 }
746
747 if ($href !== null) {
748 $tag = 'a';
749 } else {
750 $tag = 'span';
751 }
752
753 $foot[] = phutil_tag(
754 $tag,
755 array(
756 'href' => $href,
757 ),
758 $name);
759 }
760 $foot = phutil_implode_html(" \xC2\xB7 ", $foot);
761
762 return phutil_tag(
763 'div',
764 array(
765 'class' => 'phabricator-standard-page-footer grouped',
766 ),
767 $foot);
768 }
769
770 public function renderForQuicksand() {
771 parent::willRenderPage();
772 $response = $this->renderPageBodyContent();
773 $response = $this->willSendResponse($response);
774
775 $extra_config = $this->getQuicksandConfig();
776
777 return array(
778 'content' => hsprintf('%s', $response),
779 ) + $this->buildQuicksandConfig()
780 + $extra_config;
781 }
782
783 private function buildQuicksandConfig() {
784 $viewer = $this->getRequest()->getUser();
785 $controller = $this->getController();
786
787 $dropdown_query = id(new AphlictDropdownDataQuery())
788 ->setViewer($viewer);
789 $dropdown_query->execute();
790
791 $hisec_warning_config = $this->getHighSecurityWarningConfig();
792
793 $console_config = null;
794 $console = $this->getConsole();
795 if ($console) {
796 $console_config = $this->getConsoleConfig();
797 }
798
799 $upload_enabled = false;
800 if ($controller) {
801 $upload_enabled = $controller->isGlobalDragAndDropUploadEnabled();
802 }
803
804 $application_class = null;
805 $application_search_icon = null;
806 $application_help = null;
807 $controller = $this->getController();
808 if ($controller) {
809 $application = $controller->getCurrentApplication();
810 if ($application) {
811 $application_class = get_class($application);
812 if ($application->getApplicationSearchDocumentTypes()) {
813 $application_search_icon = $application->getIcon();
814 }
815
816 $help_items = $application->getHelpMenuItems($viewer);
817 if ($help_items) {
818 $help_list = id(new PhabricatorActionListView())
819 ->setViewer($viewer);
820 foreach ($help_items as $help_item) {
821 $help_list->addAction($help_item);
822 }
823 $application_help = $help_list->getDropdownMenuMetadata();
824 }
825 }
826 }
827
828 return array(
829 'title' => $this->getTitle(),
830 'bodyClasses' => $this->getBodyClasses(),
831 'aphlictDropdownData' => array(
832 $dropdown_query->getNotificationData(),
833 $dropdown_query->getConpherenceData(),
834 ),
835 'globalDragAndDrop' => $upload_enabled,
836 'hisecWarningConfig' => $hisec_warning_config,
837 'consoleConfig' => $console_config,
838 'applicationClass' => $application_class,
839 'applicationSearchIcon' => $application_search_icon,
840 'helpItems' => $application_help,
841 ) + $this->buildAphlictListenConfigData();
842 }
843
844 private function buildAphlictListenConfigData() {
845 $user = $this->getRequest()->getUser();
846 $subscriptions = $this->pageObjects;
847 $subscriptions[] = $user->getPHID();
848
849 return array(
850 'pageObjects' => array_fill_keys($this->pageObjects, true),
851 'subscriptions' => $subscriptions,
852 );
853 }
854
855 private function getQuicksandURIPatternBlacklist() {
856 $applications = PhabricatorApplication::getAllApplications();
857
858 $blacklist = array();
859 foreach ($applications as $application) {
860 $blacklist[] = $application->getQuicksandURIPatternBlacklist();
861 }
862
863 // See T4340. Currently, Phortune and Auth both require pulling in external
864 // Javascript (for Stripe card management and Recaptcha, respectively).
865 // This can put us in a position where the user loads a page with a
866 // restrictive Content-Security-Policy, then uses Quicksand to navigate to
867 // a page which needs to load external scripts. For now, just blacklist
868 // these entire applications since we aren't giving up anything
869 // significant by doing so.
870
871 $blacklist[] = array(
872 '/phortune/.*',
873 '/auth/.*',
874 );
875
876 return array_mergev($blacklist);
877 }
878
879 private function getUserPreference($key, $default = null) {
880 $request = $this->getRequest();
881 if (!$request) {
882 return $default;
883 }
884
885 $user = $request->getUser();
886 if (!$user) {
887 return $default;
888 }
889
890 return $user->getUserSetting($key);
891 }
892
893 public function produceAphrontResponse() {
894 $controller = $this->getController();
895
896 $viewer = $this->getUser();
897 if ($viewer && $viewer->getPHID()) {
898 $object_phids = $this->pageObjects;
899 foreach ($object_phids as $object_phid) {
900 PhabricatorFeedStoryNotification::updateObjectNotificationViews(
901 $viewer,
902 $object_phid);
903 }
904 }
905
906 if ($this->getRequest()->isQuicksand()) {
907 $content = $this->renderForQuicksand();
908 $response = id(new AphrontAjaxResponse())
909 ->setContent($content);
910 } else {
911 // See T13247. Try to find some navigational menu items to create a
912 // mobile navigation menu from.
913 $application_menu = $controller->buildApplicationMenu();
914 if (!$application_menu) {
915 $navigation = $this->getNavigation();
916 if ($navigation) {
917 $application_menu = $navigation->getMenu();
918 }
919 }
920 $this->applicationMenu = $application_menu;
921
922 $content = $this->render();
923
924 $response = id(new AphrontWebpageResponse())
925 ->setContent($content)
926 ->setFrameable($this->getFrameable());
927 }
928
929 return $response;
930 }
931
932}