@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
at upstream/main 717 lines 19 kB view raw
1<?php 2 3final class PhabricatorMainMenuView extends AphrontView { 4 5 private $controller; 6 private $applicationMenu; 7 8 public function setApplicationMenu(PHUIListView $application_menu) { 9 $this->applicationMenu = $application_menu; 10 return $this; 11 } 12 13 public function getApplicationMenu() { 14 return $this->applicationMenu; 15 } 16 17 public function setController(PhabricatorController $controller) { 18 $this->controller = $controller; 19 return $this; 20 } 21 22 public function getController() { 23 return $this->controller; 24 } 25 26 private static function getFavicons() { 27 $refs = array(); 28 29 $refs['favicon'] = id(new PhabricatorFaviconRef()) 30 ->setWidth(64) 31 ->setHeight(64); 32 33 $refs['message_favicon'] = id(new PhabricatorFaviconRef()) 34 ->setWidth(64) 35 ->setHeight(64) 36 ->setEmblems( 37 array( 38 'dot-pink', 39 null, 40 null, 41 null, 42 )); 43 44 id(new PhabricatorFaviconRefQuery()) 45 ->withRefs($refs) 46 ->execute(); 47 48 return mpull($refs, 'getURI'); 49 } 50 51 public function render() { 52 $viewer = $this->getViewer(); 53 54 require_celerity_resource('phabricator-main-menu-view'); 55 56 $header_id = celerity_generate_unique_node_id(); 57 $menu_bar = array(); 58 $alerts = array(); 59 $search_button = ''; 60 $app_button = ''; 61 $aural = null; 62 63 $is_full = $this->isFullSession($viewer); 64 65 if ($is_full) { 66 list($menu, $dropdowns, $aural) = $this->renderNotificationMenu(); 67 if (array_filter($menu)) { 68 $alerts[] = $menu; 69 } 70 $menu_bar = array_merge($menu_bar, $dropdowns); 71 $app_button = $this->renderApplicationMenuButton(); 72 $search_button = $this->renderSearchMenuButton($header_id); 73 } else if (!$viewer->isLoggedIn()) { 74 $app_button = $this->renderApplicationMenuButton(); 75 if (PhabricatorEnv::getEnvConfig('policy.allow-public')) { 76 $search_button = $this->renderSearchMenuButton($header_id); 77 } 78 } 79 80 if ($search_button) { 81 $search_menu = $this->renderPhabricatorSearchMenu(); 82 } else { 83 $search_menu = null; 84 } 85 86 if ($alerts) { 87 $alerts = javelin_tag( 88 'div', 89 array( 90 'class' => 'phabricator-main-menu-alerts', 91 ), 92 $alerts); 93 } 94 95 if ($aural) { 96 $aural = javelin_tag( 97 'span', 98 array( 99 'aural' => true, 100 ), 101 phutil_implode_html(' ', $aural)); 102 } 103 104 $extensions = PhabricatorMainMenuBarExtension::getAllEnabledExtensions(); 105 foreach ($extensions as $extension) { 106 $extension 107 ->setViewer($viewer) 108 ->setIsFullSession($is_full); 109 110 $controller = $this->getController(); 111 if ($controller) { 112 $extension->setController($controller); 113 $application = $controller->getCurrentApplication(); 114 if ($application) { 115 $extension->setApplication($application); 116 } 117 } 118 } 119 120 if (!$is_full) { 121 foreach ($extensions as $key => $extension) { 122 if ($extension->shouldRequireFullSession()) { 123 unset($extensions[$key]); 124 } 125 } 126 } 127 128 foreach ($extensions as $key => $extension) { 129 if (!$extension->isExtensionEnabledForViewer($extension->getViewer())) { 130 unset($extensions[$key]); 131 } 132 } 133 134 $menus = array(); 135 foreach ($extensions as $extension) { 136 foreach ($extension->buildMainMenus() as $menu) { 137 $menus[] = $menu; 138 } 139 } 140 141 // Because we display these with "float: right", reverse their order before 142 // rendering them into the document so that the extension order and display 143 // order are the same. 144 $menus = array_reverse($menus); 145 146 foreach ($menus as $menu) { 147 $menu_bar[] = $menu; 148 } 149 150 $classes = array(); 151 $classes[] = 'phabricator-main-menu'; 152 $classes[] = 'phabricator-main-menu-background'; 153 154 return phutil_tag( 155 'div', 156 array( 157 'class' => implode(' ', $classes), 158 'id' => $header_id, 159 ), 160 array( 161 $app_button, 162 $search_button, 163 $this->renderPhabricatorLogo(), 164 $alerts, 165 $aural, 166 $search_menu, 167 $menu_bar, 168 )); 169 } 170 171 private function renderSearch() { 172 $viewer = $this->getViewer(); 173 174 $result = null; 175 176 $keyboard_config = array( 177 'helpURI' => '/help/keyboardshortcut/', 178 ); 179 180 if ($viewer->isLoggedIn()) { 181 $show_search = $viewer->isUserActivated(); 182 } else { 183 $show_search = PhabricatorEnv::getEnvConfig('policy.allow-public'); 184 } 185 186 if ($show_search) { 187 $search = new PhabricatorMainMenuSearchView(); 188 $search->setViewer($viewer); 189 190 $application = null; 191 $controller = $this->getController(); 192 if ($controller) { 193 $application = $controller->getCurrentApplication(); 194 } 195 if ($application) { 196 $search->setApplication($application); 197 } 198 199 $result = $search; 200 $keyboard_config['searchID'] = $search->getID(); 201 } 202 203 $keyboard_config['pht'] = array( 204 '/' => pht('Give keyboard focus to the search box.'), 205 '?' => pht('Show keyboard shortcut help for the current page.'), 206 ); 207 208 Javelin::initBehavior( 209 'phabricator-keyboard-shortcuts', 210 $keyboard_config); 211 212 if ($result) { 213 $result = id(new PHUIListItemView()) 214 ->addClass('phabricator-main-menu-search') 215 ->appendChild($result); 216 } 217 218 return $result; 219 } 220 221 public function renderApplicationMenuButton() { 222 $dropdown = $this->renderApplicationMenu(); 223 if (!$dropdown) { 224 return null; 225 } 226 227 return id(new PHUIButtonView()) 228 ->setTag('a') 229 ->setHref('#') 230 ->setIcon('fa-bars') 231 ->addClass('phabricator-core-user-menu') 232 ->addClass('phabricator-core-user-mobile-menu') 233 ->setNoCSS(true) 234 ->setDropdownMenu($dropdown) 235 ->setAuralLabel(pht('Page Menu')); 236 } 237 238 private function renderApplicationMenu() { 239 $viewer = $this->getViewer(); 240 $view = $this->getApplicationMenu(); 241 if ($view) { 242 $items = $view->getItems(); 243 $view = id(new PhabricatorActionListView()) 244 ->setViewer($viewer); 245 foreach ($items as $item) { 246 $view->addAction( 247 id(new PhabricatorActionView()) 248 ->setName($item->getName()) 249 ->setHref($item->getHref()) 250 ->setType($item->getType())); 251 } 252 } 253 return $view; 254 } 255 256 public function renderSearchMenuButton($header_id) { 257 $button_id = celerity_generate_unique_node_id(); 258 return javelin_tag( 259 'a', 260 array( 261 'class' => 'phabricator-main-menu-search-button '. 262 'phabricator-expand-application-menu', 263 'sigil' => 'jx-toggle-class', 264 'meta' => array( 265 'map' => array( 266 $header_id => 'phabricator-search-menu-expanded', 267 $button_id => 'menu-icon-selected', 268 ), 269 ), 270 ), 271 phutil_tag( 272 'span', 273 array( 274 'class' => 'phabricator-menu-button-icon phui-icon-view '. 275 'phui-font-fa fa-search', 276 'id' => $button_id, 277 ), 278 '')); 279 } 280 281 private function renderPhabricatorSearchMenu() { 282 283 $view = new PHUIListView(); 284 $view->addClass('phabricator-search-menu'); 285 286 $search = $this->renderSearch(); 287 if ($search) { 288 $view->addMenuItem($search); 289 } 290 291 return $view; 292 } 293 294 private function renderPhabricatorLogo() { 295 $logo_style = array(); 296 $custom_header = PhabricatorCustomLogoConfigType::getLogoImagePHID(); 297 if ($custom_header) { 298 $viewer = $this->getViewer(); 299 $logo_uri = PhabricatorCustomLogoConfigType::getLogoURI($viewer); 300 $logo_style[] = 'background-size: 40px 40px;'; 301 $logo_style[] = 'background-position: 0 0;'; 302 $logo_style[] = 'background-image: url('.$logo_uri.')'; 303 } 304 305 $logo_node = phutil_tag( 306 'span', 307 array( 308 'class' => 'phabricator-main-menu-project-logo', 309 'style' => implode(' ', $logo_style), 310 )); 311 312 $wordmark_text = PhabricatorCustomLogoConfigType::getLogoWordmark(); 313 if (!phutil_nonempty_string($wordmark_text)) { 314 $wordmark_text = PlatformSymbols::getPlatformServerName(); 315 } 316 317 $wordmark_node = phutil_tag( 318 'span', 319 array( 320 'class' => 'phabricator-wordmark', 321 ), 322 $wordmark_text); 323 324 return phutil_tag( 325 'a', 326 array( 327 'class' => 'phabricator-main-menu-brand', 328 'href' => '/', 329 ), 330 array( 331 javelin_tag( 332 'span', 333 array( 334 'aural' => true, 335 ), 336 pht('Home')), 337 $logo_node, 338 $wordmark_node, 339 )); 340 } 341 342 private function renderNotificationMenu() { 343 $viewer = $this->getViewer(); 344 345 require_celerity_resource('phabricator-notification-css'); 346 require_celerity_resource('phabricator-notification-menu-css'); 347 348 $container_classes = array('alert-notifications'); 349 $aural = array(); 350 351 $dropdown_query = id(new AphlictDropdownDataQuery()) 352 ->setViewer($viewer); 353 $dropdown_data = $dropdown_query->execute(); 354 355 $message_tag = ''; 356 $message_notification_dropdown = ''; 357 $conpherence_app = PhabricatorConpherenceApplication::class; 358 $conpherence_data = $dropdown_data[$conpherence_app]; 359 if ($conpherence_data['isInstalled']) { 360 $message_id = celerity_generate_unique_node_id(); 361 $message_count_id = celerity_generate_unique_node_id(); 362 $message_dropdown_id = celerity_generate_unique_node_id(); 363 364 $message_count_number = $conpherence_data['rawCount']; 365 366 if ($message_count_number) { 367 $aural[] = phutil_tag( 368 'a', 369 array( 370 'href' => '/conpherence/', 371 ), 372 pht( 373 '%s unread messages.', 374 new PhutilNumber($message_count_number))); 375 } else { 376 $aural[] = pht('No messages.'); 377 } 378 379 $message_count_tag = phutil_tag( 380 'span', 381 array( 382 'id' => $message_count_id, 383 'class' => 'phabricator-main-menu-message-count', 384 ), 385 $conpherence_data['count']); 386 387 $message_icon_tag = javelin_tag( 388 'span', 389 array( 390 'class' => 'phabricator-main-menu-message-icon phui-icon-view '. 391 'phui-font-fa fa-comments', 392 'sigil' => 'menu-icon', 393 ), 394 ''); 395 396 if ($message_count_number) { 397 $container_classes[] = 'message-unread'; 398 } 399 400 $message_tag = phutil_tag( 401 'a', 402 array( 403 'href' => '/conpherence/', 404 'class' => implode(' ', $container_classes), 405 'id' => $message_id, 406 'aria-label' => pht('Chat Messages'), 407 ), 408 array( 409 $message_icon_tag, 410 $message_count_tag, 411 )); 412 413 Javelin::initBehavior( 414 'aphlict-dropdown', 415 array( 416 'bubbleID' => $message_id, 417 'countID' => $message_count_id, 418 'dropdownID' => $message_dropdown_id, 419 'loadingText' => pht('Loading...'), 420 'uri' => '/conpherence/panel/', 421 'countType' => $conpherence_data['countType'], 422 'countNumber' => $message_count_number, 423 'unreadClass' => 'message-unread', 424 ) + self::getFavicons()); 425 426 $message_notification_dropdown = javelin_tag( 427 'div', 428 array( 429 'id' => $message_dropdown_id, 430 'class' => 'phabricator-notification-menu', 431 'sigil' => 'phabricator-notification-menu', 432 'style' => 'display: none;', 433 ), 434 ''); 435 } 436 437 $bubble_tag = ''; 438 $notification_dropdown = ''; 439 $notification_app = PhabricatorNotificationsApplication::class; 440 $notification_data = $dropdown_data[$notification_app]; 441 if ($notification_data['isInstalled']) { 442 $count_id = celerity_generate_unique_node_id(); 443 $dropdown_id = celerity_generate_unique_node_id(); 444 $bubble_id = celerity_generate_unique_node_id(); 445 446 $count_number = $notification_data['rawCount']; 447 448 if ($count_number) { 449 $aural[] = phutil_tag( 450 'a', 451 array( 452 'href' => '/notification/', 453 ), 454 pht( 455 '%s unread notifications.', 456 new PhutilNumber($count_number))); 457 } else { 458 $aural[] = pht('No notifications.'); 459 } 460 461 $count_tag = phutil_tag( 462 'span', 463 array( 464 'id' => $count_id, 465 'class' => 'phabricator-main-menu-alert-count', 466 ), 467 $notification_data['count']); 468 469 $icon_tag = javelin_tag( 470 'span', 471 array( 472 'class' => 'phabricator-main-menu-alert-icon phui-icon-view '. 473 'phui-font-fa fa-bell', 474 'sigil' => 'menu-icon', 475 ), 476 ''); 477 478 if ($count_number) { 479 $container_classes[] = 'alert-unread'; 480 } 481 482 $bubble_tag = phutil_tag( 483 'a', 484 array( 485 'href' => '/notification/', 486 'class' => implode(' ', $container_classes), 487 'id' => $bubble_id, 488 'aria-label' => pht('Notifications'), 489 ), 490 array($icon_tag, $count_tag)); 491 492 Javelin::initBehavior( 493 'aphlict-dropdown', 494 array( 495 'bubbleID' => $bubble_id, 496 'countID' => $count_id, 497 'dropdownID' => $dropdown_id, 498 'loadingText' => pht('Loading...'), 499 'uri' => '/notification/panel/', 500 'countType' => $notification_data['countType'], 501 'countNumber' => $count_number, 502 'unreadClass' => 'alert-unread', 503 ) + self::getFavicons()); 504 505 $notification_dropdown = javelin_tag( 506 'div', 507 array( 508 'id' => $dropdown_id, 509 'class' => 'phabricator-notification-menu', 510 'sigil' => 'phabricator-notification-menu', 511 'style' => 'display: none;', 512 ), 513 ''); 514 } 515 516 // Admin Level Urgent Notification Channel 517 $setup_tag = ''; 518 $setup_notification_dropdown = ''; 519 if ($viewer && $viewer->getIsAdmin()) { 520 $open = PhabricatorSetupCheck::getOpenSetupIssueKeys(); 521 if ($open) { 522 $setup_id = celerity_generate_unique_node_id(); 523 $setup_count_id = celerity_generate_unique_node_id(); 524 $setup_dropdown_id = celerity_generate_unique_node_id(); 525 526 $setup_count_number = count($open); 527 528 if ($setup_count_number) { 529 $aural[] = phutil_tag( 530 'a', 531 array( 532 'href' => '/config/issue/', 533 ), 534 pht( 535 '%s unresolved issues.', 536 new PhutilNumber($setup_count_number))); 537 } else { 538 $aural[] = pht('No issues.'); 539 } 540 541 $setup_count_tag = phutil_tag( 542 'span', 543 array( 544 'id' => $setup_count_id, 545 'class' => 'phabricator-main-menu-setup-count', 546 ), 547 $setup_count_number); 548 549 $setup_icon_tag = javelin_tag( 550 'span', 551 array( 552 'class' => 'phabricator-main-menu-setup-icon phui-icon-view '. 553 'phui-font-fa fa-exclamation-circle', 554 'sigil' => 'menu-icon', 555 ), 556 ''); 557 558 if ($setup_count_number) { 559 $container_classes[] = 'setup-unread'; 560 } 561 562 $setup_tag = phutil_tag( 563 'a', 564 array( 565 'href' => '/config/issue/', 566 'class' => implode(' ', $container_classes), 567 'id' => $setup_id, 568 'aria-label' => pht('Unresolved Setup Issues'), 569 ), 570 array( 571 $setup_icon_tag, 572 $setup_count_tag, 573 )); 574 575 Javelin::initBehavior( 576 'aphlict-dropdown', 577 array( 578 'bubbleID' => $setup_id, 579 'countID' => $setup_count_id, 580 'dropdownID' => $setup_dropdown_id, 581 'loadingText' => pht('Loading...'), 582 'uri' => '/config/issue/panel/', 583 'countType' => null, 584 'countNumber' => null, 585 'unreadClass' => 'setup-unread', 586 ) + self::getFavicons()); 587 588 $setup_notification_dropdown = javelin_tag( 589 'div', 590 array( 591 'id' => $setup_dropdown_id, 592 'class' => 'phabricator-notification-menu', 593 'sigil' => 'phabricator-notification-menu', 594 'style' => 'display: none;', 595 ), 596 ''); 597 } 598 } 599 600 $user_dropdown = null; 601 $user_tag = null; 602 if ($viewer->isLoggedIn()) { 603 if (!$viewer->getIsEmailVerified()) { 604 $bubble_id = celerity_generate_unique_node_id(); 605 $count_id = celerity_generate_unique_node_id(); 606 $dropdown_id = celerity_generate_unique_node_id(); 607 608 $settings_uri = id(new PhabricatorEmailAddressesSettingsPanel()) 609 ->setViewer($viewer) 610 ->setUser($viewer) 611 ->getPanelURI(); 612 613 $user_icon = javelin_tag( 614 'span', 615 array( 616 'class' => 'phabricator-main-menu-setup-icon phui-icon-view '. 617 'phui-font-fa fa-user', 618 'sigil' => 'menu-icon', 619 )); 620 621 $user_count = javelin_tag( 622 'span', 623 array( 624 'class' => 'phabricator-main-menu-setup-count', 625 'id' => $count_id, 626 ), 627 1); 628 629 $user_tag = phutil_tag( 630 'a', 631 array( 632 'href' => $settings_uri, 633 'class' => 'setup-unread', 634 'id' => $bubble_id, 635 'aria-label' => pht('Account Setup Issues'), 636 ), 637 array( 638 $user_icon, 639 $user_count, 640 )); 641 642 Javelin::initBehavior( 643 'aphlict-dropdown', 644 array( 645 'bubbleID' => $bubble_id, 646 'countID' => $count_id, 647 'dropdownID' => $dropdown_id, 648 'loadingText' => pht('Loading...'), 649 'uri' => '/settings/issue/', 650 'unreadClass' => 'setup-unread', 651 )); 652 653 $user_dropdown = javelin_tag( 654 'div', 655 array( 656 'id' => $dropdown_id, 657 'class' => 'phabricator-notification-menu', 658 'sigil' => 'phabricator-notification-menu', 659 'style' => 'display: none;', 660 )); 661 } 662 } 663 664 $dropdowns = array( 665 $notification_dropdown, 666 $message_notification_dropdown, 667 $setup_notification_dropdown, 668 $user_dropdown, 669 ); 670 671 return array( 672 array( 673 $bubble_tag, 674 $message_tag, 675 $setup_tag, 676 $user_tag, 677 ), 678 $dropdowns, 679 $aural, 680 ); 681 } 682 683 private function isFullSession(PhabricatorUser $viewer) { 684 if (!$viewer->isLoggedIn()) { 685 return false; 686 } 687 688 if (!$viewer->isUserActivated()) { 689 return false; 690 } 691 692 if (!$viewer->hasSession()) { 693 return false; 694 } 695 696 $session = $viewer->getSession(); 697 if ($session->getIsPartial()) { 698 return false; 699 } 700 701 if (!$session->getSignedLegalpadDocuments()) { 702 return false; 703 } 704 705 $mfa_key = 'security.require-multi-factor-auth'; 706 $need_mfa = PhabricatorEnv::getEnvConfig($mfa_key); 707 if ($need_mfa) { 708 $have_mfa = $viewer->getIsEnrolledInMultiFactor(); 709 if (!$have_mfa) { 710 return false; 711 } 712 } 713 714 return true; 715 } 716 717}