@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
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 669 lines 18 kB view raw
1<?php 2 3final class PhabricatorApplicationTransactionCommentView 4 extends AphrontView { 5 6 private $submitButtonName; 7 private $action; 8 9 private $previewPanelID; 10 private $previewTimelineID; 11 private $previewToggleID; 12 private $formID; 13 private $commentID; 14 private $draft; 15 private $requestURI; 16 private $showPreview = true; 17 private $object; 18 private $headerText; 19 private $noPermission; 20 private $fullWidth; 21 private $infoView; 22 private $editEngine; 23 private $editEngineLock; 24 private $noBorder; 25 private $requiresMFA; 26 27 private $currentVersion; 28 private $versionedDraft; 29 private $commentActions; 30 private $commentActionGroups = array(); 31 private $transactionTimeline; 32 33 /** 34 * Set object in which this comment textarea field is displayed 35 */ 36 public function setObject($object) { 37 $this->object = $object; 38 return $this; 39 } 40 41 /** 42 * Get object in which this comment textarea is displayed 43 */ 44 public function getObject() { 45 return $this->object; 46 } 47 48 public function setShowPreview($show_preview) { 49 $this->showPreview = $show_preview; 50 return $this; 51 } 52 53 public function getShowPreview() { 54 return $this->showPreview; 55 } 56 57 public function setRequestURI(PhutilURI $request_uri) { 58 $this->requestURI = $request_uri; 59 return $this; 60 } 61 public function getRequestURI() { 62 return $this->requestURI; 63 } 64 65 public function setCurrentVersion($current_version) { 66 $this->currentVersion = $current_version; 67 return $this; 68 } 69 70 public function getCurrentVersion() { 71 return $this->currentVersion; 72 } 73 74 public function setVersionedDraft( 75 PhabricatorVersionedDraft $versioned_draft) { 76 $this->versionedDraft = $versioned_draft; 77 return $this; 78 } 79 80 public function getVersionedDraft() { 81 return $this->versionedDraft; 82 } 83 84 public function setDraft(PhabricatorDraft $draft) { 85 $this->draft = $draft; 86 return $this; 87 } 88 89 public function getDraft() { 90 return $this->draft; 91 } 92 93 public function setSubmitButtonName($submit_button_name) { 94 $this->submitButtonName = $submit_button_name; 95 return $this; 96 } 97 98 public function getSubmitButtonName() { 99 return $this->submitButtonName; 100 } 101 102 public function setAction($action) { 103 $this->action = $action; 104 return $this; 105 } 106 107 public function getAction() { 108 return $this->action; 109 } 110 111 public function setHeaderText($text) { 112 $this->headerText = $text; 113 return $this; 114 } 115 116 public function setFullWidth($fw) { 117 $this->fullWidth = $fw; 118 return $this; 119 } 120 121 public function setInfoView(PHUIInfoView $info_view) { 122 $this->infoView = $info_view; 123 return $this; 124 } 125 126 public function getInfoView() { 127 return $this->infoView; 128 } 129 130 /** 131 * @param array<PhabricatorEditEngineCommentAction> $comment_actions 132 */ 133 public function setCommentActions(array $comment_actions) { 134 assert_instances_of( 135 $comment_actions, 136 PhabricatorEditEngineCommentAction::class); 137 $this->commentActions = $comment_actions; 138 return $this; 139 } 140 141 public function getCommentActions() { 142 return $this->commentActions; 143 } 144 145 /** 146 * @param array<PhabricatorEditEngineCommentActionGroup> $groups 147 */ 148 public function setCommentActionGroups(array $groups) { 149 assert_instances_of( 150 $groups, 151 PhabricatorEditEngineCommentActionGroup::class); 152 $this->commentActionGroups = $groups; 153 return $this; 154 } 155 156 public function getCommentActionGroups() { 157 return $this->commentActionGroups; 158 } 159 160 public function setNoPermission($no_permission) { 161 $this->noPermission = $no_permission; 162 return $this; 163 } 164 165 public function getNoPermission() { 166 return $this->noPermission; 167 } 168 169 public function setEditEngine(PhabricatorEditEngine $edit_engine) { 170 $this->editEngine = $edit_engine; 171 return $this; 172 } 173 174 public function getEditEngine() { 175 return $this->editEngine; 176 } 177 178 public function setEditEngineLock(PhabricatorEditEngineLock $lock) { 179 $this->editEngineLock = $lock; 180 return $this; 181 } 182 183 public function getEditEngineLock() { 184 return $this->editEngineLock; 185 } 186 187 public function setRequiresMFA($requires_mfa) { 188 $this->requiresMFA = $requires_mfa; 189 return $this; 190 } 191 192 public function getRequiresMFA() { 193 return $this->requiresMFA; 194 } 195 196 public function setTransactionTimeline( 197 PhabricatorApplicationTransactionView $timeline) { 198 199 $timeline->setQuoteTargetID($this->getCommentID()); 200 if ($this->getNoPermission() || $this->getEditEngineLock()) { 201 $timeline->setShouldTerminate(true); 202 } 203 204 $this->transactionTimeline = $timeline; 205 return $this; 206 } 207 208 public function render() { 209 if ($this->getNoPermission()) { 210 return null; 211 } 212 213 $lock = $this->getEditEngineLock(); 214 if ($lock) { 215 return id(new PHUIInfoView()) 216 ->setSeverity(PHUIInfoView::SEVERITY_WARNING) 217 ->setErrors( 218 array( 219 $lock->getLockedObjectDisplayText(), 220 )); 221 } 222 223 $viewer = $this->getViewer(); 224 if (!$viewer->isLoggedIn()) { 225 $uri = id(new PhutilURI('/login/')) 226 ->replaceQueryParam('next', (string)$this->getRequestURI()); 227 return id(new PHUIObjectBoxView()) 228 ->setFlush(true) 229 ->appendChild( 230 javelin_tag( 231 'a', 232 array( 233 'class' => 'login-to-comment button', 234 'href' => $uri, 235 ), 236 pht('Log In to Comment'))); 237 } 238 239 if ($this->getRequiresMFA()) { 240 if (!$viewer->getIsEnrolledInMultiFactor()) { 241 $viewer->updateMultiFactorEnrollment(); 242 if (!$viewer->getIsEnrolledInMultiFactor()) { 243 $messages = array(); 244 $messages[] = pht( 245 'You must provide multi-factor credentials to comment or make '. 246 'changes, but you do not have multi-factor authentication '. 247 'configured on your account.'); 248 $messages[] = pht( 249 'To continue, configure multi-factor authentication in Settings.'); 250 251 return id(new PHUIInfoView()) 252 ->setSeverity(PHUIInfoView::SEVERITY_MFA) 253 ->setErrors($messages); 254 } 255 } 256 } 257 258 $data = array(); 259 260 $comment = $this->renderCommentPanel(); 261 262 if ($this->getShowPreview()) { 263 $preview = $this->renderPreviewPanel(); 264 } else { 265 $preview = null; 266 } 267 268 if (!$this->getCommentActions()) { 269 Javelin::initBehavior( 270 'phabricator-transaction-comment-form', 271 array( 272 'formID' => $this->getFormID(), 273 'timelineID' => $this->getPreviewTimelineID(), 274 'panelID' => $this->getPreviewPanelID(), 275 'showPreview' => $this->getShowPreview(), 276 'actionURI' => $this->getAction(), 277 )); 278 } 279 280 require_celerity_resource('phui-comment-form-css'); 281 $image_uri = $viewer->getProfileImageURI(); 282 $image = javelin_tag( 283 'div', 284 array( 285 'style' => 'background-image: url('.$image_uri.')', 286 'class' => 'phui-comment-image', 287 'aural' => false, 288 )); 289 $wedge = phutil_tag( 290 'div', 291 array( 292 'class' => 'phui-timeline-wedge', 293 ), 294 ''); 295 296 $badge_view = $this->renderBadgeView(); 297 298 $anchor = id(new PhabricatorAnchorView()) 299 ->setAnchorName('reply'); 300 301 $comment_box = id(new PHUIObjectBoxView()) 302 ->setFlush(true) 303 ->addClass('phui-comment-form-view') 304 ->addSigil('phui-comment-form') 305 ->appendChild($anchor) 306 ->appendChild( 307 phutil_tag( 308 'h2', 309 array( 310 'class' => 'aural-only', 311 ), 312 pht('Add Comment'))) 313 ->appendChild($image) 314 ->appendChild($badge_view) 315 ->appendChild($wedge) 316 ->appendChild($comment); 317 318 return array($comment_box, $preview); 319 } 320 321 private function renderCommentPanel() { 322 $viewer = $this->getViewer(); 323 $engine = $this->getEditEngine(); 324 // In a few rare cases PhabricatorApplicationTransactionCommentView gets 325 // initiated in a View or Controller class. Don't crash in that case. 326 if ($engine) { 327 $placeholder_text = $engine 328 ->getCommentFieldPlaceholderText($this->getObject()); 329 } else { 330 $placeholder_text = ''; 331 } 332 333 $remarkup_control = id(new PhabricatorRemarkupControl()) 334 ->setViewer($viewer) 335 ->setID($this->getCommentID()) 336 ->addClass('phui-comment-fullwidth-control') 337 ->addClass('phui-comment-textarea-control') 338 ->setSurroundingObject($this->getObject()) 339 ->setCanPin(true) 340 ->setPlaceholder($placeholder_text) 341 ->setName('comment'); 342 343 $draft_comment = ''; 344 $draft_metadata = array(); 345 $draft_key = null; 346 347 $legacy_draft = $this->getDraft(); 348 if ($legacy_draft) { 349 $draft_comment = $legacy_draft->getDraft(); 350 $draft_key = $legacy_draft->getDraftKey(); 351 } 352 353 $versioned_draft = $this->getVersionedDraft(); 354 if ($versioned_draft) { 355 $draft_comment = $versioned_draft->getProperty( 356 'comment', 357 $draft_comment); 358 $draft_metadata = $versioned_draft->getProperty( 359 'metadata', 360 $draft_metadata); 361 } 362 363 $remarkup_control->setValue($draft_comment); 364 365 if (!is_array($draft_metadata)) { 366 $draft_metadata = array(); 367 } 368 $remarkup_control->setRemarkupMetadata($draft_metadata); 369 370 if (!$this->getObject()->getPHID()) { 371 throw new PhutilInvalidStateException('setObjectPHID', 'render'); 372 } 373 374 $version_key = PhabricatorVersionedDraft::KEY_VERSION; 375 $version_value = $this->getCurrentVersion(); 376 377 $form = id(new AphrontFormView()) 378 ->setViewer($viewer) 379 ->addSigil('transaction-append') 380 ->setWorkflow(true) 381 ->setFullWidth($this->fullWidth) 382 ->setMetadata( 383 array( 384 'objectPHID' => $this->getObject()->getPHID(), 385 )) 386 ->setAction($this->getAction()) 387 ->setID($this->getFormID()) 388 ->addHiddenInput('__draft__', $draft_key) 389 ->addHiddenInput($version_key, $version_value); 390 391 $comment_actions = $this->getCommentActions(); 392 if ($comment_actions) { 393 $action_map = array(); 394 $type_map = array(); 395 396 $comment_actions = mpull($comment_actions, null, 'getKey'); 397 398 $draft_actions = array(); 399 $draft_keys = array(); 400 if ($versioned_draft) { 401 $draft_actions = $versioned_draft->getProperty('actions', array()); 402 403 if (!is_array($draft_actions)) { 404 $draft_actions = array(); 405 } 406 407 foreach ($draft_actions as $action) { 408 $type = idx($action, 'type'); 409 $comment_action = idx($comment_actions, $type); 410 if (!$comment_action) { 411 continue; 412 } 413 414 $value = idx($action, 'value'); 415 $comment_action->setValue($value); 416 417 $draft_keys[] = $type; 418 } 419 } 420 421 foreach ($comment_actions as $key => $comment_action) { 422 $key = $comment_action->getKey(); 423 $label = $comment_action->getLabel(); 424 425 $action_map[$key] = array( 426 'key' => $key, 427 'label' => $label, 428 'type' => $comment_action->getPHUIXControlType(), 429 'spec' => $comment_action->getPHUIXControlSpecification(), 430 'initialValue' => $comment_action->getInitialValue(), 431 'groupKey' => $comment_action->getGroupKey(), 432 'conflictKey' => $comment_action->getConflictKey(), 433 'auralLabel' => pht('Remove Action: %s', $label), 434 'buttonText' => $comment_action->getSubmitButtonText(), 435 ); 436 437 $type_map[$key] = $comment_action; 438 } 439 440 $options = $this->newCommentActionOptions($action_map); 441 442 $action_id = celerity_generate_unique_node_id(); 443 $input_id = celerity_generate_unique_node_id(); 444 $place_id = celerity_generate_unique_node_id(); 445 446 $form->appendChild( 447 phutil_tag( 448 'input', 449 array( 450 'type' => 'hidden', 451 'name' => 'editengine.actions', 452 'id' => $input_id, 453 ))); 454 455 $invisi_bar = phutil_tag( 456 'div', 457 array( 458 'id' => $place_id, 459 'class' => 'phui-comment-control-stack', 460 )); 461 462 $action_select = id(new AphrontFormSelectControl()) 463 ->addClass('phui-comment-fullwidth-control') 464 ->addClass('phui-comment-action-control') 465 ->setAriaLabel(pht('Comment Action Options')) 466 ->setID($action_id) 467 ->setOptions($options); 468 469 $action_bar = phutil_tag( 470 'div', 471 array( 472 'class' => 'phui-comment-action-bar grouped', 473 ), 474 array( 475 $action_select, 476 )); 477 478 $form->appendChild($action_bar); 479 480 $info_view = $this->getInfoView(); 481 if ($info_view) { 482 $form->appendChild($info_view); 483 } 484 485 if ($this->getRequiresMFA()) { 486 $message = pht( 487 'You will be required to provide multi-factor credentials to '. 488 'comment or make changes.'); 489 490 $form->appendChild( 491 id(new PHUIInfoView()) 492 ->setSeverity(PHUIInfoView::SEVERITY_MFA) 493 ->setErrors(array($message))); 494 } 495 496 $form->appendChild($invisi_bar); 497 $form->addClass('phui-comment-has-actions'); 498 499 $timeline = $this->transactionTimeline; 500 501 $view_data = array(); 502 if ($timeline) { 503 $view_data = $timeline->getViewData(); 504 } 505 506 Javelin::initBehavior( 507 'comment-actions', 508 array( 509 'actionID' => $action_id, 510 'inputID' => $input_id, 511 'formID' => $this->getFormID(), 512 'placeID' => $place_id, 513 'panelID' => $this->getPreviewPanelID(), 514 'timelineID' => $this->getPreviewTimelineID(), 515 'actions' => $action_map, 516 'showPreview' => $this->getShowPreview(), 517 'actionURI' => $this->getAction(), 518 'drafts' => $draft_keys, 519 'defaultButtonText' => $this->getSubmitButtonName(), 520 'viewData' => $view_data, 521 )); 522 } 523 524 $submit_button = id(new AphrontFormSubmitControl()) 525 ->addClass('phui-comment-fullwidth-control') 526 ->addClass('phui-comment-submit-control') 527 ->setValue($this->getSubmitButtonName()); 528 529 $form 530 ->appendChild($remarkup_control) 531 ->appendChild( 532 id(new AphrontFormSubmitControl()) 533 ->addClass('phui-comment-fullwidth-control') 534 ->addClass('phui-comment-submit-control') 535 ->addSigil('submit-transactions') 536 ->setValue($this->getSubmitButtonName())); 537 538 return $form; 539 } 540 541 private function renderPreviewPanel() { 542 543 $preview = id(new PHUITimelineView()) 544 ->setID($this->getPreviewTimelineID()); 545 546 return phutil_tag( 547 'div', 548 array( 549 'id' => $this->getPreviewPanelID(), 550 'style' => 'display: none', 551 'class' => 'phui-comment-preview-view', 552 ), 553 $preview); 554 } 555 556 private function getPreviewPanelID() { 557 if (!$this->previewPanelID) { 558 $this->previewPanelID = celerity_generate_unique_node_id(); 559 } 560 return $this->previewPanelID; 561 } 562 563 private function getPreviewTimelineID() { 564 if (!$this->previewTimelineID) { 565 $this->previewTimelineID = celerity_generate_unique_node_id(); 566 } 567 return $this->previewTimelineID; 568 } 569 570 public function setFormID($id) { 571 $this->formID = $id; 572 return $this; 573 } 574 575 private function getFormID() { 576 if (!$this->formID) { 577 $this->formID = celerity_generate_unique_node_id(); 578 } 579 return $this->formID; 580 } 581 582 private function getCommentID() { 583 if (!$this->commentID) { 584 $this->commentID = celerity_generate_unique_node_id(); 585 } 586 return $this->commentID; 587 } 588 589 private function newCommentActionOptions(array $action_map) { 590 $options = array(); 591 $options['+'] = pht('Add Action...'); 592 593 // Merge options into groups. 594 $groups = array(); 595 foreach ($action_map as $key => $item) { 596 $group_key = $item['groupKey'] ?? ''; 597 if (!isset($groups[$group_key])) { 598 $groups[$group_key] = array(); 599 } 600 $groups[$group_key][$key] = $item; 601 } 602 603 $group_specs = $this->getCommentActionGroups(); 604 $group_labels = mpull($group_specs, 'getLabel', 'getKey'); 605 606 // Reorder groups to put them in the same order as the recognized 607 // group definitions. 608 $groups = array_select_keys($groups, array_keys($group_labels)) + $groups; 609 610 // Move options with no group to the end. 611 $default_group = idx($groups, ''); 612 if ($default_group) { 613 unset($groups['']); 614 $groups[''] = $default_group; 615 } 616 617 foreach ($groups as $group_key => $group_items) { 618 if (strlen($group_key)) { 619 $group_label = idx($group_labels, $group_key, $group_key); 620 $options[$group_label] = ipull($group_items, 'label'); 621 } else { 622 foreach ($group_items as $key => $item) { 623 $options[$key] = $item['label']; 624 } 625 } 626 } 627 628 return $options; 629 } 630 631 private function renderBadgeView() { 632 $user = $this->getUser(); 633 $can_use_badges = PhabricatorApplication::isClassInstalledForViewer( 634 PhabricatorBadgesApplication::class, 635 $user); 636 if (!$can_use_badges) { 637 return null; 638 } 639 640 // Pull Badges from UserCache 641 $badges = $user->getRecentBadgeAwards(); 642 $badge_view = null; 643 if ($badges) { 644 $badge_list = array(); 645 foreach ($badges as $badge) { 646 $badge_view = id(new PHUIBadgeMiniView()) 647 ->setIcon($badge['icon']) 648 ->setQuality($badge['quality']) 649 ->setHeader($badge['name']) 650 ->setTipDirection('E') 651 ->setHref('/badges/view/'.$badge['id'].'/'); 652 653 $badge_list[] = $badge_view; 654 } 655 $flex = new PHUIBadgeBoxView(); 656 $flex->addItems($badge_list); 657 $flex->setCollapsed(true); 658 $badge_view = phutil_tag( 659 'div', 660 array( 661 'class' => 'phui-timeline-badges', 662 ), 663 $flex); 664 } 665 666 return $badge_view; 667 } 668 669}