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

Merge branch 'master' into redesign-2015

+3214 -2555
+3 -4
.arcconfig
··· 1 1 { 2 - "project.name" : "phabricator", 3 - "phabricator.uri" : "https://secure.phabricator.com/", 4 - "unit.engine" : "PhutilUnitTestEngine", 5 - "load" : ["src/"] 2 + "phabricator.uri": "https://secure.phabricator.com/", 3 + "unit.engine": "PhutilUnitTestEngine", 4 + "load": ["src/"] 6 5 }
+1
.arclint
··· 74 74 "type": "xhpast", 75 75 "include": "(\\.php$)", 76 76 "severity": { 77 + "16": "advice", 77 78 "34": "error" 78 79 }, 79 80 "xhpast.blacklisted.function": {
+70 -68
resources/celerity/map.php
··· 7 7 */ 8 8 return array( 9 9 'names' => array( 10 - 'core.pkg.css' => '55901d68', 11 - 'core.pkg.js' => 'e4f47dfd', 10 + 'core.pkg.css' => 'f85a5ce0', 11 + 'core.pkg.js' => 'fbf1d615', 12 12 'darkconsole.pkg.js' => 'e7393ebb', 13 - 'differential.pkg.css' => 'bb338e4b', 14 - 'differential.pkg.js' => '63a77807', 13 + 'differential.pkg.css' => '30602b8c', 14 + 'differential.pkg.js' => '8c98ce21', 15 15 'diffusion.pkg.css' => '385e85b3', 16 16 'diffusion.pkg.js' => '0115b37c', 17 17 'maniphest.pkg.css' => '4845691a', ··· 26 26 'rsrc/css/aphront/pager-view.css' => '2e3539af', 27 27 'rsrc/css/aphront/panel-view.css' => '8427b78d', 28 28 'rsrc/css/aphront/phabricator-nav-view.css' => '0ecd30a1', 29 - 'rsrc/css/aphront/table-view.css' => 'fb17602c', 29 + 'rsrc/css/aphront/table-view.css' => '15fedf7a', 30 30 'rsrc/css/aphront/tokenizer.css' => '04875312', 31 31 'rsrc/css/aphront/tooltip.css' => '7672b60f', 32 32 'rsrc/css/aphront/two-column.css' => '16ab3ad2', ··· 60 60 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', 61 61 'rsrc/css/application/differential/changeset-view.css' => 'e19cfd6e', 62 62 'rsrc/css/application/differential/core.css' => '7ac3cabc', 63 - 'rsrc/css/application/differential/phui-inline-comment.css' => '2174771a', 63 + 'rsrc/css/application/differential/phui-inline-comment.css' => 'aa16f165', 64 64 'rsrc/css/application/differential/results-table.css' => '181aa9d9', 65 65 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 66 66 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', ··· 111 111 'rsrc/css/core/core.css' => 'bbc7187b', 112 112 'rsrc/css/core/remarkup.css' => 'ea91b3ee', 113 113 'rsrc/css/core/syntax.css' => '6b7b24d9', 114 - 'rsrc/css/core/z-index.css' => '8c8c40aa', 114 + 'rsrc/css/core/z-index.css' => '63689f49', 115 115 'rsrc/css/diviner/diviner-shared.css' => '38813222', 116 116 'rsrc/css/font/font-awesome.css' => 'e2e712fe', 117 117 'rsrc/css/font/font-source-sans-pro.css' => '8906c07b', ··· 120 120 'rsrc/css/layout/phabricator-hovercard-view.css' => '0d665853', 121 121 'rsrc/css/layout/phabricator-side-menu-view.css' => 'a440478a', 122 122 'rsrc/css/layout/phabricator-source-code-view.css' => '2ceee894', 123 - 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'c0cf782a', 123 + 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93', 124 124 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1c7f338', 125 125 'rsrc/css/phui/calendar/phui-calendar-month.css' => '476be7e0', 126 126 'rsrc/css/phui/calendar/phui-calendar.css' => 'ccabe893', ··· 132 132 'rsrc/css/phui/phui-document.css' => '8be7a5e3', 133 133 'rsrc/css/phui/phui-feed-story.css' => 'e5682f4c', 134 134 'rsrc/css/phui/phui-fontkit.css' => 'b664ac96', 135 - 'rsrc/css/phui/phui-form-view.css' => '87263b05', 135 + 'rsrc/css/phui/phui-form-view.css' => 'a0e8f168', 136 136 'rsrc/css/phui/phui-form.css' => 'f535f938', 137 137 'rsrc/css/phui/phui-header-view.css' => 'd7612da3', 138 138 'rsrc/css/phui/phui-icon.css' => '88ba9081', ··· 325 325 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974', 326 326 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 327 327 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 328 - 'rsrc/js/application/calendar/event-all-day.js' => 'ca5fa62a', 329 - 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 330 - 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '10246726', 328 + 'rsrc/js/application/calendar/behavior-day-view.js' => '5c46cff2', 329 + 'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8', 330 + 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', 331 + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2', 331 332 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', 332 333 'rsrc/js/application/conpherence/behavior-durable-column.js' => '16c695bf', 333 334 'rsrc/js/application/conpherence/behavior-menu.js' => 'c0348cac', ··· 346 347 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 347 348 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 348 349 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '2035b9cb', 349 - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => 'e723c323', 350 + 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '037b59eb', 350 351 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '2c426492', 351 352 'rsrc/js/application/differential/behavior-populate.js' => '8694b1df', 352 353 'rsrc/js/application/differential/behavior-show-field-details.js' => 'bba9eedf', ··· 363 364 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781', 364 365 'rsrc/js/application/files/behavior-icon-composer.js' => '8ef9ab58', 365 366 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 366 - 'rsrc/js/application/herald/HeraldRuleEditor.js' => '9229e764', 367 + 'rsrc/js/application/herald/HeraldRuleEditor.js' => '271ffdd7', 367 368 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 368 369 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 369 370 'rsrc/js/application/maniphest/behavior-batch-editor.js' => 'f5d1233b', ··· 436 437 'rsrc/js/core/behavior-device.js' => 'a205cf28', 437 438 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6d49590e', 438 439 'rsrc/js/core/behavior-error-log.js' => '6882e80a', 439 - 'rsrc/js/core/behavior-fancy-datepicker.js' => '5c0f680f', 440 + 'rsrc/js/core/behavior-fancy-datepicker.js' => '510b5809', 440 441 'rsrc/js/core/behavior-file-tree.js' => '88236f00', 441 442 'rsrc/js/core/behavior-form.js' => '5c54cbf3', 442 443 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', ··· 482 483 'aphront-multi-column-view-css' => 'fd18389d', 483 484 'aphront-pager-view-css' => '2e3539af', 484 485 'aphront-panel-view-css' => '8427b78d', 485 - 'aphront-table-view-css' => 'fb17602c', 486 + 'aphront-table-view-css' => '15fedf7a', 486 487 'aphront-tokenizer-control-css' => '04875312', 487 488 'aphront-tooltip-css' => '7672b60f', 488 489 'aphront-two-column-view-css' => '16ab3ad2', ··· 497 498 'conpherence-menu-css' => 'f9f1d143', 498 499 'conpherence-message-pane-css' => '7cbf4cbb', 499 500 'conpherence-notification-css' => '919974b6', 500 - 'conpherence-thread-manager' => '10246726', 501 + 'conpherence-thread-manager' => '01774ab2', 501 502 'conpherence-transaction-css' => '42a457f6', 502 503 'conpherence-update-css' => '1099a660', 503 504 'conpherence-widget-pane-css' => '77096740', ··· 519 520 'global-drag-and-drop-css' => '697324ad', 520 521 'harbormaster-css' => '49d64eb4', 521 522 'herald-css' => '826075fa', 522 - 'herald-rule-editor' => '9229e764', 523 + 'herald-rule-editor' => '271ffdd7', 523 524 'herald-test-css' => '778b008e', 524 525 'inline-comment-summary-css' => 'eb5f8e8c', 525 526 'javelin-aphlict' => '5359e785', ··· 535 536 'javelin-behavior-audio-source' => '59b251eb', 536 537 'javelin-behavior-audit-preview' => 'd835b03a', 537 538 'javelin-behavior-choose-control' => '6153c708', 538 - 'javelin-behavior-config-reorder-fields' => '14a827de', 539 + 'javelin-behavior-config-reorder-fields' => 'b6993408', 539 540 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', 540 541 'javelin-behavior-conpherence-menu' => 'c0348cac', 541 542 'javelin-behavior-conpherence-pontificate' => '21ba5861', ··· 546 547 'javelin-behavior-dashboard-move-panels' => '82439934', 547 548 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 548 549 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', 550 + 'javelin-behavior-day-view' => '5c46cff2', 549 551 'javelin-behavior-device' => 'a205cf28', 550 552 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 551 553 'javelin-behavior-differential-comment-jump' => '4fdb476d', 552 554 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 553 555 'javelin-behavior-differential-dropdown-menus' => '2035b9cb', 554 - 'javelin-behavior-differential-edit-inline-comments' => 'e723c323', 556 + 'javelin-behavior-differential-edit-inline-comments' => '037b59eb', 555 557 'javelin-behavior-differential-feedback-preview' => 'b064af76', 556 558 'javelin-behavior-differential-keyboard-navigation' => '2c426492', 557 559 'javelin-behavior-differential-populate' => '8694b1df', ··· 566 568 'javelin-behavior-doorkeeper-tag' => 'e5822781', 567 569 'javelin-behavior-durable-column' => '16c695bf', 568 570 'javelin-behavior-error-log' => '6882e80a', 569 - 'javelin-behavior-event-all-day' => 'ca5fa62a', 570 - 'javelin-behavior-fancy-datepicker' => '5c0f680f', 571 + 'javelin-behavior-event-all-day' => '38dcf3c8', 572 + 'javelin-behavior-fancy-datepicker' => '510b5809', 571 573 'javelin-behavior-global-drag-and-drop' => 'c8e57404', 572 574 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 573 575 'javelin-behavior-high-security-warning' => 'a464fe03', ··· 743 745 'phabricator-uiexample-reactor-select' => 'a155550f', 744 746 'phabricator-uiexample-reactor-sendclass' => '1def2711', 745 747 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', 746 - 'phabricator-zindex-css' => '8c8c40aa', 748 + 'phabricator-zindex-css' => '63689f49', 747 749 'phame-css' => '88bd4705', 748 750 'pholio-css' => '95174bdd', 749 751 'pholio-edit-css' => '3ad9d1ee', ··· 757 759 'phui-box-css' => 'a5bb366d', 758 760 'phui-button-css' => 'b995182d', 759 761 'phui-calendar-css' => 'ccabe893', 760 - 'phui-calendar-day-css' => 'c0cf782a', 762 + 'phui-calendar-day-css' => 'd1cf6f93', 761 763 'phui-calendar-list-css' => 'c1c7f338', 762 764 'phui-calendar-month-css' => '476be7e0', 763 765 'phui-crumbs-view-css' => '3840dc4c', ··· 766 768 'phui-font-icon-base-css' => '3dad2ae3', 767 769 'phui-fontkit-css' => 'b664ac96', 768 770 'phui-form-css' => 'f535f938', 769 - 'phui-form-view-css' => '87263b05', 771 + 'phui-form-view-css' => 'a0e8f168', 770 772 'phui-header-view-css' => 'd7612da3', 771 773 'phui-icon-view-css' => '88ba9081', 772 774 'phui-image-mask-css' => '5a8b09c8', 773 775 'phui-info-panel-css' => '27ea50a1', 774 776 'phui-info-view-css' => '33e54618', 775 - 'phui-inline-comment-view-css' => '2174771a', 777 + 'phui-inline-comment-view-css' => 'aa16f165', 776 778 'phui-list-view-css' => 'e448b6ba', 777 779 'phui-object-box-css' => '8eacbeed', 778 780 'phui-object-item-list-view-css' => '24ed8d94', ··· 815 817 'unhandled-exception-css' => '4c96257a', 816 818 ), 817 819 'requires' => array( 820 + '01774ab2' => array( 821 + 'javelin-dom', 822 + 'javelin-util', 823 + 'javelin-stratcom', 824 + 'javelin-install', 825 + 'javelin-aphlict', 826 + 'javelin-workflow', 827 + 'javelin-router', 828 + 'javelin-behavior-device', 829 + 'javelin-vector', 830 + ), 818 831 '029a133d' => array( 819 832 'aphront-dialog-view-css', 820 833 ), 834 + '037b59eb' => array( 835 + 'javelin-behavior', 836 + 'javelin-stratcom', 837 + 'javelin-dom', 838 + 'javelin-util', 839 + 'javelin-vector', 840 + 'differential-inline-comment-editor', 841 + ), 821 842 '048330fa' => array( 822 843 'javelin-behavior', 823 844 'javelin-typeahead-ondemand-source', ··· 874 895 'javelin-install', 875 896 'javelin-util', 876 897 ), 877 - 10246726 => array( 878 - 'javelin-dom', 879 - 'javelin-util', 880 - 'javelin-stratcom', 881 - 'javelin-install', 882 - 'javelin-aphlict', 883 - 'javelin-workflow', 884 - 'javelin-router', 885 - 'javelin-behavior-device', 886 - 'javelin-vector', 887 - ), 888 898 '13c739ea' => array( 889 899 'javelin-behavior', 890 900 'javelin-stratcom', ··· 899 909 'javelin-dom', 900 910 'javelin-history', 901 911 ), 902 - '14a827de' => array( 903 - 'javelin-behavior', 904 - 'javelin-stratcom', 905 - 'javelin-dom', 906 - 'javelin-json', 907 - 'phabricator-draggable-list', 908 - ), 909 912 '14ac66f5' => array( 910 913 'javelin-install', 911 914 'javelin-dom', ··· 981 984 'phabricator-phtize', 982 985 'phabricator-drag-and-drop-file-upload', 983 986 'phabricator-draggable-list', 987 + ), 988 + '271ffdd7' => array( 989 + 'multirow-row-manager', 990 + 'javelin-install', 991 + 'javelin-util', 992 + 'javelin-dom', 993 + 'javelin-stratcom', 994 + 'javelin-json', 995 + 'phabricator-prefab', 984 996 ), 985 997 '2818f5ce' => array( 986 998 'javelin-install', ··· 1147 1159 'javelin-typeahead-source', 1148 1160 'javelin-util', 1149 1161 ), 1162 + '510b5809' => array( 1163 + 'javelin-behavior', 1164 + 'javelin-util', 1165 + 'javelin-dom', 1166 + 'javelin-stratcom', 1167 + 'javelin-vector', 1168 + ), 1150 1169 '519705ea' => array( 1151 1170 'javelin-install', 1152 1171 'javelin-dom', ··· 1223 1242 'javelin-mask', 1224 1243 'javelin-uri', 1225 1244 'javelin-routable', 1226 - ), 1227 - '5c0f680f' => array( 1228 - 'javelin-behavior', 1229 - 'javelin-util', 1230 - 'javelin-dom', 1231 - 'javelin-stratcom', 1232 - 'javelin-vector', 1233 1245 ), 1234 1246 '5c54cbf3' => array( 1235 1247 'javelin-behavior', ··· 1505 1517 'javelin-dom', 1506 1518 'javelin-stratcom', 1507 1519 ), 1508 - '9229e764' => array( 1509 - 'multirow-row-manager', 1510 - 'javelin-install', 1511 - 'javelin-util', 1512 - 'javelin-dom', 1513 - 'javelin-stratcom', 1514 - 'javelin-json', 1515 - 'phabricator-prefab', 1516 - ), 1517 1520 93568464 => array( 1518 1521 'javelin-behavior', 1519 1522 'javelin-dom', ··· 1705 1708 'javelin-stratcom', 1706 1709 'javelin-dom', 1707 1710 'javelin-util', 1711 + ), 1712 + 'b6993408' => array( 1713 + 'javelin-behavior', 1714 + 'javelin-stratcom', 1715 + 'javelin-dom', 1716 + 'javelin-json', 1717 + 'phabricator-draggable-list', 1708 1718 ), 1709 1719 'ba4fa35c' => array( 1710 1720 'javelin-behavior', ··· 1912 1922 ), 1913 1923 'e6e25838' => array( 1914 1924 'javelin-install', 1915 - ), 1916 - 'e723c323' => array( 1917 - 'javelin-behavior', 1918 - 'javelin-stratcom', 1919 - 'javelin-dom', 1920 - 'javelin-util', 1921 - 'javelin-vector', 1922 - 'differential-inline-comment-editor', 1923 1925 ), 1924 1926 'e9581f08' => array( 1925 1927 'javelin-behavior',
+2
resources/sql/autopatches/20150521.releephrepository.sql
··· 1 + ALTER TABLE {$NAMESPACE}_releeph.releeph_project 2 + MODIFY arcanistProjectID int(10) unsigned NULL;
+7
resources/sql/autopatches/20150525.diff.hidden.1.sql
··· 1 + CREATE TABLE {$NAMESPACE}_differential.differential_hiddencomment ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + userPHID VARBINARY(64) NOT NULL, 4 + commentID INT UNSIGNED NOT NULL, 5 + UNIQUE KEY `key_user` (userPHID, commentID), 6 + KEY `key_comment` (commentID) 7 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+2
resources/sql/autopatches/20150526.owners.mailkey.1.sql
··· 1 + ALTER TABLE {$NAMESPACE}_owners.owners_package 2 + ADD mailKey binary(20) NOT NULL;
+18
resources/sql/autopatches/20150526.owners.mailkey.2.php
··· 1 + <?php 2 + 3 + $table = new PhabricatorOwnersPackage(); 4 + $conn_w = $table->establishConnection('w'); 5 + $iterator = new LiskMigrationIterator($table); 6 + foreach ($iterator as $package) { 7 + $id = $package->getID(); 8 + 9 + echo pht('Adding mail key for package %d...', $id); 10 + echo "\n"; 11 + 12 + queryfx( 13 + $conn_w, 14 + 'UPDATE %T SET mailKey = %s WHERE id = %d', 15 + $table->getTableName(), 16 + Filesystem::readRandomCharacters(20), 17 + $id); 18 + }
+19
resources/sql/autopatches/20150526.owners.xaction.sql
··· 1 + CREATE TABLE {$NAMESPACE}_owners.owners_packagetransaction ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARBINARY(64) NOT NULL, 4 + authorPHID VARBINARY(64) NOT NULL, 5 + objectPHID VARBINARY(64) NOT NULL, 6 + viewPolicy VARBINARY(64) NOT NULL, 7 + editPolicy VARBINARY(64) NOT NULL, 8 + commentPHID VARBINARY(64) DEFAULT NULL, 9 + commentVersion INT UNSIGNED NOT NULL, 10 + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, 11 + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 12 + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 13 + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 14 + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + UNIQUE KEY `key_phid` (`phid`), 18 + KEY `key_object` (`objectPHID`) 19 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+23 -21
src/__phutil_library_map__.php
··· 374 374 'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php', 375 375 'DifferentialGitHubLandingStrategy' => 'applications/differential/landing/DifferentialGitHubLandingStrategy.php', 376 376 'DifferentialGitSVNIDField' => 'applications/differential/customfield/DifferentialGitSVNIDField.php', 377 + 'DifferentialHiddenComment' => 'applications/differential/storage/DifferentialHiddenComment.php', 377 378 'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php', 378 379 'DifferentialHostedGitLandingStrategy' => 'applications/differential/landing/DifferentialHostedGitLandingStrategy.php', 379 380 'DifferentialHostedMercurialLandingStrategy' => 'applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php', ··· 461 462 'DifferentialUpdateRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php', 462 463 'DifferentialUpdateUnitResultsConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateUnitResultsConduitAPIMethod.php', 463 464 'DifferentialViewPolicyField' => 'applications/differential/customfield/DifferentialViewPolicyField.php', 464 - 'DiffusionArcanistProjectDatasource' => 'applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php', 465 465 'DiffusionAuditorDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorDatasource.php', 466 466 'DiffusionBranchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php', 467 467 'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php', ··· 596 596 'DiffusionRepositoryEditEncodingController' => 'applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php', 597 597 'DiffusionRepositoryEditHostingController' => 'applications/diffusion/controller/DiffusionRepositoryEditHostingController.php', 598 598 'DiffusionRepositoryEditMainController' => 'applications/diffusion/controller/DiffusionRepositoryEditMainController.php', 599 + 'DiffusionRepositoryEditStagingController' => 'applications/diffusion/controller/DiffusionRepositoryEditStagingController.php', 599 600 'DiffusionRepositoryEditStorageController' => 'applications/diffusion/controller/DiffusionRepositoryEditStorageController.php', 600 601 'DiffusionRepositoryEditSubversionController' => 'applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php', 601 602 'DiffusionRepositoryEditUpdateController' => 'applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php', ··· 1176 1177 'PHUIDiffInlineCommentUndoView' => 'infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php', 1177 1178 'PHUIDiffInlineCommentView' => 'infrastructure/diff/view/PHUIDiffInlineCommentView.php', 1178 1179 'PHUIDiffOneUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php', 1180 + 'PHUIDiffRevealIconView' => 'infrastructure/diff/view/PHUIDiffRevealIconView.php', 1179 1181 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php', 1180 1182 'PHUIDocumentExample' => 'applications/uiexample/examples/PHUIDocumentExample.php', 1181 1183 'PHUIDocumentView' => 'view/phui/PHUIDocumentView.php', ··· 1226 1228 'PHUITypeaheadExample' => 'applications/uiexample/examples/PHUITypeaheadExample.php', 1227 1229 'PHUIWorkboardView' => 'view/phui/PHUIWorkboardView.php', 1228 1230 'PHUIWorkpanelView' => 'view/phui/PHUIWorkpanelView.php', 1229 - 'PackageCreateMail' => 'applications/owners/mail/PackageCreateMail.php', 1230 - 'PackageDeleteMail' => 'applications/owners/mail/PackageDeleteMail.php', 1231 - 'PackageMail' => 'applications/owners/mail/PackageMail.php', 1232 - 'PackageModifyMail' => 'applications/owners/mail/PackageModifyMail.php', 1233 1231 'PassphraseAbstractKey' => 'applications/passphrase/keys/PassphraseAbstractKey.php', 1234 1232 'PassphraseConduitAPIMethod' => 'applications/passphrase/conduit/PassphraseConduitAPIMethod.php', 1235 1233 'PassphraseController' => 'applications/passphrase/controller/PassphraseController.php', ··· 1496 1494 'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php', 1497 1495 'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php', 1498 1496 'PhabricatorCalendarEventCommentController' => 'applications/calendar/controller/PhabricatorCalendarEventCommentController.php', 1497 + 'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php', 1499 1498 'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php', 1500 1499 'PhabricatorCalendarEventEditIconController' => 'applications/calendar/controller/PhabricatorCalendarEventEditIconController.php', 1501 1500 'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php', ··· 1538 1537 'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php', 1539 1538 'PhabricatorCommitBranchesField' => 'applications/repository/customfield/PhabricatorCommitBranchesField.php', 1540 1539 'PhabricatorCommitCustomField' => 'applications/repository/customfield/PhabricatorCommitCustomField.php', 1540 + 'PhabricatorCommitMergedCommitsField' => 'applications/repository/customfield/PhabricatorCommitMergedCommitsField.php', 1541 + 'PhabricatorCommitRepositoryField' => 'applications/repository/customfield/PhabricatorCommitRepositoryField.php', 1541 1542 'PhabricatorCommitSearchEngine' => 'applications/audit/query/PhabricatorCommitSearchEngine.php', 1542 1543 'PhabricatorCommitTagsField' => 'applications/repository/customfield/PhabricatorCommitTagsField.php', 1543 1544 'PhabricatorCommonPasswords' => 'applications/auth/constants/PhabricatorCommonPasswords.php', ··· 2160 2161 'PhabricatorOwnersConfigOptions' => 'applications/owners/config/PhabricatorOwnersConfigOptions.php', 2161 2162 'PhabricatorOwnersController' => 'applications/owners/controller/PhabricatorOwnersController.php', 2162 2163 'PhabricatorOwnersDAO' => 'applications/owners/storage/PhabricatorOwnersDAO.php', 2163 - 'PhabricatorOwnersDeleteController' => 'applications/owners/controller/PhabricatorOwnersDeleteController.php', 2164 2164 'PhabricatorOwnersDetailController' => 'applications/owners/controller/PhabricatorOwnersDetailController.php', 2165 2165 'PhabricatorOwnersEditController' => 'applications/owners/controller/PhabricatorOwnersEditController.php', 2166 2166 'PhabricatorOwnersListController' => 'applications/owners/controller/PhabricatorOwnersListController.php', 2167 2167 'PhabricatorOwnersOwner' => 'applications/owners/storage/PhabricatorOwnersOwner.php', 2168 2168 'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php', 2169 2169 'PhabricatorOwnersPackageDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php', 2170 - 'PhabricatorOwnersPackageEditor' => 'applications/owners/editor/PhabricatorOwnersPackageEditor.php', 2171 2170 'PhabricatorOwnersPackagePHIDType' => 'applications/owners/phid/PhabricatorOwnersPackagePHIDType.php', 2172 - 'PhabricatorOwnersPackagePathValidator' => 'applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php', 2173 2171 'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php', 2172 + 'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php', 2174 2173 'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php', 2174 + 'PhabricatorOwnersPackageTransaction' => 'applications/owners/storage/PhabricatorOwnersPackageTransaction.php', 2175 + 'PhabricatorOwnersPackageTransactionEditor' => 'applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php', 2176 + 'PhabricatorOwnersPackageTransactionQuery' => 'applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php', 2175 2177 'PhabricatorOwnersPath' => 'applications/owners/storage/PhabricatorOwnersPath.php', 2178 + 'PhabricatorOwnersPathsController' => 'applications/owners/controller/PhabricatorOwnersPathsController.php', 2176 2179 'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php', 2177 2180 'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php', 2178 2181 'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php', ··· 2383 2386 'PhabricatorRepositoriesSetupCheck' => 'applications/config/check/PhabricatorRepositoriesSetupCheck.php', 2384 2387 'PhabricatorRepository' => 'applications/repository/storage/PhabricatorRepository.php', 2385 2388 'PhabricatorRepositoryArcanistProject' => 'applications/repository/storage/PhabricatorRepositoryArcanistProject.php', 2386 - 'PhabricatorRepositoryArcanistProjectDeleteController' => 'applications/repository/controller/PhabricatorRepositoryArcanistProjectDeleteController.php', 2387 - 'PhabricatorRepositoryArcanistProjectEditController' => 'applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php', 2388 2389 'PhabricatorRepositoryArcanistProjectPHIDType' => 'applications/repository/phid/PhabricatorRepositoryArcanistProjectPHIDType.php', 2389 2390 'PhabricatorRepositoryArcanistProjectQuery' => 'applications/repository/query/PhabricatorRepositoryArcanistProjectQuery.php', 2390 2391 'PhabricatorRepositoryAuditRequest' => 'applications/repository/storage/PhabricatorRepositoryAuditRequest.php', ··· 3146 3147 'ReleephProductTransactionQuery' => 'applications/releeph/query/ReleephProductTransactionQuery.php', 3147 3148 'ReleephProductViewController' => 'applications/releeph/controller/product/ReleephProductViewController.php', 3148 3149 'ReleephProject' => 'applications/releeph/storage/ReleephProject.php', 3149 - 'ReleephProjectInfoConduitAPIMethod' => 'applications/releeph/conduit/ReleephProjectInfoConduitAPIMethod.php', 3150 3150 'ReleephQueryBranchesConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryBranchesConduitAPIMethod.php', 3151 3151 'ReleephQueryProductsConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryProductsConduitAPIMethod.php', 3152 3152 'ReleephQueryRequestsConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryRequestsConduitAPIMethod.php', ··· 3614 3614 'DifferentialGetRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', 3615 3615 'DifferentialGitHubLandingStrategy' => 'DifferentialLandingStrategy', 3616 3616 'DifferentialGitSVNIDField' => 'DifferentialCustomField', 3617 + 'DifferentialHiddenComment' => 'DifferentialDAO', 3617 3618 'DifferentialHostField' => 'DifferentialCustomField', 3618 3619 'DifferentialHostedGitLandingStrategy' => 'DifferentialLandingStrategy', 3619 3620 'DifferentialHostedMercurialLandingStrategy' => 'DifferentialLandingStrategy', ··· 3706 3707 'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', 3707 3708 'DifferentialUpdateUnitResultsConduitAPIMethod' => 'DifferentialConduitAPIMethod', 3708 3709 'DifferentialViewPolicyField' => 'DifferentialCoreCustomField', 3709 - 'DiffusionArcanistProjectDatasource' => 'PhabricatorTypeaheadDatasource', 3710 3710 'DiffusionAuditorDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 3711 3711 'DiffusionBranchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 3712 3712 'DiffusionBranchTableController' => 'DiffusionController', ··· 3831 3831 'DiffusionRepositoryEditEncodingController' => 'DiffusionRepositoryEditController', 3832 3832 'DiffusionRepositoryEditHostingController' => 'DiffusionRepositoryEditController', 3833 3833 'DiffusionRepositoryEditMainController' => 'DiffusionRepositoryEditController', 3834 + 'DiffusionRepositoryEditStagingController' => 'DiffusionRepositoryEditController', 3834 3835 'DiffusionRepositoryEditStorageController' => 'DiffusionRepositoryEditController', 3835 3836 'DiffusionRepositoryEditSubversionController' => 'DiffusionRepositoryEditController', 3836 3837 'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryEditController', ··· 4502 4503 'PHUIDiffInlineCommentUndoView' => 'PHUIDiffInlineCommentView', 4503 4504 'PHUIDiffInlineCommentView' => 'AphrontView', 4504 4505 'PHUIDiffOneUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 4506 + 'PHUIDiffRevealIconView' => 'AphrontView', 4505 4507 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 4506 4508 'PHUIDocumentExample' => 'PhabricatorUIExample', 4507 4509 'PHUIDocumentView' => 'AphrontTagView', ··· 4552 4554 'PHUITypeaheadExample' => 'PhabricatorUIExample', 4553 4555 'PHUIWorkboardView' => 'AphrontTagView', 4554 4556 'PHUIWorkpanelView' => 'AphrontTagView', 4555 - 'PackageCreateMail' => 'PackageMail', 4556 - 'PackageDeleteMail' => 'PackageMail', 4557 - 'PackageMail' => 'PhabricatorMail', 4558 - 'PackageModifyMail' => 'PackageMail', 4559 4557 'PassphraseAbstractKey' => 'Phobject', 4560 4558 'PassphraseConduitAPIMethod' => 'ConduitAPIMethod', 4561 4559 'PassphraseController' => 'PhabricatorController', ··· 4849 4847 ), 4850 4848 'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController', 4851 4849 'PhabricatorCalendarEventCommentController' => 'PhabricatorCalendarController', 4850 + 'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController', 4852 4851 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController', 4853 4852 'PhabricatorCalendarEventEditIconController' => 'PhabricatorCalendarController', 4854 4853 'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor', ··· 4900 4899 'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions', 4901 4900 'PhabricatorCommitBranchesField' => 'PhabricatorCommitCustomField', 4902 4901 'PhabricatorCommitCustomField' => 'PhabricatorCustomField', 4902 + 'PhabricatorCommitMergedCommitsField' => 'PhabricatorCommitCustomField', 4903 + 'PhabricatorCommitRepositoryField' => 'PhabricatorCommitCustomField', 4903 4904 'PhabricatorCommitSearchEngine' => 'PhabricatorApplicationSearchEngine', 4904 4905 'PhabricatorCommitTagsField' => 'PhabricatorCommitCustomField', 4905 4906 'PhabricatorCommonPasswords' => 'Phobject', ··· 5560 5561 'PhabricatorOwnersConfigOptions' => 'PhabricatorApplicationConfigOptions', 5561 5562 'PhabricatorOwnersController' => 'PhabricatorController', 5562 5563 'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO', 5563 - 'PhabricatorOwnersDeleteController' => 'PhabricatorOwnersController', 5564 5564 'PhabricatorOwnersDetailController' => 'PhabricatorOwnersController', 5565 5565 'PhabricatorOwnersEditController' => 'PhabricatorOwnersController', 5566 5566 'PhabricatorOwnersListController' => 'PhabricatorOwnersController', ··· 5568 5568 'PhabricatorOwnersPackage' => array( 5569 5569 'PhabricatorOwnersDAO', 5570 5570 'PhabricatorPolicyInterface', 5571 + 'PhabricatorApplicationTransactionInterface', 5571 5572 ), 5572 5573 'PhabricatorOwnersPackageDatasource' => 'PhabricatorTypeaheadDatasource', 5573 - 'PhabricatorOwnersPackageEditor' => 'PhabricatorEditor', 5574 5574 'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType', 5575 5575 'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5576 + 'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine', 5576 5577 'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase', 5578 + 'PhabricatorOwnersPackageTransaction' => 'PhabricatorApplicationTransaction', 5579 + 'PhabricatorOwnersPackageTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 5580 + 'PhabricatorOwnersPackageTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 5577 5581 'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO', 5582 + 'PhabricatorOwnersPathsController' => 'PhabricatorOwnersController', 5578 5583 'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions', 5579 5584 'PhabricatorPHPASTApplication' => 'PhabricatorApplication', 5580 5585 'PhabricatorPHPConfigSetupCheck' => 'PhabricatorSetupCheck', ··· 5825 5830 'PhabricatorPolicyInterface', 5826 5831 'PhabricatorDestructibleInterface', 5827 5832 ), 5828 - 'PhabricatorRepositoryArcanistProjectDeleteController' => 'PhabricatorRepositoryController', 5829 - 'PhabricatorRepositoryArcanistProjectEditController' => 'PhabricatorRepositoryController', 5830 5833 'PhabricatorRepositoryArcanistProjectPHIDType' => 'PhabricatorPHIDType', 5831 5834 'PhabricatorRepositoryArcanistProjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5832 5835 'PhabricatorRepositoryAuditRequest' => array( ··· 6724 6727 'PhabricatorApplicationTransactionInterface', 6725 6728 'PhabricatorPolicyInterface', 6726 6729 ), 6727 - 'ReleephProjectInfoConduitAPIMethod' => 'ReleephConduitAPIMethod', 6728 6730 'ReleephQueryBranchesConduitAPIMethod' => 'ReleephConduitAPIMethod', 6729 6731 'ReleephQueryProductsConduitAPIMethod' => 'ReleephConduitAPIMethod', 6730 6732 'ReleephQueryRequestsConduitAPIMethod' => 'ReleephConduitAPIMethod',
+5 -1
src/applications/arcanist/conduit/ArcanistProjectInfoConduitAPIMethod.php
··· 7 7 return 'arcanist.projectinfo'; 8 8 } 9 9 10 + public function getMethodStatus() { 11 + return self::METHOD_STATUS_DEPRECATED; 12 + } 13 + 10 14 public function getMethodDescription() { 11 - return pht('Get information about Arcanist projects.'); 15 + return pht('Arcanist projects are deprecated.'); 12 16 } 13 17 14 18 protected function defineParamTypes() {
+8
src/applications/audit/storage/PhabricatorAuditInlineComment.php
··· 23 23 return $this->proxy; 24 24 } 25 25 26 + public function supportsHiding() { 27 + return false; 28 + } 29 + 30 + public function isHidden() { 31 + return false; 32 + } 33 + 26 34 public function getTransactionCommentForSave() { 27 35 $content_source = PhabricatorContentSource::newForSource( 28 36 PhabricatorContentSource::SOURCE_LEGACY,
+2
src/applications/calendar/application/PhabricatorCalendarApplication.php
··· 54 54 => 'PhabricatorCalendarEventEditController', 55 55 'edit/(?P<id>[1-9]\d*)/' 56 56 => 'PhabricatorCalendarEventEditController', 57 + 'drag/(?P<id>[1-9]\d*)/' 58 + => 'PhabricatorCalendarEventDragController', 57 59 'cancel/(?P<id>[1-9]\d*)/' 58 60 => 'PhabricatorCalendarEventCancelController', 59 61 '(?P<action>join|decline|accept)/(?P<id>[1-9]\d*)/'
+13 -1
src/applications/calendar/controller/PhabricatorCalendarController.php
··· 5 5 protected function buildApplicationCrumbs() { 6 6 $crumbs = parent::buildApplicationCrumbs(); 7 7 8 + $actions = id(new PhabricatorActionListView()) 9 + ->setUser($this->getViewer()) 10 + ->addAction( 11 + id(new PhabricatorActionView()) 12 + ->setName(pht('Create Private Event')) 13 + ->setHref('/calendar/event/create/?mode=private')) 14 + ->addAction( 15 + id(new PhabricatorActionView()) 16 + ->setName(pht('Create Public Event')) 17 + ->setHref('/calendar/event/create/?mode=public')); 18 + 8 19 $crumbs->addAction( 9 20 id(new PHUIListItemView()) 10 21 ->setName(pht('Create Event')) 11 22 ->setHref($this->getApplicationURI().'event/create/') 12 - ->setIcon('fa-plus-square')); 23 + ->setIcon('fa-plus-square') 24 + ->setDropdownMenu($actions)); 13 25 14 26 return $crumbs; 15 27 }
+66
src/applications/calendar/controller/PhabricatorCalendarEventDragController.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarEventDragController 4 + extends PhabricatorCalendarController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + 9 + $event = id(new PhabricatorCalendarEventQuery()) 10 + ->setViewer($viewer) 11 + ->withIDs(array($request->getURIData('id'))) 12 + ->requireCapabilities( 13 + array( 14 + PhabricatorPolicyCapability::CAN_VIEW, 15 + PhabricatorPolicyCapability::CAN_EDIT, 16 + )) 17 + ->executeOne(); 18 + if (!$event) { 19 + return new Aphront404Response(); 20 + } 21 + 22 + if (!$request->validateCSRF()) { 23 + return new Aphront400Response(); 24 + } 25 + 26 + if ($event->getIsAllDay()) { 27 + return new Aphront400Response(); 28 + } 29 + 30 + $xactions = array(); 31 + 32 + $duration = $event->getDateTo() - $event->getDateFrom(); 33 + 34 + $start = $request->getInt('start'); 35 + $start_value = id(AphrontFormDateControlValue::newFromEpoch( 36 + $viewer, 37 + $start)); 38 + 39 + $end = $start + $duration; 40 + $end_value = id(AphrontFormDateControlValue::newFromEpoch( 41 + $viewer, 42 + $end)); 43 + 44 + 45 + $xactions[] = id(new PhabricatorCalendarEventTransaction()) 46 + ->setTransactionType( 47 + PhabricatorCalendarEventTransaction::TYPE_START_DATE) 48 + ->setNewValue($start_value); 49 + 50 + $xactions[] = id(new PhabricatorCalendarEventTransaction()) 51 + ->setTransactionType( 52 + PhabricatorCalendarEventTransaction::TYPE_END_DATE) 53 + ->setNewValue($end_value); 54 + 55 + 56 + $editor = id(new PhabricatorCalendarEventEditor()) 57 + ->setActor($viewer) 58 + ->setContinueOnMissingFields(true) 59 + ->setContentSourceFromRequest($request) 60 + ->setContinueOnNoEffect(true); 61 + 62 + $xactions = $editor->applyTransactions($event, $xactions); 63 + 64 + return id(new AphrontReloadResponse()); 65 + } 66 + }
+75 -23
src/applications/calendar/controller/PhabricatorCalendarEventEditController.php
··· 14 14 } 15 15 16 16 public function handleRequest(AphrontRequest $request) { 17 - $user = $request->getUser(); 18 - $user_phid = $user->getPHID(); 17 + $viewer = $request->getViewer(); 18 + $user_phid = $viewer->getPHID(); 19 19 $error_name = true; 20 20 $error_start_date = true; 21 21 $error_end_date = true; ··· 24 24 $all_day_id = celerity_generate_unique_node_id(); 25 25 $start_date_id = celerity_generate_unique_node_id(); 26 26 $end_date_id = null; 27 + 28 + $next_workflow = $request->getStr('next'); 29 + $uri_query = $request->getStr('query'); 27 30 28 31 if ($this->isCreate()) { 29 - $event = PhabricatorCalendarEvent::initializeNewCalendarEvent($user); 30 - list($start_value, $end_value) = $this->getDefaultTimeValues($user); 32 + $mode = $request->getStr('mode'); 33 + $event = PhabricatorCalendarEvent::initializeNewCalendarEvent( 34 + $viewer, 35 + $mode); 36 + 37 + $create_start_year = $request->getInt('year'); 38 + $create_start_month = $request->getInt('month'); 39 + $create_start_day = $request->getInt('day'); 40 + $create_start_time = $request->getStr('time'); 41 + 42 + if ($create_start_year) { 43 + $start = AphrontFormDateControlValue::newFromParts( 44 + $viewer, 45 + $create_start_year, 46 + $create_start_month, 47 + $create_start_day, 48 + $create_start_time); 49 + if (!$start->isValid()) { 50 + return new Aphront400Response(); 51 + } 52 + $start_value = AphrontFormDateControlValue::newFromEpoch( 53 + $viewer, 54 + $start->getEpoch()); 55 + 56 + $end = clone $start_value->getDateTime(); 57 + $end->modify('+1 hour'); 58 + $end_value = AphrontFormDateControlValue::newFromEpoch( 59 + $viewer, 60 + $end->format('U')); 61 + 62 + } else { 63 + list($start_value, $end_value) = $this->getDefaultTimeValues($viewer); 64 + } 65 + 31 66 32 67 $submit_label = pht('Create'); 33 68 $page_title = pht('Create Event'); ··· 38 73 $end_date_id = celerity_generate_unique_node_id(); 39 74 } else { 40 75 $event = id(new PhabricatorCalendarEventQuery()) 41 - ->setViewer($user) 76 + ->setViewer($viewer) 42 77 ->withIDs(array($this->id)) 43 78 ->requireCapabilities( 44 79 array( ··· 51 86 } 52 87 53 88 $end_value = AphrontFormDateControlValue::newFromEpoch( 54 - $user, 89 + $viewer, 55 90 $event->getDateTo()); 56 91 $start_value = AphrontFormDateControlValue::newFromEpoch( 57 - $user, 92 + $viewer, 58 93 $event->getDateFrom()); 59 94 60 95 $submit_label = pht('Update'); ··· 81 116 $icon = $event->getIcon(); 82 117 83 118 $current_policies = id(new PhabricatorPolicyQuery()) 84 - ->setViewer($user) 119 + ->setViewer($viewer) 85 120 ->setObject($event) 86 121 ->execute(); 87 122 ··· 106 141 $new_invitees = $this->getNewInviteeList($invitees, $event); 107 142 $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; 108 143 if ($this->isCreate()) { 109 - $status = idx($new_invitees, $user->getPHID()); 144 + $status = idx($new_invitees, $viewer->getPHID()); 110 145 if ($status) { 111 - $new_invitees[$user->getPHID()] = $status_attending; 146 + $new_invitees[$viewer->getPHID()] = $status_attending; 112 147 } 113 148 } 114 149 ··· 161 196 ->setNewValue($request->getStr('editPolicy')); 162 197 163 198 $editor = id(new PhabricatorCalendarEventEditor()) 164 - ->setActor($user) 199 + ->setActor($viewer) 165 200 ->setContentSourceFromRequest($request) 166 201 ->setContinueOnNoEffect(true); 167 202 168 203 try { 169 204 $xactions = $editor->applyTransactions($event, $xactions); 170 205 $response = id(new AphrontRedirectResponse()); 171 - return $response->setURI('/E'.$event->getID()); 206 + switch ($next_workflow) { 207 + case 'day': 208 + if (!$uri_query) { 209 + $uri_query = 'month'; 210 + } 211 + $year = $start_value->getDateTime()->format('Y'); 212 + $month = $start_value->getDateTime()->format('m'); 213 + $day = $start_value->getDateTime()->format('d'); 214 + $response->setURI( 215 + '/calendar/query/'.$uri_query.'/'.$year.'/'.$month.'/'.$day.'/'); 216 + break; 217 + default: 218 + $response->setURI('/E'.$event->getID()); 219 + break; 220 + } 221 + return $response; 172 222 } catch (PhabricatorApplicationTransactionValidationException $ex) { 173 223 $validation_exception = $ex; 174 224 $error_name = $ex->getShortMessage( ··· 204 254 $all_day_id); 205 255 206 256 $start_control = id(new AphrontFormDateControl()) 207 - ->setUser($user) 257 + ->setUser($viewer) 208 258 ->setName('start') 209 259 ->setLabel(pht('Start')) 210 260 ->setError($error_start_date) ··· 214 264 ->setEndDateID($end_date_id); 215 265 216 266 $end_control = id(new AphrontFormDateControl()) 217 - ->setUser($user) 267 + ->setUser($viewer) 218 268 ->setName('end') 219 269 ->setLabel(pht('End')) 220 270 ->setError($error_end_date) ··· 228 278 ->setValue($description); 229 279 230 280 $view_policies = id(new AphrontFormPolicyControl()) 231 - ->setUser($user) 281 + ->setUser($viewer) 232 282 ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) 233 283 ->setPolicyObject($event) 234 284 ->setPolicies($current_policies) 235 285 ->setName('viewPolicy'); 236 286 $edit_policies = id(new AphrontFormPolicyControl()) 237 - ->setUser($user) 287 + ->setUser($viewer) 238 288 ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) 239 289 ->setPolicyObject($event) 240 290 ->setPolicies($current_policies) ··· 244 294 ->setLabel(pht('Subscribers')) 245 295 ->setName('subscribers') 246 296 ->setValue($subscribers) 247 - ->setUser($user) 297 + ->setUser($viewer) 248 298 ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); 249 299 250 300 $invitees = id(new AphrontFormTokenizerControl()) 251 301 ->setLabel(pht('Invitees')) 252 302 ->setName('invitees') 253 303 ->setValue($invitees) 254 - ->setUser($user) 304 + ->setUser($viewer) 255 305 ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); 256 306 257 307 if ($this->isCreate()) { ··· 269 319 ->setValue($icon); 270 320 271 321 $form = id(new AphrontFormView()) 272 - ->setUser($user) 322 + ->addHiddenInput('next', $next_workflow) 323 + ->addHiddenInput('query', $uri_query) 324 + ->setUser($viewer) 273 325 ->appendChild($name) 274 326 ->appendChild($all_day_checkbox) 275 327 ->appendChild($start_control) ··· 351 403 return $new; 352 404 } 353 405 354 - private function getDefaultTimeValues($user) { 406 + private function getDefaultTimeValues($viewer) { 355 407 $start = new DateTime('@'.time()); 356 - $start->setTimeZone($user->getTimeZone()); 408 + $start->setTimeZone($viewer->getTimeZone()); 357 409 358 410 $start->setTime($start->format('H'), 0, 0); 359 411 $start->modify('+1 hour'); 360 412 $end = id(clone $start)->modify('+1 hour'); 361 413 362 414 $start_value = AphrontFormDateControlValue::newFromEpoch( 363 - $user, 415 + $viewer, 364 416 $start->format('U')); 365 417 $end_value = AphrontFormDateControlValue::newFromEpoch( 366 - $user, 418 + $viewer, 367 419 $end->format('U')); 368 420 369 421 return array($start_value, $end_value);
+17
src/applications/calendar/query/PhabricatorCalendarEventQuery.php
··· 180 180 181 181 182 182 protected function willFilterPage(array $events) { 183 + $range_start = $this->rangeBegin; 184 + $range_end = $this->rangeEnd; 185 + 186 + foreach ($events as $key => $event) { 187 + $event_start = $event->getDateFrom(); 188 + $event_end = $event->getDateTo(); 189 + 190 + if ($range_start && $event_end < $range_start) { 191 + unset($events[$key]); 192 + } 193 + if ($range_end && $event_start > $range_end) { 194 + unset($events[$key]); 195 + } 196 + } 197 + 183 198 $phids = array(); 184 199 185 200 foreach ($events as $event) { ··· 196 211 $event_invitees = idx($invitees, $event->getPHID(), array()); 197 212 $event->attachInvitees($event_invitees); 198 213 } 214 + 215 + $events = msort($events, 'getDateFrom'); 199 216 200 217 return $events; 201 218 }
+9 -2
src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
··· 390 390 list($start_year, $start_month, $start_day) = 391 391 $this->getDisplayYearAndMonthAndDay($query); 392 392 393 - $day_view = new PHUICalendarDayView( 393 + $day_view = id(new PHUICalendarDayView( 394 394 $this->getDateFrom($query), 395 395 $this->getDateTo($query), 396 396 $start_year, 397 397 $start_month, 398 - $start_day); 398 + $start_day)) 399 + ->setQuery($query->getQueryKey()); 399 400 400 401 $day_view->setUser($viewer); 401 402 ··· 408 409 409 410 $viewer_is_invited = $status->getIsUserInvited($viewer->getPHID()); 410 411 412 + $can_edit = PhabricatorPolicyFilter::hasCapability( 413 + $viewer, 414 + $status, 415 + PhabricatorPolicyCapability::CAN_EDIT); 416 + 411 417 $event = new AphrontCalendarEventView(); 418 + $event->setCanEdit($can_edit); 412 419 $event->setEventID($status->getID()); 413 420 $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); 414 421 $event->setIsAllDay($status->getIsAllDay());
+10 -2
src/applications/calendar/storage/PhabricatorCalendarEvent.php
··· 28 28 private $invitees = self::ATTACHABLE; 29 29 private $appliedViewer; 30 30 31 - public static function initializeNewCalendarEvent(PhabricatorUser $actor) { 31 + public static function initializeNewCalendarEvent( 32 + PhabricatorUser $actor, 33 + $mode) { 32 34 $app = id(new PhabricatorApplicationQuery()) 33 35 ->setViewer($actor) 34 36 ->withClasses(array('PhabricatorCalendarApplication')) 35 37 ->executeOne(); 36 38 39 + if ($mode == 'public') { 40 + $view_policy = PhabricatorPolicies::getMostOpenPolicy(); 41 + } else { 42 + $view_policy = $actor->getPHID(); 43 + } 44 + 37 45 return id(new PhabricatorCalendarEvent()) 38 46 ->setUserPHID($actor->getPHID()) 39 47 ->setIsCancelled(0) 40 48 ->setIsAllDay(0) 41 49 ->setIcon(self::DEFAULT_ICON) 42 - ->setViewPolicy($actor->getPHID()) 50 + ->setViewPolicy($view_policy) 43 51 ->setEditPolicy($actor->getPHID()) 44 52 ->attachInvitees(array()) 45 53 ->applyViewerTimezone($actor);
+9
src/applications/calendar/view/AphrontCalendarEventView.php
··· 12 12 private $uri; 13 13 private $isAllDay; 14 14 private $icon; 15 + private $canEdit; 15 16 16 17 public function setURI($uri) { 17 18 $this->uri = $uri; ··· 97 98 return $this->icon; 98 99 } 99 100 101 + public function setCanEdit($can_edit) { 102 + $this->canEdit = $can_edit; 103 + return $this; 104 + } 105 + 106 + public function getCanEdit() { 107 + return $this->canEdit; 108 + } 100 109 101 110 public function getMultiDay() { 102 111 $nextday = strtotime('12:00 AM Tomorrow', $this->getEpochStart());
+1 -1
src/applications/conduit/controller/PhabricatorConduitLogController.php
··· 98 98 array($call->getMethod(), $client), 99 99 $status, 100 100 $call->getError(), 101 - pht('%d us', number_format($call->getDuration())), 101 + pht('%s us', new PhutilNumber($call->getDuration())), 102 102 phabricator_datetime($call->getDateCreated(), $viewer), 103 103 ); 104 104 }
+5 -3
src/applications/console/plugin/DarkConsoleServicesPlugin.php
··· 198 198 $summary[] = array( 199 199 $type, 200 200 number_format($counts[$type]), 201 - pht('%d us', number_format((int)(1000000 * $totals[$type]))), 201 + pht('%s us', new PhutilNumber((int)(1000000 * $totals[$type]))), 202 202 sprintf('%.1f%%', 100 * $totals[$type] / $page_total), 203 203 ); 204 204 } ··· 258 258 break; 259 259 } 260 260 261 + $offset = ($row['begin'] - $data['start']); 262 + 261 263 $rows[] = array( 262 264 $row['type'], 263 - pht('+%d ms', number_format(1000 * ($row['begin'] - $data['start']))), 264 - pht('%d us', number_format(1000000 * $row['duration'])), 265 + pht('+%s ms', new PhutilNumber(1000 * $offset)), 266 + pht('%s us', new PhutilNumber(1000000 * $row['duration'])), 265 267 $info, 266 268 $analysis, 267 269 );
+1 -1
src/applications/daemon/controller/PhabricatorDaemonConsoleController.php
··· 50 50 $rows[] = array( 51 51 $class, 52 52 number_format($info['n']), 53 - pht('%d us', number_format((int)($info['duration'] / $info['n']))), 53 + pht('%s us', new PhutilNumber((int)($info['duration'] / $info['n']))), 54 54 ); 55 55 } 56 56
+1 -1
src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
··· 141 141 $expires); 142 142 143 143 if ($task->isArchived()) { 144 - $duration = pht('%d us', number_format($task->getDuration())); 144 + $duration = pht('%s us', new PhutilNumber($task->getDuration())); 145 145 } else { 146 146 $duration = phutil_tag('em', array(), pht('Not Completed')); 147 147 }
+1
src/applications/differential/controller/DifferentialChangesetViewController.php
··· 191 191 if ($revision) { 192 192 $query = id(new DifferentialInlineCommentQuery()) 193 193 ->setViewer($viewer) 194 + ->needHidden(true) 194 195 ->withRevisionPHIDs(array($revision->getPHID())); 195 196 $inlines = $query->execute(); 196 197 $inlines = $query->adjustInlinesForChangesets(
+36
src/applications/differential/controller/DifferentialInlineCommentEditController.php
··· 42 42 ->setViewer($this->getViewer()) 43 43 ->withIDs(array($id)) 44 44 ->withDeletedDrafts(true) 45 + ->needHidden(true) 45 46 ->executeOne(); 46 47 } 47 48 ··· 50 51 ->setViewer($this->getViewer()) 51 52 ->withPHIDs(array($phid)) 52 53 ->withDeletedDrafts(true) 54 + ->needHidden(true) 53 55 ->executeOne(); 54 56 } 55 57 ··· 150 152 protected function loadObjectOwnerPHID( 151 153 PhabricatorInlineCommentInterface $inline) { 152 154 return $this->loadRevision()->getAuthorPHID(); 155 + } 156 + 157 + protected function hideComments(array $ids) { 158 + $viewer = $this->getViewer(); 159 + $table = new DifferentialHiddenComment(); 160 + $conn_w = $table->establishConnection('w'); 161 + 162 + $sql = array(); 163 + foreach ($ids as $id) { 164 + $sql[] = qsprintf( 165 + $conn_w, 166 + '(%s, %d)', 167 + $viewer->getPHID(), 168 + $id); 169 + } 170 + 171 + queryfx( 172 + $conn_w, 173 + 'INSERT IGNORE INTO %T (userPHID, commentID) VALUES %Q', 174 + $table->getTableName(), 175 + implode(', ', $sql)); 176 + } 177 + 178 + protected function showComments(array $ids) { 179 + $viewer = $this->getViewer(); 180 + $table = new DifferentialHiddenComment(); 181 + $conn_w = $table->establishConnection('w'); 182 + 183 + queryfx( 184 + $conn_w, 185 + 'DELETE FROM %T WHERE userPHID = %s AND commentID IN (%Ld)', 186 + $table->getTableName(), 187 + $viewer->getPHID(), 188 + $ids); 153 189 } 154 190 155 191 }
+1
src/applications/differential/controller/DifferentialInlineCommentPreviewController.php
··· 19 19 ->withDrafts(true) 20 20 ->withAuthorPHIDs(array($viewer->getPHID())) 21 21 ->withRevisionPHIDs(array($revision->getPHID())) 22 + ->needHidden(true) 22 23 ->execute(); 23 24 } 24 25
+1
src/applications/differential/controller/DifferentialRevisionViewController.php
··· 175 175 176 176 $query = id(new DifferentialInlineCommentQuery()) 177 177 ->setViewer($user) 178 + ->needHidden(true) 178 179 ->withRevisionPHIDs(array($revision->getPHID())); 179 180 $inlines = $query->execute(); 180 181 $inlines = $query->adjustInlinesForChangesets(
+5
src/applications/differential/landing/DifferentialGitHubLandingStrategy.php
··· 47 47 DifferentialRevision $revision, 48 48 PhabricatorRepository $repository) { 49 49 50 + // TODO: This temporarily disables this action, because it doesn't work 51 + // and is confusing to users. If you want to use it, comment out this line 52 + // for now and we'll provide real support eventually. 53 + return; 54 + 50 55 $vcs = $repository->getVersionControlSystem(); 51 56 if ($vcs !== PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) { 52 57 return;
+1 -2
src/applications/differential/parser/DifferentialChangesetParser.php
··· 906 906 $shield = $renderer->renderShield( 907 907 pht('This file was completely deleted.')); 908 908 } else if ($this->changeset->getAffectedLineCount() > 2500) { 909 - $lines = number_format($this->changeset->getAffectedLineCount()); 910 909 $shield = $renderer->renderShield( 911 910 pht( 912 911 'This file has a very large number of changes (%s lines).', 913 - $lines)); 912 + new PhutilNumber($this->changeset->getAffectedLineCount()))); 914 913 } 915 914 } 916 915
+26
src/applications/differential/query/DifferentialInlineCommentQuery.php
··· 15 15 private $authorPHIDs; 16 16 private $revisionPHIDs; 17 17 private $deletedDrafts; 18 + private $needHidden; 18 19 19 20 public function setViewer(PhabricatorUser $viewer) { 20 21 $this->viewer = $viewer; ··· 55 56 return $this; 56 57 } 57 58 59 + public function needHidden($need) { 60 + $this->needHidden = $need; 61 + return $this; 62 + } 63 + 58 64 public function execute() { 59 65 $table = new DifferentialTransactionComment(); 60 66 $conn_r = $table->establishConnection('r'); ··· 67 73 $this->buildLimitClause($conn_r)); 68 74 69 75 $comments = $table->loadAllFromArray($data); 76 + 77 + if ($this->needHidden) { 78 + $viewer_phid = $this->getViewer()->getPHID(); 79 + if ($viewer_phid && $comments) { 80 + $hidden = queryfx_all( 81 + $conn_r, 82 + 'SELECT commentID FROM %T WHERE userPHID = %s 83 + AND commentID IN (%Ls)', 84 + id(new DifferentialHiddenComment())->getTableName(), 85 + $viewer_phid, 86 + mpull($comments, 'getID')); 87 + $hidden = array_fuse(ipull($hidden, 'commentID')); 88 + } else { 89 + $hidden = array(); 90 + } 91 + 92 + foreach ($comments as $inline) { 93 + $inline->attachIsHidden(isset($hidden[$inline->getID()])); 94 + } 95 + } 70 96 71 97 foreach ($comments as $key => $value) { 72 98 $comments[$key] = DifferentialInlineComment::newFromModernComment(
+44 -4
src/applications/differential/render/DifferentialChangesetOneUpRenderer.php
··· 41 41 42 42 $column_width = 4; 43 43 44 + $hidden = new PHUIDiffRevealIconView(); 45 + 44 46 $out = array(); 45 - foreach ($primitives as $p) { 47 + foreach ($primitives as $k => $p) { 46 48 $type = $p['type']; 47 49 switch ($type) { 48 50 case 'old': ··· 51 53 case 'new-file': 52 54 $is_old = ($type == 'old' || $type == 'old-file'); 53 55 56 + $o_hidden = array(); 57 + $n_hidden = array(); 58 + 59 + for ($look = $k + 1; isset($primitives[$look]); $look++) { 60 + $next = $primitives[$look]; 61 + switch ($next['type']) { 62 + case 'inline': 63 + $comment = $next['comment']; 64 + if ($comment->isHidden()) { 65 + if ($next['right']) { 66 + $n_hidden[] = $comment; 67 + } else { 68 + $o_hidden[] = $comment; 69 + } 70 + } 71 + break; 72 + default: 73 + break 2; 74 + } 75 + } 76 + 54 77 $cells = array(); 55 78 if ($is_old) { 56 79 if ($p['htype']) { ··· 68 91 } else { 69 92 $left_id = null; 70 93 } 71 - $cells[] = phutil_tag('th', array('id' => $left_id), $p['line']); 94 + 95 + $line = $p['line']; 96 + if ($o_hidden) { 97 + $line = array($hidden, $line); 98 + } 99 + 100 + $cells[] = phutil_tag('th', array('id' => $left_id), $line); 72 101 73 102 $cells[] = phutil_tag('th', array()); 74 103 $cells[] = $no_copy; ··· 85 114 } else { 86 115 $left_id = null; 87 116 } 88 - $cells[] = phutil_tag('th', array('id' => $left_id), $p['oline']); 117 + 118 + $oline = $p['oline']; 119 + if ($o_hidden) { 120 + $oline = array($hidden, $oline); 121 + } 122 + 123 + $cells[] = phutil_tag('th', array('id' => $left_id), $oline); 89 124 } 90 125 91 126 if ($type == 'new-file') { ··· 97 132 } else { 98 133 $right_id = null; 99 134 } 100 - $cells[] = phutil_tag('th', array('id' => $right_id), $p['line']); 101 135 136 + $line = $p['line']; 137 + if ($n_hidden) { 138 + $line = array($hidden, $line); 139 + } 140 + 141 + $cells[] = phutil_tag('th', array('id' => $right_id), $line); 102 142 103 143 $cells[] = $no_copy; 104 144 $cells[] = phutil_tag('td', array('class' => $class), $p['render']);
+59 -26
src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
··· 69 69 $depths = $this->getDepths(); 70 70 $mask = $this->getMask(); 71 71 72 + $hidden = new PHUIDiffRevealIconView(); 73 + 72 74 for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) { 73 75 if (empty($mask[$ii])) { 74 76 // If we aren't going to show this line, we've just entered a gap. ··· 235 237 $n_id = null; 236 238 } 237 239 238 - // NOTE: This is a unicode zero-width space, which we use as a hint when 239 - // intercepting 'copy' events to make sure sensible text ends up on the 240 - // clipboard. See the 'phabricator-oncopy' behavior. 241 - $zero_space = "\xE2\x80\x8B"; 242 - 243 - $html[] = phutil_tag('tr', array(), array( 244 - phutil_tag('th', array('id' => $o_id), $o_num), 245 - phutil_tag('td', array('class' => $o_classes), $o_text), 246 - phutil_tag('th', array('id' => $n_id), $n_num), 247 - $n_copy, 248 - phutil_tag( 249 - 'td', 250 - array('class' => $n_classes, 'colspan' => $n_colspan), 251 - array( 252 - phutil_tag('span', array('class' => 'zwsp'), $zero_space), 253 - $n_text, 254 - )), 255 - $n_cov, 256 - )); 257 - 258 - if ($context_not_available && ($ii == $rows - 1)) { 259 - $html[] = $context_not_available; 260 - } 261 - 262 240 $old_comments = $this->getOldComments(); 263 241 $new_comments = $this->getNewComments(); 242 + $scaffolds = array(); 243 + 244 + $o_hidden = array(); 245 + $n_hidden = array(); 264 246 265 247 if ($o_num && isset($old_comments[$o_num])) { 266 248 foreach ($old_comments[$o_num] as $comment) { ··· 268 250 $comment, 269 251 $on_right = false); 270 252 $scaffold = $this->getRowScaffoldForInline($inline); 253 + 254 + if ($comment->isHidden()) { 255 + $o_hidden[] = $comment; 256 + } 271 257 272 258 if ($n_num && isset($new_comments[$n_num])) { 273 259 foreach ($new_comments[$n_num] as $key => $new_comment) { ··· 276 262 $new_comment, 277 263 $on_right = true); 278 264 265 + if ($new_comment->isHidden()) { 266 + $n_hidden = $new_comment; 267 + } 268 + 279 269 $scaffold->addInlineView($companion); 280 270 unset($new_comments[$n_num][$key]); 281 271 break; ··· 283 273 } 284 274 } 285 275 286 - $html[] = $scaffold; 276 + 277 + $scaffolds[] = $scaffold; 287 278 } 288 279 } 280 + 289 281 if ($n_num && isset($new_comments[$n_num])) { 290 282 foreach ($new_comments[$n_num] as $comment) { 291 283 $inline = $this->buildInlineComment( 292 284 $comment, 293 285 $on_right = true); 294 - $html[] = $this->getRowScaffoldForInline($inline); 286 + 287 + if ($comment->isHidden()) { 288 + $n_hidden[] = $comment; 289 + } 290 + 291 + $scaffolds[] = $this->getRowScaffoldForInline($inline); 295 292 } 293 + } 294 + 295 + if ($o_hidden) { 296 + $o_num = array($hidden, $o_num); 297 + } 298 + 299 + if ($n_hidden) { 300 + $n_num = array($hidden, $n_num); 301 + } 302 + 303 + // NOTE: This is a unicode zero-width space, which we use as a hint when 304 + // intercepting 'copy' events to make sure sensible text ends up on the 305 + // clipboard. See the 'phabricator-oncopy' behavior. 306 + $zero_space = "\xE2\x80\x8B"; 307 + 308 + $html[] = phutil_tag('tr', array(), array( 309 + phutil_tag('th', array('id' => $o_id), $o_num), 310 + phutil_tag('td', array('class' => $o_classes), $o_text), 311 + phutil_tag('th', array('id' => $n_id), $n_num), 312 + $n_copy, 313 + phutil_tag( 314 + 'td', 315 + array('class' => $n_classes, 'colspan' => $n_colspan), 316 + array( 317 + phutil_tag('span', array('class' => 'zwsp'), $zero_space), 318 + $n_text, 319 + )), 320 + $n_cov, 321 + )); 322 + 323 + if ($context_not_available && ($ii == $rows - 1)) { 324 + $html[] = $context_not_available; 325 + } 326 + 327 + foreach ($scaffolds as $scaffold) { 328 + $html[] = $scaffold; 296 329 } 297 330 } 298 331
+24
src/applications/differential/storage/DifferentialHiddenComment.php
··· 1 + <?php 2 + 3 + final class DifferentialHiddenComment 4 + extends DifferentialDAO { 5 + 6 + protected $userPHID; 7 + protected $commentID; 8 + 9 + protected function getConfiguration() { 10 + return array( 11 + self::CONFIG_TIMESTAMPS => false, 12 + self::CONFIG_KEY_SCHEMA => array( 13 + 'key_user' => array( 14 + 'columns' => array('userPHID', 'commentID'), 15 + 'unique' => true, 16 + ), 17 + 'key_comment' => array( 18 + 'columns' => array('commentID'), 19 + ), 20 + ), 21 + ) + parent::getConfiguration(); 22 + } 23 + 24 + }
+15
src/applications/differential/storage/DifferentialInlineComment.php
··· 24 24 ->setViewPolicy('public') 25 25 ->setEditPolicy($this->getAuthorPHID()) 26 26 ->setContentSource($content_source) 27 + ->attachIsHidden(false) 27 28 ->setCommentVersion(1); 28 29 29 30 return $this->proxy; ··· 47 48 $this->proxy->delete(); 48 49 49 50 return $this; 51 + } 52 + 53 + public function supportsHiding() { 54 + if ($this->getSyntheticAuthor()) { 55 + return false; 56 + } 57 + return true; 58 + } 59 + 60 + public function isHidden() { 61 + if (!$this->supportsHiding()) { 62 + return false; 63 + } 64 + return $this->proxy->getIsHidden(); 50 65 } 51 66 52 67 public function getID() {
+1
src/applications/differential/storage/DifferentialRevision.php
··· 522 522 } 523 523 524 524 $query = id(new DifferentialInlineCommentQuery()) 525 + ->needHidden(true) 525 526 ->setViewer($viewer); 526 527 527 528 // NOTE: This is a bit sketchy: this method adjusts the inlines as a
+10
src/applications/differential/storage/DifferentialTransactionComment.php
··· 13 13 protected $replyToCommentPHID; 14 14 15 15 private $replyToComment = self::ATTACHABLE; 16 + private $isHidden = self::ATTACHABLE; 16 17 17 18 public function getApplicationTransactionObject() { 18 19 return new DifferentialTransaction(); ··· 97 98 } 98 99 99 100 return $inline_groups; 101 + } 102 + 103 + public function getIsHidden() { 104 + return $this->assertAttached($this->isHidden); 105 + } 106 + 107 + public function attachIsHidden($hidden) { 108 + $this->isHidden = $hidden; 109 + return $this; 100 110 } 101 111 102 112 }
+3 -2
src/applications/differential/view/DifferentialChangesetListView.php
··· 232 232 233 233 if ($this->inlineURI) { 234 234 Javelin::initBehavior('differential-edit-inline-comments', array( 235 - 'uri' => $this->inlineURI, 236 - 'stage' => 'differential-review-stage', 235 + 'uri' => $this->inlineURI, 236 + 'stage' => 'differential-review-stage', 237 + 'revealIcon' => hsprintf('%s', new PHUIDiffRevealIconView()), 237 238 )); 238 239 } 239 240
+1
src/applications/diffusion/application/PhabricatorDiffusionApplication.php
··· 101 101 '(?P<serve>serve)/' => 'DiffusionRepositoryEditHostingController', 102 102 'update/' => 'DiffusionRepositoryEditUpdateController', 103 103 'symbol/' => 'DiffusionRepositorySymbolsController', 104 + 'staging/' => 'DiffusionRepositoryEditStagingController', 104 105 ), 105 106 'pathtree/(?P<dblob>.*)' => 'DiffusionPathTreeController', 106 107 'mirror/' => array(
+22
src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php
··· 20 20 } 21 21 22 22 public function getOptions() { 23 + $custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType'; 24 + 25 + $fields = array( 26 + new PhabricatorCommitRepositoryField(), 27 + new PhabricatorCommitBranchesField(), 28 + new PhabricatorCommitTagsField(), 29 + new PhabricatorCommitMergedCommitsField(), 30 + ); 31 + 32 + $default_fields = array(); 33 + foreach ($fields as $field) { 34 + $default_fields[$field->getFieldKey()] = array( 35 + 'disabled' => $field->shouldDisableByDefault(), 36 + ); 37 + } 38 + 23 39 return array( 24 40 $this->newOption( 25 41 'metamta.diffusion.subject-prefix', ··· 124 140 'from web traffic (for example, if you use different SSH and '. 125 141 'web load balancers), you can set the SSH hostname here. This '. 126 142 'is an advanced option.')), 143 + $this->newOption('diffusion.fields', $custom_field_type, $default_fields) 144 + ->setCustomData( 145 + id(new PhabricatorRepositoryCommit()) 146 + ->getCustomFieldBaseClass()) 147 + ->setDescription( 148 + pht('Select and reorder Diffusion fields.')), 127 149 ); 128 150 } 129 151
+2 -2
src/applications/diffusion/controller/DiffusionCommitController.php
··· 240 240 241 241 $change_panel = new PHUIObjectBoxView(); 242 242 $header = new PHUIHeaderView(); 243 - $header->setHeader(pht('Changes (%d', number_format($count))); 243 + $header->setHeader(pht('Changes (%s)', new PhutilNumber($count))); 244 244 $change_panel->setID('toc'); 245 - if ($count > self::CHANGES_LIMIT && !$show_all_details) { 246 245 246 + if ($count > self::CHANGES_LIMIT && !$show_all_details) { 247 247 $icon = id(new PHUIIconView()) 248 248 ->setIconFont('fa-files-o'); 249 249
+53
src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
··· 30 30 31 31 $has_branches = ($is_git || $is_hg); 32 32 $has_local = $repository->usesLocalWorkingCopy(); 33 + $supports_staging = $repository->supportsStaging(); 33 34 34 35 $crumbs = $this->buildApplicationCrumbs($is_main = true); 35 36 ··· 92 93 $this->buildStorageActions($repository)); 93 94 } 94 95 96 + $staging_properties = null; 97 + if ($supports_staging) { 98 + $staging_properties = $this->buildStagingProperties( 99 + $repository, 100 + $this->buildStagingActions($repository)); 101 + } 102 + 95 103 $actions_properties = $this->buildActionsProperties( 96 104 $repository, 97 105 $this->buildActionsActions($repository)); ··· 155 163 $boxes[] = id(new PHUIObjectBoxView()) 156 164 ->setHeaderText(pht('Storage')) 157 165 ->addPropertyList($storage_properties); 166 + } 167 + 168 + if ($staging_properties) { 169 + $boxes[] = id(new PHUIObjectBoxView()) 170 + ->setHeaderText(pht('Staging')) 171 + ->addPropertyList($staging_properties); 158 172 } 159 173 160 174 $boxes[] = id(new PHUIObjectBoxView()) ··· 605 619 $view->addProperty( 606 620 pht('Storage Path'), 607 621 $repository->getHumanReadableDetail('local-path')); 622 + 623 + return $view; 624 + } 625 + 626 + 627 + private function buildStagingActions(PhabricatorRepository $repository) { 628 + $viewer = $this->getViewer(); 629 + 630 + $view = id(new PhabricatorActionListView()) 631 + ->setObjectURI($this->getRequest()->getRequestURI()) 632 + ->setUser($viewer); 633 + 634 + $edit = id(new PhabricatorActionView()) 635 + ->setIcon('fa-pencil') 636 + ->setName(pht('Edit Staging')) 637 + ->setHref( 638 + $this->getRepositoryControllerURI($repository, 'edit/staging/')); 639 + $view->addAction($edit); 640 + 641 + return $view; 642 + } 643 + 644 + private function buildStagingProperties( 645 + PhabricatorRepository $repository, 646 + PhabricatorActionListView $actions) { 647 + $viewer = $this->getViewer(); 648 + 649 + $view = id(new PHUIPropertyListView()) 650 + ->setUser($viewer) 651 + ->setActionList($actions); 652 + 653 + $staging_uri = $repository->getStagingURI(); 654 + if (!$staging_uri) { 655 + $staging_uri = phutil_tag('em', array(), pht('No Staging Area')); 656 + } 657 + 658 + $view->addProperty( 659 + pht('Staging Area'), 660 + $staging_uri); 608 661 609 662 return $view; 610 663 }
+92
src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php
··· 1 + <?php 2 + 3 + final class DiffusionRepositoryEditStagingController 4 + extends DiffusionRepositoryEditController { 5 + 6 + protected function processDiffusionRequest(AphrontRequest $request) { 7 + $user = $request->getUser(); 8 + $drequest = $this->diffusionRequest; 9 + $repository = $drequest->getRepository(); 10 + 11 + $repository = id(new PhabricatorRepositoryQuery()) 12 + ->setViewer($user) 13 + ->requireCapabilities( 14 + array( 15 + PhabricatorPolicyCapability::CAN_VIEW, 16 + PhabricatorPolicyCapability::CAN_EDIT, 17 + )) 18 + ->withIDs(array($repository->getID())) 19 + ->executeOne(); 20 + if (!$repository) { 21 + return new Aphront404Response(); 22 + } 23 + 24 + if (!$repository->supportsStaging()) { 25 + return new Aphront404Response(); 26 + } 27 + 28 + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); 29 + 30 + $v_area = $repository->getHumanReadableDetail('staging-uri'); 31 + if ($request->isFormPost()) { 32 + $v_area = $request->getStr('area'); 33 + 34 + $xactions = array(); 35 + $template = id(new PhabricatorRepositoryTransaction()); 36 + 37 + $type_encoding = PhabricatorRepositoryTransaction::TYPE_STAGING_URI; 38 + 39 + $xactions[] = id(clone $template) 40 + ->setTransactionType($type_encoding) 41 + ->setNewValue($v_area); 42 + 43 + id(new PhabricatorRepositoryEditor()) 44 + ->setContinueOnNoEffect(true) 45 + ->setContentSourceFromRequest($request) 46 + ->setActor($user) 47 + ->applyTransactions($repository, $xactions); 48 + 49 + return id(new AphrontRedirectResponse())->setURI($edit_uri); 50 + } 51 + 52 + $crumbs = $this->buildApplicationCrumbs(); 53 + $crumbs->addTextCrumb(pht('Edit Staging')); 54 + 55 + $title = pht('Edit %s', $repository->getName()); 56 + 57 + $form = id(new AphrontFormView()) 58 + ->setUser($user) 59 + ->appendRemarkupInstructions( 60 + pht( 61 + "To make it easier to run integration tests and builds on code ". 62 + "under review, you can configure a **Staging Area**. When `arc` ". 63 + "creates a diff, it will push a copy of the changes to the ". 64 + "configured staging area with a corresponding tag.". 65 + "\n\n". 66 + "IMPORTANT: This feature is new, experimental, and not supported. ". 67 + "Use it at your own risk.")) 68 + ->appendChild( 69 + id(new AphrontFormTextControl()) 70 + ->setLabel(pht('Staging Area URI')) 71 + ->setName('area') 72 + ->setValue($v_area)) 73 + ->appendChild( 74 + id(new AphrontFormSubmitControl()) 75 + ->setValue(pht('Save')) 76 + ->addCancelButton($edit_uri)); 77 + 78 + $object_box = id(new PHUIObjectBoxView()) 79 + ->setHeaderText($title) 80 + ->setForm($form); 81 + 82 + return $this->buildApplicationPage( 83 + array( 84 + $crumbs, 85 + $object_box, 86 + ), 87 + array( 88 + 'title' => $title, 89 + )); 90 + } 91 + 92 + }
-39
src/applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php
··· 1 - <?php 2 - 3 - final class DiffusionArcanistProjectDatasource 4 - extends PhabricatorTypeaheadDatasource { 5 - 6 - public function isBrowsable() { 7 - // TODO: We should probably make this browsable, or maybe remove it. 8 - return false; 9 - } 10 - 11 - public function getBrowseTitle() { 12 - return pht('Browse Arcanist Projects'); 13 - } 14 - 15 - public function getPlaceholderText() { 16 - return pht('Type an arcanist project name...'); 17 - } 18 - 19 - public function getDatasourceApplicationClass() { 20 - return 'PhabricatorDiffusionApplication'; 21 - } 22 - 23 - public function loadResults() { 24 - $viewer = $this->getViewer(); 25 - $raw_query = $this->getRawQuery(); 26 - 27 - $results = array(); 28 - 29 - $arcprojs = id(new PhabricatorRepositoryArcanistProject())->loadAll(); 30 - foreach ($arcprojs as $proj) { 31 - $results[] = id(new PhabricatorTypeaheadResult()) 32 - ->setName($proj->getName()) 33 - ->setPHID($proj->getPHID()); 34 - } 35 - 36 - return $results; 37 - } 38 - 39 - }
+15 -9
src/applications/herald/adapter/HeraldAdapter.php
··· 42 42 const FIELD_APPLICATION_EMAIL = 'applicaton-email'; 43 43 const FIELD_TASK_PRIORITY = 'taskpriority'; 44 44 const FIELD_TASK_STATUS = 'taskstatus'; 45 - const FIELD_ARCANIST_PROJECT = 'arcanist-project'; 46 45 const FIELD_PUSHER_IS_COMMITTER = 'pusher-is-committer'; 47 46 const FIELD_PATH = 'path'; 48 47 ··· 100 99 const VALUE_BUILD_PLAN = 'buildplan'; 101 100 const VALUE_TASK_PRIORITY = 'taskpriority'; 102 101 const VALUE_TASK_STATUS = 'taskstatus'; 103 - const VALUE_ARCANIST_PROJECT = 'arcanistprojects'; 104 102 const VALUE_LEGAL_DOCUMENTS = 'legaldocuments'; 105 103 const VALUE_APPLICATION_EMAIL = 'applicationemail'; 106 104 ··· 385 383 self::FIELD_APPLICATION_EMAIL => pht('Receiving email address'), 386 384 self::FIELD_TASK_PRIORITY => pht('Task priority'), 387 385 self::FIELD_TASK_STATUS => pht('Task status'), 388 - self::FIELD_ARCANIST_PROJECT => pht('Arcanist Project'), 389 386 self::FIELD_PUSHER_IS_COMMITTER => pht('Pusher same as committer'), 390 387 self::FIELD_PATH => pht('Path'), 391 388 ) + $this->getCustomFieldNameMap(); ··· 441 438 case self::FIELD_PUSHER: 442 439 case self::FIELD_TASK_PRIORITY: 443 440 case self::FIELD_TASK_STATUS: 444 - case self::FIELD_ARCANIST_PROJECT: 445 441 return array( 446 442 self::CONDITION_IS_ANY, 447 443 self::CONDITION_IS_NOT_ANY, ··· 946 942 return self::VALUE_TASK_PRIORITY; 947 943 case self::FIELD_TASK_STATUS: 948 944 return self::VALUE_TASK_STATUS; 949 - case self::FIELD_ARCANIST_PROJECT: 950 - return self::VALUE_ARCANIST_PROJECT; 951 945 default: 952 946 return self::VALUE_USER; 953 947 } ··· 1203 1197 $rule_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL; 1204 1198 1205 1199 $action_type = $action->getAction(); 1206 - $action_name = idx($this->getActionNameMap($rule_global), $action_type); 1200 + 1201 + $default = $this->isHeraldCustomKey($action_type) 1202 + ? pht('(Unknown Custom Action "%s") equals', $action_type) 1203 + : pht('(Unknown Action "%s") equals', $action_type); 1204 + 1205 + $action_name = idx( 1206 + $this->getActionNameMap($rule_global), 1207 + $action_type, 1208 + $default); 1207 1209 1208 1210 $target = $this->renderActionTargetAsText($action, $handles); 1209 1211 ··· 1525 1527 $supported = $this->getActions($rule_type); 1526 1528 $supported = array_fuse($supported); 1527 1529 if (empty($supported[$action])) { 1528 - throw new Exception( 1530 + return new HeraldApplyTranscript( 1531 + $effect, 1532 + false, 1529 1533 pht( 1530 1534 'Adapter "%s" does not support action "%s" for rule type "%s".', 1531 1535 get_class($this), ··· 1548 1552 $result = $this->handleCustomHeraldEffect($effect); 1549 1553 1550 1554 if (!$result) { 1551 - throw new Exception( 1555 + return new HeraldApplyTranscript( 1556 + $effect, 1557 + false, 1552 1558 pht( 1553 1559 'No custom action exists to handle rule action "%s".', 1554 1560 $action));
+4 -1
src/applications/herald/adapter/HeraldDifferentialDiffAdapter.php
··· 154 154 pht('Blocked diff.')); 155 155 break; 156 156 default: 157 - throw new Exception(pht('No rules to handle action "%s"!', $action)); 157 + $result[] = new HeraldApplyTranscript( 158 + $effect, 159 + false, 160 + pht('No rules to handle action "%s"!', $action)); 158 161 } 159 162 } 160 163
-3
src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php
··· 80 80 self::FIELD_AFFECTED_PACKAGE, 81 81 self::FIELD_AFFECTED_PACKAGE_OWNER, 82 82 self::FIELD_IS_NEW_OBJECT, 83 - self::FIELD_ARCANIST_PROJECT, 84 83 ), 85 84 parent::getFields()); 86 85 } ··· 259 258 $packages = $this->loadAffectedPackages(); 260 259 return PhabricatorOwnersOwner::loadAffiliatedUserPHIDs( 261 260 mpull($packages, 'getID')); 262 - case self::FIELD_ARCANIST_PROJECT: 263 - return $this->revision->getArcanistProjectPHID(); 264 261 } 265 262 266 263 return parent::getHeraldField($field);
+32 -10
src/applications/herald/controller/HeraldRuleController.php
··· 320 320 try { 321 321 $adapter->willSaveAction($rule, $obj); 322 322 } catch (HeraldInvalidActionException $ex) { 323 - $errors[] = $ex; 323 + $errors[] = $ex->getMessage(); 324 324 } 325 325 326 326 $actions[] = $obj; ··· 354 354 if ($rule->getConditions()) { 355 355 $serial_conditions = array(); 356 356 foreach ($rule->getConditions() as $condition) { 357 - 358 357 $value = $condition->getValue(); 359 358 switch ($condition->getFieldName()) { 360 359 case HeraldAdapter::FIELD_TASK_PRIORITY: ··· 394 393 $serial_actions = array( 395 394 array('default', ''), 396 395 ); 396 + 397 397 if ($rule->getActions()) { 398 398 $serial_actions = array(); 399 399 foreach ($rule->getActions() as $action) { 400 - 401 400 switch ($action->getAction()) { 402 401 case HeraldAdapter::ACTION_FLAG: 403 402 case HeraldAdapter::ACTION_BLOCK: ··· 438 437 // names of, so that saving a rule without touching anything doesn't change 439 438 // it. 440 439 foreach ($rule->getConditions() as $condition) { 441 - if (empty($field_map[$condition->getFieldName()])) { 442 - $field_map[$condition->getFieldName()] = pht('<Unknown Field>'); 440 + $field_name = $condition->getFieldName(); 441 + 442 + if (empty($field_map[$field_name])) { 443 + $field_map[$field_name] = pht('<Unknown Field "%s">', $field_name); 443 444 } 444 445 } 445 446 446 447 $actions = $adapter->getActions($rule->getRuleType()); 447 448 $action_map = array_select_keys($all_actions, $actions); 448 449 450 + // Populate any actions which exist in the rule but which we don't know the 451 + // names of, so that saving a rule without touching anything doesn't change 452 + // it. 453 + foreach ($rule->getActions() as $action) { 454 + $action_name = $action->getAction(); 455 + 456 + if (empty($action_map[$action_name])) { 457 + $action_map[$action_name] = pht('<Unknown Action "%s">', $action_name); 458 + } 459 + } 460 + 461 + 449 462 $config_info = array(); 450 463 $config_info['fields'] = $field_map; 451 464 $config_info['conditions'] = $all_conditions; 452 465 $config_info['actions'] = $action_map; 453 466 454 467 foreach ($config_info['fields'] as $field => $name) { 455 - $field_conditions = $adapter->getConditionsForField($field); 468 + try { 469 + $field_conditions = $adapter->getConditionsForField($field); 470 + } catch (Exception $ex) { 471 + $field_conditions = array(HeraldAdapter::CONDITION_UNCONDITIONALLY); 472 + } 456 473 $config_info['conditionMap'][$field] = $field_conditions; 457 474 } 458 475 ··· 468 485 $config_info['rule_type'] = $rule->getRuleType(); 469 486 470 487 foreach ($config_info['actions'] as $action => $name) { 471 - $config_info['targets'][$action] = $adapter->getValueTypeForAction( 472 - $action, 473 - $rule->getRuleType()); 488 + try { 489 + $action_value = $adapter->getValueTypeForAction( 490 + $action, 491 + $rule->getRuleType()); 492 + } catch (Exception $ex) { 493 + $action_value = array(HeraldAdapter::VALUE_NONE); 494 + } 495 + 496 + $config_info['targets'][$action] = $action_value; 474 497 } 475 498 476 499 $changeflag_options = ··· 603 626 'taskpriority' => new ManiphestTaskPriorityDatasource(), 604 627 'taskstatus' => new ManiphestTaskStatusDatasource(), 605 628 'buildplan' => new HarbormasterBuildPlanDatasource(), 606 - 'arcanistprojects' => new DiffusionArcanistProjectDatasource(), 607 629 'package' => new PhabricatorOwnersPackageDatasource(), 608 630 'project' => new PhabricatorProjectDatasource(), 609 631 'user' => new PhabricatorPeopleDatasource(),
+4 -1
src/applications/herald/controller/HeraldTranscriptController.php
··· 380 380 $item->setState(PHUIObjectItemView::STATE_FAIL); 381 381 } 382 382 383 - $rule = idx($action_names, $apply_xscript->getAction(), pht('Unknown')); 383 + $rule = idx( 384 + $action_names, 385 + $apply_xscript->getAction(), 386 + pht('Unknown Action "%s"', $apply_xscript->getAction())); 384 387 385 388 $item->setHeader(pht('%s: %s', $rule, $target)); 386 389 $item->addAttribute($apply_xscript->getReason());
+10
src/applications/herald/engine/HeraldEngine.php
··· 272 272 $result = false; 273 273 } else { 274 274 foreach ($conditions as $condition) { 275 + try { 276 + $object->getHeraldField($condition->getFieldName()); 277 + } catch (Exception $ex) { 278 + $reason = pht( 279 + 'Field "%s" does not exist!', 280 + $condition->getFieldName()); 281 + $result = false; 282 + break; 283 + } 284 + 275 285 $match = $this->doesConditionMatch($rule, $condition, $object); 276 286 277 287 if (!$all && $match) {
+1 -1
src/applications/herald/query/HeraldTranscriptSearchEngine.php
··· 125 125 } 126 126 $item->addAttribute($handles[$xscript->getObjectPHID()]->renderLink()); 127 127 $item->addAttribute( 128 - pht('%d ms', number_format((int)(1000 * $xscript->getDuration())))); 128 + pht('%s ms', new PhutilNumber((int)(1000 * $xscript->getDuration())))); 129 129 $item->addIcon( 130 130 'none', 131 131 phabricator_datetime($xscript->getTime(), $viewer));
+2 -3
src/applications/owners/application/PhabricatorOwnersApplication.php
··· 42 42 public function getRoutes() { 43 43 return array( 44 44 '/owners/' => array( 45 - '' => 'PhabricatorOwnersListController', 46 - 'view/(?P<view>[^/]+)/' => 'PhabricatorOwnersListController', 45 + '(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorOwnersListController', 47 46 'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorOwnersEditController', 48 47 'new/' => 'PhabricatorOwnersEditController', 49 48 'package/(?P<id>[1-9]\d*)/' => 'PhabricatorOwnersDetailController', 50 - 'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorOwnersDeleteController', 49 + 'paths/(?P<id>[1-9]\d*)/' => 'PhabricatorOwnersPathsController', 51 50 ), 52 51 ); 53 52 }
+1 -68
src/applications/owners/controller/PhabricatorOwnersController.php
··· 1 1 <?php 2 2 3 - abstract class PhabricatorOwnersController extends PhabricatorController { 4 - 5 - private $filter; 6 - 7 - private function getSideNavFilter() { 8 - return $this->filter; 9 - } 10 - protected function setSideNavFilter($filter) { 11 - $this->filter = $filter; 12 - return $this; 13 - } 14 - 15 - public function buildSideNavView() { 16 - $nav = new AphrontSideNavFilterView(); 17 - $base_uri = new PhutilURI('/owners/'); 18 - $nav->setBaseURI($base_uri); 19 - 20 - $nav->addLabel(pht('Packages')); 21 - $this->getExtraPackageViews($nav); 22 - $nav->addFilter('view/owned', pht('Owned')); 23 - $nav->addFilter('view/projects', pht('Projects')); 24 - $nav->addFilter('view/all', pht('All')); 25 - 26 - $nav->selectFilter($this->getSideNavFilter(), 'view/owned'); 27 - 28 - $filter = $nav->getSelectedFilter(); 29 - switch ($filter) { 30 - case 'view/owned': 31 - $title = pht('Owned Packages'); 32 - break; 33 - case 'view/all': 34 - $title = pht('All Packages'); 35 - break; 36 - case 'view/projects': 37 - $title = pht('Projects'); 38 - break; 39 - case 'new': 40 - $title = pht('New Package'); 41 - break; 42 - default: 43 - $title = pht('Package'); 44 - break; 45 - } 46 - 47 - return $nav; 48 - } 49 - 50 - protected function buildApplicationCrumbs() { 51 - $crumbs = parent::buildApplicationCrumbs(); 52 - 53 - $crumbs->addAction( 54 - id(new PHUIListItemView()) 55 - ->setName(pht('Create Package')) 56 - ->setHref('/owners/new/') 57 - ->setIcon('fa-plus-square')); 58 - 59 - return $crumbs; 60 - } 61 - 62 - public function buildApplicationMenu() { 63 - return $this->buildSideNavView()->getMenu(); 64 - } 65 - 66 - protected function getExtraPackageViews(AphrontSideNavFilterView $view) { 67 - return; 68 - } 69 - 70 - } 3 + abstract class PhabricatorOwnersController extends PhabricatorController {}
-44
src/applications/owners/controller/PhabricatorOwnersDeleteController.php
··· 1 - <?php 2 - 3 - final class PhabricatorOwnersDeleteController 4 - extends PhabricatorOwnersController { 5 - 6 - private $id; 7 - 8 - public function willProcessRequest(array $data) { 9 - $this->id = $data['id']; 10 - } 11 - 12 - public function processRequest() { 13 - $request = $this->getRequest(); 14 - $user = $request->getUser(); 15 - 16 - $package = id(new PhabricatorOwnersPackage())->load($this->id); 17 - if (!$package) { 18 - return new Aphront404Response(); 19 - } 20 - 21 - if ($request->isDialogFormPost()) { 22 - id(new PhabricatorOwnersPackageEditor()) 23 - ->setActor($user) 24 - ->setPackage($package) 25 - ->delete(); 26 - return id(new AphrontRedirectResponse())->setURI('/owners/'); 27 - } 28 - 29 - $text = pht( 30 - 'Are you sure you want to delete the "%s" package? This '. 31 - 'operation can not be undone.', 32 - $package->getName()); 33 - $dialog = id(new AphrontDialogView()) 34 - ->setUser($user) 35 - ->setTitle(pht('Really delete this package?')) 36 - ->appendChild(phutil_tag('p', array(), $text)) 37 - ->addSubmitButton(pht('Delete')) 38 - ->addCancelButton('/owners/package/'.$package->getID().'/') 39 - ->setSubmitURI($request->getRequestURI()); 40 - 41 - return id(new AphrontDialogResponse())->setDialog($dialog); 42 - } 43 - 44 - }
+174 -116
src/applications/owners/controller/PhabricatorOwnersDetailController.php
··· 3 3 final class PhabricatorOwnersDetailController 4 4 extends PhabricatorOwnersController { 5 5 6 - private $id; 7 - private $package; 8 - 9 - public function willProcessRequest(array $data) { 10 - $this->id = $data['id']; 6 + public function shouldAllowPublic() { 7 + return true; 11 8 } 12 9 13 - public function processRequest() { 14 - $request = $this->getRequest(); 15 - $user = $request->getUser(); 10 + public function handleRequest(AphrontRequest $request) { 11 + $viewer = $this->getViewer(); 16 12 17 - $package = id(new PhabricatorOwnersPackage())->load($this->id); 13 + $package = id(new PhabricatorOwnersPackageQuery()) 14 + ->setViewer($viewer) 15 + ->withIDs(array($request->getURIData('id'))) 16 + ->executeOne(); 18 17 if (!$package) { 19 18 return new Aphront404Response(); 20 19 } 21 - $this->package = $package; 22 20 23 21 $paths = $package->loadPaths(); 24 - $owners = $package->loadOwners(); 25 22 26 23 $repository_phids = array(); 27 24 foreach ($paths as $path) { ··· 30 27 31 28 if ($repository_phids) { 32 29 $repositories = id(new PhabricatorRepositoryQuery()) 33 - ->setViewer($user) 30 + ->setViewer($viewer) 34 31 ->withPHIDs(array_keys($repository_phids)) 35 32 ->execute(); 36 33 $repositories = mpull($repositories, null, 'getPHID'); ··· 38 35 $repositories = array(); 39 36 } 40 37 41 - $phids = array(); 42 - foreach ($owners as $owner) { 43 - $phids[$owner->getUserPHID()] = true; 44 - } 45 - $phids = array_keys($phids); 38 + $actions = $this->buildPackageActionView($package); 39 + $properties = $this->buildPackagePropertyView($package); 40 + $properties->setActionList($actions); 46 41 47 - $handles = $this->loadViewerHandles($phids); 42 + $header = id(new PHUIHeaderView()) 43 + ->setUser($viewer) 44 + ->setHeader($package->getName()) 45 + ->setPolicyObject($package); 48 46 49 - $rows = array(); 50 - 51 - $rows[] = array(pht('Name'), $package->getName()); 52 - $rows[] = array(pht('Description'), $package->getDescription()); 53 - 54 - $primary_owner = null; 55 - $primary_phid = $package->getPrimaryOwnerPHID(); 56 - if ($primary_phid && isset($handles[$primary_phid])) { 57 - $primary_owner = phutil_tag( 58 - 'strong', 59 - array(), 60 - $handles[$primary_phid]->renderLink()); 61 - } 62 - $rows[] = array(pht('Primary Owner'), $primary_owner); 63 - 64 - $owner_links = array(); 65 - foreach ($owners as $owner) { 66 - $owner_links[] = $handles[$owner->getUserPHID()]->renderLink(); 67 - } 68 - $owner_links = phutil_implode_html(phutil_tag('br'), $owner_links); 69 - $rows[] = array(pht('Owners'), $owner_links); 70 - 71 - $rows[] = array( 72 - pht('Auditing'), 73 - $package->getAuditingEnabled() ? 74 - pht('Enabled') : 75 - pht('Disabled'), 76 - ); 77 - 78 - $path_links = array(); 79 - foreach ($paths as $path) { 80 - $repo = idx($repositories, $path->getRepositoryPHID()); 81 - if (!$repo) { 82 - continue; 83 - } 84 - $href = DiffusionRequest::generateDiffusionURI( 85 - array( 86 - 'callsign' => $repo->getCallsign(), 87 - 'branch' => $repo->getDefaultBranch(), 88 - 'path' => $path->getPath(), 89 - 'action' => 'browse', 90 - )); 91 - $repo_name = phutil_tag('strong', array(), $repo->getName()); 92 - $path_link = phutil_tag( 93 - 'a', 94 - array( 95 - 'href' => (string)$href, 96 - ), 97 - $path->getPath()); 98 - $path_links[] = hsprintf( 99 - '%s %s %s', 100 - ($path->getExcluded() ? "\xE2\x80\x93" : '+'), 101 - $repo_name, 102 - $path_link); 103 - } 104 - $path_links = phutil_implode_html(phutil_tag('br'), $path_links); 105 - $rows[] = array(pht('Paths'), $path_links); 106 - 107 - $table = new AphrontTableView($rows); 108 - $table->setColumnClasses( 109 - array( 110 - 'header', 111 - 'wide', 112 - )); 113 - 114 - $panel = new PHUIObjectBoxView(); 115 - $header = new PHUIHeaderView(); 116 - $header->setHeader( 117 - pht('Package Details for "%s"', $package->getName())); 118 - $header->addActionLink( 119 - id(new PHUIButtonView()) 120 - ->setTag('a') 121 - ->setHref('/owners/delete/'.$package->getID().'/') 122 - ->addSigil('workflow') 123 - ->setText(pht('Delete Package'))); 124 - 125 - $header->addActionLink( 126 - id(new PHUIButtonView()) 127 - ->setTag('a') 128 - ->setHref('/owners/edit/'.$package->getID().'/') 129 - ->setText(pht('Edit Package'))); 130 - 131 - $panel->setHeader($header); 132 - $panel->setTable($table); 133 - 134 - $key = 'package/'.$package->getID(); 135 - $this->setSideNavFilter($key); 47 + $panel = id(new PHUIObjectBoxView()) 48 + ->setHeader($header) 49 + ->addPropertyList($properties); 136 50 137 51 $commit_views = array(); 138 52 ··· 151 65 ->execute(); 152 66 if ($attention_commits) { 153 67 $view = id(new PhabricatorAuditListView()) 154 - ->setUser($user) 68 + ->setUser($viewer) 155 69 ->setCommits($attention_commits); 156 70 157 71 $commit_views[] = array( ··· 172 86 ->execute(); 173 87 174 88 $view = id(new PhabricatorAuditListView()) 175 - ->setUser($user) 89 + ->setUser($viewer) 176 90 ->setCommits($all_commits) 177 91 ->setNoDataString(pht('No commits in this package.')); 178 92 ··· 210 124 $crumbs = $this->buildApplicationCrumbs(); 211 125 $crumbs->addTextCrumb($package->getName()); 212 126 213 - $nav = $this->buildSideNavView(); 214 - $nav->appendChild($crumbs); 215 - $nav->appendChild($panel); 216 - $nav->appendChild($commit_panels); 127 + $timeline = $this->buildTransactionTimeline( 128 + $package, 129 + new PhabricatorOwnersPackageTransactionQuery()); 130 + $timeline->setShouldTerminate(true); 217 131 218 132 return $this->buildApplicationPage( 219 - $nav, 133 + array( 134 + $crumbs, 135 + $panel, 136 + $this->renderPathsTable($paths, $repositories), 137 + $commit_panels, 138 + $timeline, 139 + ), 220 140 array( 221 - 'title' => pht('Package %s', $package->getName()), 141 + 'title' => $package->getName(), 222 142 )); 223 143 } 224 144 225 - protected function getExtraPackageViews(AphrontSideNavFilterView $view) { 226 - $package = $this->package; 227 - $view->addFilter('package/'.$package->getID(), pht('Details')); 145 + 146 + private function buildPackagePropertyView(PhabricatorOwnersPackage $package) { 147 + $viewer = $this->getViewer(); 148 + 149 + $view = id(new PHUIPropertyListView()) 150 + ->setUser($viewer); 151 + 152 + $primary_phid = $package->getPrimaryOwnerPHID(); 153 + if ($primary_phid) { 154 + $primary_owner = $viewer->renderHandle($primary_phid); 155 + } else { 156 + $primary_owner = phutil_tag('em', array(), pht('None')); 157 + } 158 + $view->addProperty(pht('Primary Owner'), $primary_owner); 159 + 160 + // TODO: needOwners() this on the Query. 161 + $owners = $package->loadOwners(); 162 + if ($owners) { 163 + $owner_list = $viewer->renderHandleList(mpull($owners, 'getUserPHID')); 164 + } else { 165 + $owner_list = phutil_tag('em', array(), pht('None')); 166 + } 167 + $view->addProperty(pht('Owners'), $owner_list); 168 + 169 + if ($package->getAuditingEnabled()) { 170 + $auditing = pht('Enabled'); 171 + } else { 172 + $auditing = pht('Disabled'); 173 + } 174 + $view->addProperty(pht('Auditing'), $auditing); 175 + 176 + $description = $package->getDescription(); 177 + if (strlen($description)) { 178 + $view->addSectionHeader(pht('Description')); 179 + $view->addTextContent( 180 + $output = PhabricatorMarkupEngine::renderOneObject( 181 + id(new PhabricatorMarkupOneOff())->setContent($description), 182 + 'default', 183 + $viewer)); 184 + } 185 + 186 + return $view; 187 + } 188 + 189 + private function buildPackageActionView(PhabricatorOwnersPackage $package) { 190 + $viewer = $this->getViewer(); 191 + 192 + // TODO: Implement this capability. 193 + $can_edit = true; 194 + 195 + $id = $package->getID(); 196 + $edit_uri = $this->getApplicationURI("/edit/{$id}/"); 197 + $paths_uri = $this->getApplicationURI("/paths/{$id}/"); 198 + 199 + $view = id(new PhabricatorActionListView()) 200 + ->setUser($viewer) 201 + ->setObject($package) 202 + ->addAction( 203 + id(new PhabricatorActionView()) 204 + ->setName(pht('Edit Package')) 205 + ->setIcon('fa-pencil') 206 + ->setDisabled(!$can_edit) 207 + ->setWorkflow(!$can_edit) 208 + ->setHref($edit_uri)) 209 + ->addAction( 210 + id(new PhabricatorActionView()) 211 + ->setName(pht('Edit Paths')) 212 + ->setIcon('fa-folder-open') 213 + ->setDisabled(!$can_edit) 214 + ->setWorkflow(!$can_edit) 215 + ->setHref($paths_uri)); 216 + 217 + return $view; 218 + } 219 + 220 + private function renderPathsTable(array $paths, array $repositories) { 221 + $viewer = $this->getViewer(); 222 + 223 + $rows = array(); 224 + foreach ($paths as $path) { 225 + $repo = idx($repositories, $path->getRepositoryPHID()); 226 + if (!$repo) { 227 + continue; 228 + } 229 + $href = DiffusionRequest::generateDiffusionURI( 230 + array( 231 + 'callsign' => $repo->getCallsign(), 232 + 'branch' => $repo->getDefaultBranch(), 233 + 'path' => $path->getPath(), 234 + 'action' => 'browse', 235 + )); 236 + 237 + $path_link = phutil_tag( 238 + 'a', 239 + array( 240 + 'href' => (string)$href, 241 + ), 242 + $path->getPath()); 243 + 244 + $rows[] = array( 245 + ($path->getExcluded() ? '-' : '+'), 246 + $repo->getName(), 247 + $path_link, 248 + ); 249 + } 250 + 251 + $info = null; 252 + if (!$paths) { 253 + $info = id(new PHUIInfoView()) 254 + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) 255 + ->setErrors( 256 + array( 257 + pht( 258 + 'This package does not contain any paths yet. Use '. 259 + '"Edit Paths" to add some.'), 260 + )); 261 + } 262 + 263 + $table = id(new AphrontTableView($rows)) 264 + ->setHeaders( 265 + array( 266 + null, 267 + pht('Repository'), 268 + pht('Path'), 269 + )) 270 + ->setColumnClasses( 271 + array( 272 + null, 273 + null, 274 + 'wide', 275 + )); 276 + 277 + $box = id(new PHUIObjectBoxView()) 278 + ->setHeaderText(pht('Paths')) 279 + ->appendChild($table); 280 + 281 + if ($info) { 282 + $box->setInfoView($info); 283 + } 284 + 285 + return $box; 228 286 } 229 287 230 288 }
+105 -185
src/applications/owners/controller/PhabricatorOwnersEditController.php
··· 3 3 final class PhabricatorOwnersEditController 4 4 extends PhabricatorOwnersController { 5 5 6 - private $id; 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getUser(); 7 8 8 - public function willProcessRequest(array $data) { 9 - $this->id = idx($data, 'id'); 10 - } 11 - 12 - public function processRequest() { 13 - $request = $this->getRequest(); 14 - $user = $request->getUser(); 15 - 16 - if ($this->id) { 17 - $package = id(new PhabricatorOwnersPackage())->load($this->id); 9 + $id = $request->getURIData('id'); 10 + if ($id) { 11 + $package = id(new PhabricatorOwnersPackageQuery()) 12 + ->setViewer($viewer) 13 + ->withIDs(array($id)) 14 + ->requireCapabilities( 15 + array( 16 + PhabricatorPolicyCapability::CAN_VIEW, 17 + // TODO: Support this capability. 18 + // PhabricatorPolicyCapability::CAN_EDIT, 19 + )) 20 + ->executeOne(); 18 21 if (!$package) { 19 22 return new Aphront404Response(); 20 23 } 24 + $is_new = false; 21 25 } else { 22 - $package = new PhabricatorOwnersPackage(); 23 - $package->setPrimaryOwnerPHID($user->getPHID()); 26 + $package = PhabricatorOwnersPackage::initializeNewPackage($viewer); 27 + $is_new = true; 24 28 } 25 29 26 30 $e_name = true; 27 31 $e_primary = true; 28 32 29 - $errors = array(); 33 + $v_name = $package->getName(); 34 + $v_primary = $package->getPrimaryOwnerPHID(); 35 + // TODO: Pull these off needOwners() on the Query. 36 + $v_owners = mpull($package->loadOwners(), 'getUserPHID'); 37 + $v_auditing = $package->getAuditingEnabled(); 38 + $v_description = $package->getDescription(); 39 + 30 40 41 + $errors = array(); 31 42 if ($request->isFormPost()) { 32 - $package->setName($request->getStr('name')); 33 - $package->setDescription($request->getStr('description')); 34 - $old_auditing_enabled = $package->getAuditingEnabled(); 35 - $package->setAuditingEnabled( 36 - ($request->getStr('auditing') === 'enabled') 37 - ? 1 38 - : 0); 43 + $xactions = array(); 39 44 40 - $primary = $request->getArr('primary'); 41 - $primary = reset($primary); 42 - $old_primary = $package->getPrimaryOwnerPHID(); 43 - $package->setPrimaryOwnerPHID($primary); 45 + $v_name = $request->getStr('name'); 46 + $v_primary = head($request->getArr('primary')); 47 + $v_owners = $request->getArr('owners'); 48 + $v_auditing = ($request->getStr('auditing') == 'enabled'); 49 + $v_description = $request->getStr('description'); 44 50 45 - $owners = $request->getArr('owners'); 46 - if ($primary) { 47 - array_unshift($owners, $primary); 51 + if ($v_primary) { 52 + $v_owners[] = $v_primary; 53 + $v_owners = array_unique($v_owners); 48 54 } 49 - $owners = array_unique($owners); 55 + 56 + $type_name = PhabricatorOwnersPackageTransaction::TYPE_NAME; 57 + $type_primary = PhabricatorOwnersPackageTransaction::TYPE_PRIMARY; 58 + $type_owners = PhabricatorOwnersPackageTransaction::TYPE_OWNERS; 59 + $type_auditing = PhabricatorOwnersPackageTransaction::TYPE_AUDITING; 60 + $type_description = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION; 61 + 62 + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) 63 + ->setTransactionType($type_name) 64 + ->setNewValue($v_name); 65 + 66 + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) 67 + ->setTransactionType($type_primary) 68 + ->setNewValue($v_primary); 50 69 51 - $paths = $request->getArr('path'); 52 - $repos = $request->getArr('repo'); 53 - $excludes = $request->getArr('exclude'); 70 + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) 71 + ->setTransactionType($type_owners) 72 + ->setNewValue($v_owners); 54 73 55 - $path_refs = array(); 56 - for ($ii = 0; $ii < count($paths); $ii++) { 57 - if (empty($paths[$ii]) || empty($repos[$ii])) { 58 - continue; 59 - } 60 - $path_refs[] = array( 61 - 'repositoryPHID' => $repos[$ii], 62 - 'path' => $paths[$ii], 63 - 'excluded' => $excludes[$ii], 64 - ); 65 - } 74 + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) 75 + ->setTransactionType($type_auditing) 76 + ->setNewValue($v_auditing); 66 77 67 - if (!strlen($package->getName())) { 68 - $e_name = pht('Required'); 69 - $errors[] = pht('Package name is required.'); 70 - } else { 71 - $e_name = null; 72 - } 78 + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) 79 + ->setTransactionType($type_description) 80 + ->setNewValue($v_description); 73 81 74 - if (!$package->getPrimaryOwnerPHID()) { 75 - $e_primary = pht('Required'); 76 - $errors[] = pht('Package must have a primary owner.'); 77 - } else { 78 - $e_primary = null; 79 - } 82 + $editor = id(new PhabricatorOwnersPackageTransactionEditor()) 83 + ->setActor($viewer) 84 + ->setContentSourceFromRequest($request) 85 + ->setContinueOnNoEffect(true); 80 86 81 - if (!$path_refs) { 82 - $errors[] = pht('Package must include at least one path.'); 83 - } 87 + try { 88 + $editor->applyTransactions($package, $xactions); 84 89 85 - if (!$errors) { 86 - $package->attachUnsavedOwners($owners); 87 - $package->attachUnsavedPaths($path_refs); 88 - $package->attachOldAuditingEnabled($old_auditing_enabled); 89 - $package->attachOldPrimaryOwnerPHID($old_primary); 90 - try { 91 - id(new PhabricatorOwnersPackageEditor()) 92 - ->setActor($user) 93 - ->setPackage($package) 94 - ->save(); 95 - return id(new AphrontRedirectResponse()) 96 - ->setURI('/owners/package/'.$package->getID().'/'); 97 - } catch (AphrontDuplicateKeyQueryException $ex) { 98 - $e_name = pht('Duplicate'); 99 - $errors[] = pht('Package name must be unique.'); 90 + $id = $package->getID(); 91 + if ($is_new) { 92 + $next_uri = '/owners/paths/'.$id.'/'; 93 + } else { 94 + $next_uri = '/owners/package/'.$id.'/'; 100 95 } 101 - } 102 - } else { 103 - $owners = $package->loadOwners(); 104 - $owners = mpull($owners, 'getUserPHID'); 105 96 106 - $paths = $package->loadPaths(); 107 - $path_refs = array(); 108 - foreach ($paths as $path) { 109 - $path_refs[] = array( 110 - 'repositoryPHID' => $path->getRepositoryPHID(), 111 - 'path' => $path->getPath(), 112 - 'excluded' => $path->getExcluded(), 113 - ); 97 + return id(new AphrontRedirectResponse())->setURI($next_uri); 98 + } catch (AphrontDuplicateKeyQueryException $ex) { 99 + $e_name = pht('Duplicate'); 100 + $errors[] = pht('Package name must be unique.'); 101 + } catch (PhabricatorApplicationTransactionValidationException $ex) { 102 + $validation_exception = $ex; 103 + 104 + $e_name = $ex->getShortMessage($type_name); 105 + $e_primary = $ex->getShortMessage($type_primary); 114 106 } 115 107 } 116 108 117 - $primary = $package->getPrimaryOwnerPHID(); 118 - if ($primary) { 119 - $value_primary_owner = array($primary); 109 + if ($v_primary) { 110 + $value_primary_owner = array($v_primary); 120 111 } else { 121 112 $value_primary_owner = array(); 122 113 } 123 114 124 - if ($package->getID()) { 125 - $title = pht('Edit Package'); 126 - $side_nav_filter = 'edit/'.$this->id; 127 - } else { 115 + if ($is_new) { 116 + $cancel_uri = '/owners/'; 128 117 $title = pht('New Package'); 129 - $side_nav_filter = 'new'; 118 + $button_text = pht('Continue'); 119 + } else { 120 + $cancel_uri = '/owners/package/'.$package->getID().'/'; 121 + $title = pht('Edit Package'); 122 + $button_text = pht('Save Package'); 130 123 } 131 - $this->setSideNavFilter($side_nav_filter); 132 - 133 - $repos = id(new PhabricatorRepositoryQuery()) 134 - ->setViewer($user) 135 - ->execute(); 136 - 137 - $default_paths = array(); 138 - foreach ($repos as $repo) { 139 - $default_path = $repo->getDetail('default-owners-path'); 140 - if ($default_path) { 141 - $default_paths[$repo->getPHID()] = $default_path; 142 - } 143 - } 144 - 145 - $repos = mpull($repos, 'getCallsign', 'getPHID'); 146 - asort($repos); 147 - 148 - $template = new AphrontTypeaheadTemplateView(); 149 - $template = $template->render(); 150 - 151 - Javelin::initBehavior( 152 - 'owners-path-editor', 153 - array( 154 - 'root' => 'path-editor', 155 - 'table' => 'paths', 156 - 'add_button' => 'addpath', 157 - 'repositories' => $repos, 158 - 'input_template' => $template, 159 - 'pathRefs' => $path_refs, 160 - 161 - 'completeURI' => '/diffusion/services/path/complete/', 162 - 'validateURI' => '/diffusion/services/path/validate/', 163 - 164 - 'repositoryDefaultPaths' => $default_paths, 165 - )); 166 - 167 - require_celerity_resource('owners-path-editor-css'); 168 - 169 - $cancel_uri = $package->getID() 170 - ? '/owners/package/'.$package->getID().'/' 171 - : '/owners/'; 172 124 173 125 $form = id(new AphrontFormView()) 174 - ->setUser($user) 126 + ->setUser($viewer) 175 127 ->appendChild( 176 128 id(new AphrontFormTextControl()) 177 129 ->setLabel(pht('Name')) 178 130 ->setName('name') 179 - ->setValue($package->getName()) 131 + ->setValue($v_name) 180 132 ->setError($e_name)) 181 133 ->appendControl( 182 134 id(new AphrontFormTokenizerControl()) ··· 191 143 ->setDatasource(new PhabricatorProjectOrUserDatasource()) 192 144 ->setLabel(pht('Owners')) 193 145 ->setName('owners') 194 - ->setValue($owners)) 146 + ->setValue($v_owners)) 195 147 ->appendChild( 196 148 id(new AphrontFormSelectControl()) 197 149 ->setName('auditing') ··· 202 154 'this package will be reviewed to make sure an owner '. 203 155 'of the package is involved and the commit message has '. 204 156 'a valid revision, reviewed by, and author.')) 205 - ->setOptions(array( 206 - 'disabled' => pht('Disabled'), 207 - 'enabled' => pht('Enabled'), 208 - )) 209 - ->setValue( 210 - $package->getAuditingEnabled() 211 - ? 'enabled' 212 - : 'disabled')) 213 - ->appendChild( 214 - id(new PHUIFormInsetView()) 215 - ->setTitle(pht('Paths')) 216 - ->addDivAttributes(array('id' => 'path-editor')) 217 - ->setRightButton(javelin_tag( 218 - 'a', 219 - array( 220 - 'href' => '#', 221 - 'class' => 'button green', 222 - 'sigil' => 'addpath', 223 - 'mustcapture' => true, 224 - ), 225 - pht('Add New Path'))) 226 - ->setDescription( 227 - pht( 228 - 'Specify the files and directories which comprise '. 229 - 'this package.')) 230 - ->setContent(javelin_tag( 231 - 'table', 232 - array( 233 - 'class' => 'owners-path-editor-table', 234 - 'sigil' => 'paths', 235 - ), 236 - ''))) 157 + ->setOptions( 158 + array( 159 + 'disabled' => pht('Disabled'), 160 + 'enabled' => pht('Enabled'), 161 + )) 162 + ->setValue(($v_auditing ? 'enabled' : 'disabled'))) 237 163 ->appendChild( 238 - id(new AphrontFormTextAreaControl()) 164 + id(new PhabricatorRemarkupControl()) 165 + ->setUser($viewer) 239 166 ->setLabel(pht('Description')) 240 167 ->setName('description') 241 - ->setValue($package->getDescription())) 168 + ->setValue($v_description)) 242 169 ->appendChild( 243 170 id(new AphrontFormSubmitControl()) 244 171 ->addCancelButton($cancel_uri) 245 - ->setValue(pht('Save Package'))); 172 + ->setValue($button_text)); 246 173 247 174 $form_box = id(new PHUIObjectBoxView()) 248 175 ->setHeaderText($title) ··· 251 178 252 179 $crumbs = $this->buildApplicationCrumbs(); 253 180 if ($package->getID()) { 254 - $crumbs->addTextCrumb(pht('Edit %s', $package->getName())); 181 + $crumbs->addTextCrumb( 182 + $package->getName(), 183 + $this->getApplicationURI('package/'.$package->getID().'/')); 184 + $crumbs->addTextCrumb(pht('Edit')); 255 185 } else { 256 186 $crumbs->addTextCrumb(pht('New Package')); 257 187 } 258 188 259 - $nav = $this->buildSideNavView(); 260 - $nav->appendChild($crumbs); 261 - $nav->appendChild($form_box); 262 - 263 189 return $this->buildApplicationPage( 264 190 array( 265 - $nav, 191 + $crumbs, 192 + $form_box, 266 193 ), 267 194 array( 268 195 'title' => $title, 269 196 )); 270 197 } 271 198 272 - protected function getExtraPackageViews(AphrontSideNavFilterView $view) { 273 - if ($this->id) { 274 - $view->addFilter('edit/'.$this->id, pht('Edit')); 275 - } else { 276 - $view->addFilter('new', pht('New')); 277 - } 278 - } 279 199 }
+31 -319
src/applications/owners/controller/PhabricatorOwnersListController.php
··· 3 3 final class PhabricatorOwnersListController 4 4 extends PhabricatorOwnersController { 5 5 6 - protected $view; 7 - 8 - public function willProcessRequest(array $data) { 9 - $this->view = idx($data, 'view', 'owned'); 10 - $this->setSideNavFilter('view/'.$this->view); 6 + public function shouldAllowPublic() { 7 + return true; 11 8 } 12 9 13 - public function processRequest() { 14 - 15 - $request = $this->getRequest(); 16 - $user = $request->getUser(); 17 - 18 - $package = new PhabricatorOwnersPackage(); 19 - $owner = new PhabricatorOwnersOwner(); 20 - $path = new PhabricatorOwnersPath(); 21 - 22 - $repository_phid = ''; 23 - if ($request->getStr('repository') != '') { 24 - $repository_phid = id(new PhabricatorRepositoryQuery()) 25 - ->setViewer($user) 26 - ->withCallsigns(array($request->getStr('repository'))) 27 - ->executeOne() 28 - ->getPHID(); 29 - } 30 - 31 - switch ($this->view) { 32 - case 'search': 33 - $packages = array(); 34 - 35 - $conn_r = $package->establishConnection('r'); 36 - 37 - $where = array('1 = 1'); 38 - $join = array(); 39 - $having = ''; 40 - 41 - if ($request->getStr('name')) { 42 - $where[] = qsprintf( 43 - $conn_r, 44 - 'p.name LIKE %~', 45 - $request->getStr('name')); 46 - } 47 - 48 - if ($repository_phid || $request->getStr('path')) { 49 - 50 - $join[] = qsprintf( 51 - $conn_r, 52 - 'JOIN %T path ON path.packageID = p.id', 53 - $path->getTableName()); 54 - 55 - if ($repository_phid) { 56 - $where[] = qsprintf( 57 - $conn_r, 58 - 'path.repositoryPHID = %s', 59 - $repository_phid); 60 - } 61 - 62 - if ($request->getStr('path')) { 63 - $where[] = qsprintf( 64 - $conn_r, 65 - '(path.path LIKE %~ AND NOT path.excluded) OR 66 - %s LIKE CONCAT(REPLACE(path.path, %s, %s), %s)', 67 - $request->getStr('path'), 68 - $request->getStr('path'), 69 - '_', 70 - '\_', 71 - '%'); 72 - $having = 'HAVING MAX(path.excluded) = 0'; 73 - } 74 - 75 - } 76 - 77 - if ($request->getArr('owner')) { 78 - $join[] = qsprintf( 79 - $conn_r, 80 - 'JOIN %T o ON o.packageID = p.id', 81 - $owner->getTableName()); 82 - $where[] = qsprintf( 83 - $conn_r, 84 - 'o.userPHID IN (%Ls)', 85 - $request->getArr('owner')); 86 - } 87 - 88 - $data = queryfx_all( 89 - $conn_r, 90 - 'SELECT p.* FROM %T p %Q WHERE %Q GROUP BY p.id %Q', 91 - $package->getTableName(), 92 - implode(' ', $join), 93 - '('.implode(') AND (', $where).')', 94 - $having); 95 - $packages = $package->loadAllFromArray($data); 96 - 97 - $header = pht('Search Results'); 98 - $nodata = pht('No packages match your query.'); 99 - break; 100 - case 'owned': 101 - $data = queryfx_all( 102 - $package->establishConnection('r'), 103 - 'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID 104 - WHERE o.userPHID = %s GROUP BY p.id', 105 - $package->getTableName(), 106 - $owner->getTableName(), 107 - $user->getPHID()); 108 - $packages = $package->loadAllFromArray($data); 109 - 110 - $header = pht('Owned Packages'); 111 - $nodata = pht('No owned packages'); 112 - break; 113 - case 'projects': 114 - $projects = id(new PhabricatorProjectQuery()) 115 - ->setViewer($user) 116 - ->withMemberPHIDs(array($user->getPHID())) 117 - ->withStatus(PhabricatorProjectQuery::STATUS_ANY) 118 - ->execute(); 119 - $owner_phids = mpull($projects, 'getPHID'); 120 - if ($owner_phids) { 121 - $data = queryfx_all( 122 - $package->establishConnection('r'), 123 - 'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID 124 - WHERE o.userPHID IN (%Ls) GROUP BY p.id', 125 - $package->getTableName(), 126 - $owner->getTableName(), 127 - $owner_phids); 128 - } else { 129 - $data = array(); 130 - } 131 - $packages = $package->loadAllFromArray($data); 132 - 133 - $header = pht('Project Packages'); 134 - $nodata = pht('No owned packages'); 135 - break; 136 - case 'all': 137 - $packages = $package->loadAll(); 138 - 139 - $header = pht('All Packages'); 140 - $nodata = pht('There are no defined packages.'); 141 - break; 142 - } 143 - 144 - $content = $this->renderPackageTable( 145 - $packages, 146 - $header, 147 - $nodata); 148 - 149 - $filter = new AphrontListFilterView(); 150 - 151 - $owner_phids = $request->getArr('owner'); 152 - 153 - $callsigns = array('' => pht('(Any Repository)')); 154 - $repositories = id(new PhabricatorRepositoryQuery()) 155 - ->setViewer($user) 156 - ->setOrder('callsign') 157 - ->execute(); 158 - foreach ($repositories as $repository) { 159 - $callsigns[$repository->getCallsign()] = 160 - $repository->getCallsign().': '.$repository->getName(); 161 - } 162 - 163 - $form = id(new AphrontFormView()) 164 - ->setUser($user) 165 - ->setAction('/owners/view/search/') 166 - ->setMethod('GET') 167 - ->appendChild( 168 - id(new AphrontFormTextControl()) 169 - ->setName('name') 170 - ->setLabel(pht('Name')) 171 - ->setValue($request->getStr('name'))) 172 - ->appendControl( 173 - id(new AphrontFormTokenizerControl()) 174 - ->setDatasource(new PhabricatorProjectOrUserDatasource()) 175 - ->setLimit(1) 176 - ->setName('owner') 177 - ->setLabel(pht('Owner')) 178 - ->setValue($owner_phids)) 179 - ->appendChild( 180 - id(new AphrontFormSelectControl()) 181 - ->setName('repository') 182 - ->setLabel(pht('Repository')) 183 - ->setOptions($callsigns) 184 - ->setValue($request->getStr('repository'))) 185 - ->appendChild( 186 - id(new AphrontFormTextControl()) 187 - ->setName('path') 188 - ->setLabel(pht('Path')) 189 - ->setValue($request->getStr('path'))) 190 - ->appendChild( 191 - id(new AphrontFormSubmitControl()) 192 - ->setValue(pht('Search for Packages'))); 193 - 194 - $filter->appendChild($form); 195 - $title = pht('Package Index'); 196 - 197 - $crumbs = $this->buildApplicationCrumbs(); 198 - $crumbs->addTextCrumb($header); 199 - 200 - $nav = $this->buildSideNavView(); 201 - $nav->appendChild($crumbs); 202 - $nav->appendChild($filter); 203 - $nav->appendChild($content); 10 + public function handleRequest(AphrontRequest $request) { 11 + $controller = id(new PhabricatorApplicationSearchController()) 12 + ->setQueryKey($request->getURIData('queryKey')) 13 + ->setSearchEngine(new PhabricatorOwnersPackageSearchEngine()) 14 + ->setNavigation($this->buildSideNavView()); 204 15 205 - return $this->buildApplicationPage( 206 - $nav, 207 - array( 208 - 'title' => pht('Package Index'), 209 - )); 16 + return $this->delegateToController($controller); 210 17 } 211 18 212 - private function renderPackageTable(array $packages, $header, $nodata) { 213 - assert_instances_of($packages, 'PhabricatorOwnersPackage'); 214 - 215 - if ($packages) { 216 - $package_ids = mpull($packages, 'getID'); 217 - 218 - $owners = id(new PhabricatorOwnersOwner())->loadAllWhere( 219 - 'packageID IN (%Ld)', 220 - $package_ids); 221 - 222 - $paths = id(new PhabricatorOwnersPath())->loadAllWhere( 223 - 'packageID in (%Ld)', 224 - $package_ids); 225 - 226 - $phids = array(); 227 - foreach ($owners as $owner) { 228 - $phids[$owner->getUserPHID()] = true; 229 - } 230 - $phids = array_keys($phids); 231 - $handles = $this->loadViewerHandles($phids); 232 - 233 - $repository_phids = array(); 234 - foreach ($paths as $path) { 235 - $repository_phids[$path->getRepositoryPHID()] = true; 236 - } 19 + public function buildSideNavView($for_app = false) { 20 + $viewer = $this->getViewer(); 237 21 238 - if ($repository_phids) { 239 - $repositories = id(new PhabricatorRepositoryQuery()) 240 - ->setViewer($this->getRequest()->getUser()) 241 - ->withPHIDs(array_keys($repository_phids)) 242 - ->execute(); 243 - } else { 244 - $repositories = array(); 245 - } 22 + $nav = new AphrontSideNavFilterView(); 23 + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 246 24 247 - $repositories = mpull($repositories, null, 'getPHID'); 248 - $owners = mgroup($owners, 'getPackageID'); 249 - $paths = mgroup($paths, 'getPackageID'); 250 - } else { 251 - $handles = array(); 252 - $repositories = array(); 253 - $owners = array(); 254 - $paths = array(); 25 + if ($for_app) { 26 + $nav->addFilter('new/', pht('Create Package')); 255 27 } 256 28 257 - $rows = array(); 258 - foreach ($packages as $package) { 29 + id(new PhabricatorOwnersPackageSearchEngine()) 30 + ->setViewer($viewer) 31 + ->addNavigationItems($nav->getMenu()); 259 32 260 - $pkg_owners = idx($owners, $package->getID(), array()); 261 - foreach ($pkg_owners as $key => $owner) { 262 - $pkg_owners[$key] = $handles[$owner->getUserPHID()]->renderLink(); 263 - if ($owner->getUserPHID() == $package->getPrimaryOwnerPHID()) { 264 - $pkg_owners[$key] = phutil_tag('strong', array(), $pkg_owners[$key]); 265 - } 266 - } 267 - $pkg_owners = phutil_implode_html(phutil_tag('br'), $pkg_owners); 33 + $nav->selectFilter(null); 268 34 269 - $pkg_paths = idx($paths, $package->getID(), array()); 270 - foreach ($pkg_paths as $key => $path) { 271 - $repo = idx($repositories, $path->getRepositoryPHID()); 272 - if ($repo) { 273 - $href = DiffusionRequest::generateDiffusionURI( 274 - array( 275 - 'callsign' => $repo->getCallsign(), 276 - 'branch' => $repo->getDefaultBranch(), 277 - 'path' => $path->getPath(), 278 - 'action' => 'browse', 279 - )); 280 - $pkg_paths[$key] = hsprintf( 281 - '%s %s%s', 282 - ($path->getExcluded() ? "\xE2\x80\x93" : '+'), 283 - phutil_tag('strong', array(), $repo->getName()), 284 - phutil_tag( 285 - 'a', 286 - array( 287 - 'href' => (string)$href, 288 - ), 289 - $path->getPath())); 290 - } else { 291 - $pkg_paths[$key] = $path->getPath(); 292 - } 293 - } 294 - $pkg_paths = phutil_implode_html(phutil_tag('br'), $pkg_paths); 35 + return $nav; 36 + } 295 37 296 - $rows[] = array( 297 - phutil_tag( 298 - 'a', 299 - array( 300 - 'href' => '/owners/package/'.$package->getID().'/', 301 - ), 302 - $package->getName()), 303 - $pkg_owners, 304 - $pkg_paths, 305 - phutil_tag( 306 - 'a', 307 - array( 308 - 'href' => '/audit/?auditorPHIDs='.$package->getPHID(), 309 - ), 310 - pht('Related Commits')), 311 - ); 312 - } 38 + public function buildApplicationMenu() { 39 + return $this->buildSideNavView(true)->getMenu(); 40 + } 313 41 314 - $table = new AphrontTableView($rows); 315 - $table->setHeaders( 316 - array( 317 - pht('Name'), 318 - pht('Owners'), 319 - pht('Paths'), 320 - pht('Related Commits'), 321 - )); 322 - $table->setColumnClasses( 323 - array( 324 - 'pri', 325 - '', 326 - 'wide wrap', 327 - 'narrow', 328 - )); 42 + protected function buildApplicationCrumbs() { 43 + $crumbs = parent::buildApplicationCrumbs(); 329 44 330 - $panel = new PHUIObjectBoxView(); 331 - $panel->setHeaderText($header); 332 - $panel->setTable($table); 45 + $crumbs->addAction( 46 + id(new PHUIListItemView()) 47 + ->setName(pht('Create Package')) 48 + ->setHref($this->getApplicationURI('new/')) 49 + ->setIcon('fa-plus-square')); 333 50 334 - return $panel; 51 + return $crumbs; 335 52 } 336 53 337 - protected function getExtraPackageViews(AphrontSideNavFilterView $view) { 338 - if ($this->view == 'search') { 339 - $view->addFilter('view/search', pht('Search Results')); 340 - } 341 - } 342 54 }
+165
src/applications/owners/controller/PhabricatorOwnersPathsController.php
··· 1 + <?php 2 + 3 + final class PhabricatorOwnersPathsController 4 + extends PhabricatorOwnersController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getUser(); 8 + 9 + $package = id(new PhabricatorOwnersPackageQuery()) 10 + ->setViewer($viewer) 11 + ->withIDs(array($request->getURIData('id'))) 12 + ->requireCapabilities( 13 + array( 14 + PhabricatorPolicyCapability::CAN_VIEW, 15 + // TODO: Support this capability. 16 + // PhabricatorPolicyCapability::CAN_EDIT, 17 + )) 18 + ->executeOne(); 19 + if (!$package) { 20 + return new Aphront404Response(); 21 + } 22 + 23 + if ($request->isFormPost()) { 24 + $paths = $request->getArr('path'); 25 + $repos = $request->getArr('repo'); 26 + $excludes = $request->getArr('exclude'); 27 + 28 + $path_refs = array(); 29 + foreach ($paths as $key => $path) { 30 + if (!isset($repos[$key])) { 31 + throw new Exception( 32 + pht( 33 + 'No repository PHID for path "%s"!', 34 + $key)); 35 + } 36 + 37 + if (!isset($excludes[$key])) { 38 + throw new Exception( 39 + pht( 40 + 'No exclusion value for path "%s"!', 41 + $key)); 42 + } 43 + 44 + $path_refs[] = array( 45 + 'repositoryPHID' => $repos[$key], 46 + 'path' => $path, 47 + 'excluded' => (int)$excludes[$key], 48 + ); 49 + } 50 + 51 + $type_paths = PhabricatorOwnersPackageTransaction::TYPE_PATHS; 52 + 53 + $xactions = array(); 54 + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) 55 + ->setTransactionType($type_paths) 56 + ->setNewValue($path_refs); 57 + 58 + $editor = id(new PhabricatorOwnersPackageTransactionEditor()) 59 + ->setActor($viewer) 60 + ->setContentSourceFromRequest($request) 61 + ->setContinueOnNoEffect(true) 62 + ->setContinueOnMissingFields(true); 63 + 64 + $editor->applyTransactions($package, $xactions); 65 + 66 + return id(new AphrontRedirectResponse()) 67 + ->setURI('/owners/package/'.$package->getID().'/'); 68 + } else { 69 + $paths = $package->loadPaths(); 70 + $path_refs = mpull($paths, 'getRef'); 71 + } 72 + 73 + $repos = id(new PhabricatorRepositoryQuery()) 74 + ->setViewer($viewer) 75 + ->execute(); 76 + 77 + $default_paths = array(); 78 + foreach ($repos as $repo) { 79 + $default_path = $repo->getDetail('default-owners-path'); 80 + if ($default_path) { 81 + $default_paths[$repo->getPHID()] = $default_path; 82 + } 83 + } 84 + 85 + $repos = mpull($repos, 'getCallsign', 'getPHID'); 86 + asort($repos); 87 + 88 + $template = new AphrontTypeaheadTemplateView(); 89 + $template = $template->render(); 90 + 91 + Javelin::initBehavior( 92 + 'owners-path-editor', 93 + array( 94 + 'root' => 'path-editor', 95 + 'table' => 'paths', 96 + 'add_button' => 'addpath', 97 + 'repositories' => $repos, 98 + 'input_template' => $template, 99 + 'pathRefs' => $path_refs, 100 + 101 + 'completeURI' => '/diffusion/services/path/complete/', 102 + 'validateURI' => '/diffusion/services/path/validate/', 103 + 104 + 'repositoryDefaultPaths' => $default_paths, 105 + )); 106 + 107 + require_celerity_resource('owners-path-editor-css'); 108 + 109 + $cancel_uri = '/owners/package/'.$package->getID().'/'; 110 + 111 + $form = id(new AphrontFormView()) 112 + ->setUser($viewer) 113 + ->appendChild( 114 + id(new PHUIFormInsetView()) 115 + ->setTitle(pht('Paths')) 116 + ->addDivAttributes(array('id' => 'path-editor')) 117 + ->setRightButton(javelin_tag( 118 + 'a', 119 + array( 120 + 'href' => '#', 121 + 'class' => 'button green', 122 + 'sigil' => 'addpath', 123 + 'mustcapture' => true, 124 + ), 125 + pht('Add New Path'))) 126 + ->setDescription( 127 + pht( 128 + 'Specify the files and directories which comprise '. 129 + 'this package.')) 130 + ->setContent(javelin_tag( 131 + 'table', 132 + array( 133 + 'class' => 'owners-path-editor-table', 134 + 'sigil' => 'paths', 135 + ), 136 + ''))) 137 + ->appendChild( 138 + id(new AphrontFormSubmitControl()) 139 + ->addCancelButton($cancel_uri) 140 + ->setValue(pht('Save Paths'))); 141 + 142 + $form_box = id(new PHUIObjectBoxView()) 143 + ->setHeaderText(pht('Edit Paths')) 144 + ->setForm($form); 145 + 146 + $crumbs = $this->buildApplicationCrumbs(); 147 + $crumbs->addTextCrumb( 148 + $package->getName(), 149 + $this->getApplicationURI('package/'.$package->getID().'/')); 150 + $crumbs->addTextCrumb(pht('Edit Paths')); 151 + 152 + return $this->buildApplicationPage( 153 + array( 154 + $crumbs, 155 + $form_box, 156 + ), 157 + array( 158 + 'title' => array( 159 + $package->getName(), 160 + pht('Edit Paths'), 161 + ), 162 + )); 163 + } 164 + 165 + }
-198
src/applications/owners/editor/PhabricatorOwnersPackageEditor.php
··· 1 - <?php 2 - 3 - final class PhabricatorOwnersPackageEditor extends PhabricatorEditor { 4 - 5 - private $package; 6 - 7 - public function setPackage(PhabricatorOwnersPackage $package) { 8 - $this->package = $package; 9 - return $this; 10 - } 11 - 12 - public function getPackage() { 13 - return $this->package; 14 - } 15 - 16 - public function save() { 17 - $actor = $this->getActor(); 18 - $package = $this->getPackage(); 19 - $package->attachActorPHID($actor->getPHID()); 20 - 21 - if ($package->getID()) { 22 - $is_new = false; 23 - } else { 24 - $is_new = true; 25 - } 26 - 27 - $package->openTransaction(); 28 - 29 - $ret = $package->save(); 30 - 31 - $add_owners = array(); 32 - $remove_owners = array(); 33 - $all_owners = array(); 34 - if ($package->getUnsavedOwners()) { 35 - $new_owners = array_fill_keys($package->getUnsavedOwners(), true); 36 - $cur_owners = array(); 37 - foreach ($package->loadOwners() as $owner) { 38 - if (empty($new_owners[$owner->getUserPHID()])) { 39 - $remove_owners[$owner->getUserPHID()] = true; 40 - $owner->delete(); 41 - continue; 42 - } 43 - $cur_owners[$owner->getUserPHID()] = true; 44 - } 45 - 46 - $add_owners = array_diff_key($new_owners, $cur_owners); 47 - $all_owners = array_merge( 48 - array($package->getPrimaryOwnerPHID() => true), 49 - $new_owners, 50 - $remove_owners); 51 - foreach ($add_owners as $phid => $ignored) { 52 - $owner = new PhabricatorOwnersOwner(); 53 - $owner->setPackageID($package->getID()); 54 - $owner->setUserPHID($phid); 55 - $owner->save(); 56 - } 57 - $package->attachUnsavedOwners(array()); 58 - } 59 - 60 - $add_paths = array(); 61 - $remove_paths = array(); 62 - $touched_repos = array(); 63 - if ($package->getUnsavedPaths()) { 64 - $new_paths = igroup( 65 - $package->getUnsavedPaths(), 66 - 'repositoryPHID', 67 - 'path'); 68 - $cur_paths = $package->loadPaths(); 69 - foreach ($cur_paths as $key => $path) { 70 - $repository_phid = $path->getRepositoryPHID(); 71 - $new_path = head(idx( 72 - idx($new_paths, $repository_phid, array()), 73 - $path->getPath(), 74 - array())); 75 - $excluded = $path->getExcluded(); 76 - if ($new_path === false || 77 - idx($new_path, 'excluded') != $excluded) { 78 - $touched_repos[$repository_phid] = true; 79 - $remove_paths[$repository_phid][$path->getPath()] = $excluded; 80 - $path->delete(); 81 - unset($cur_paths[$key]); 82 - } 83 - } 84 - 85 - $cur_paths = mgroup($cur_paths, 'getRepositoryPHID', 'getPath'); 86 - $repositories = id(new PhabricatorRepositoryQuery()) 87 - ->setViewer($actor) 88 - ->withPHIDs(array_keys($cur_paths)) 89 - ->execute(); 90 - $repositories = mpull($repositories, null, 'getPHID'); 91 - foreach ($new_paths as $repository_phid => $paths) { 92 - $repository = idx($repositories, $repository_phid); 93 - if (!$repository) { 94 - continue; 95 - } 96 - foreach ($paths as $path => $dicts) { 97 - $path = ltrim($path, '/'); 98 - // build query to validate path 99 - $drequest = DiffusionRequest::newFromDictionary( 100 - array( 101 - 'user' => $actor, 102 - 'repository' => $repository, 103 - 'path' => $path, 104 - )); 105 - $results = DiffusionBrowseResultSet::newFromConduit( 106 - DiffusionQuery::callConduitWithDiffusionRequest( 107 - $actor, 108 - $drequest, 109 - 'diffusion.browsequery', 110 - array( 111 - 'commit' => $drequest->getCommit(), 112 - 'path' => $path, 113 - 'needValidityOnly' => true, 114 - ))); 115 - $valid = $results->isValidResults(); 116 - $is_directory = true; 117 - if (!$valid) { 118 - switch ($results->getReasonForEmptyResultSet()) { 119 - case DiffusionBrowseResultSet::REASON_IS_FILE: 120 - $valid = true; 121 - $is_directory = false; 122 - break; 123 - case DiffusionBrowseResultSet::REASON_IS_EMPTY: 124 - $valid = true; 125 - break; 126 - } 127 - } 128 - if ($is_directory && substr($path, -1) != '/') { 129 - $path .= '/'; 130 - } 131 - if (substr($path, 0, 1) != '/') { 132 - $path = '/'.$path; 133 - } 134 - if (empty($cur_paths[$repository_phid][$path]) && $valid) { 135 - $touched_repos[$repository_phid] = true; 136 - $excluded = idx(reset($dicts), 'excluded', 0); 137 - $add_paths[$repository_phid][$path] = $excluded; 138 - $obj = new PhabricatorOwnersPath(); 139 - $obj->setPackageID($package->getID()); 140 - $obj->setRepositoryPHID($repository_phid); 141 - $obj->setPath($path); 142 - $obj->setExcluded($excluded); 143 - $obj->save(); 144 - } 145 - } 146 - } 147 - $package->attachUnsavedPaths(array()); 148 - } 149 - 150 - $package->saveTransaction(); 151 - 152 - if ($is_new) { 153 - $mail = new PackageCreateMail($package); 154 - } else { 155 - $mail = new PackageModifyMail( 156 - $package, 157 - array_keys($add_owners), 158 - array_keys($remove_owners), 159 - array_keys($all_owners), 160 - array_keys($touched_repos), 161 - $add_paths, 162 - $remove_paths); 163 - } 164 - $mail->setActor($actor); 165 - $mail->send(); 166 - 167 - return $ret; 168 - } 169 - 170 - public function delete() { 171 - $actor = $this->getActor(); 172 - $package = $this->getPackage(); 173 - $package->attachActorPHID($actor->getPHID()); 174 - 175 - $mails = id(new PackageDeleteMail($package)) 176 - ->setActor($actor) 177 - ->prepareMails(); 178 - 179 - $package->openTransaction(); 180 - 181 - foreach ($package->loadOwners() as $owner) { 182 - $owner->delete(); 183 - } 184 - foreach ($package->loadPaths() as $path) { 185 - $path->delete(); 186 - } 187 - $ret = $package->delete(); 188 - 189 - $package->saveTransaction(); 190 - 191 - foreach ($mails as $mail) { 192 - $mail->saveAndSend(); 193 - } 194 - 195 - return $ret; 196 - } 197 - 198 - }
+290
src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php
··· 1 + <?php 2 + 3 + final class PhabricatorOwnersPackageTransactionEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorOwnersApplication'; 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Owners Packages'); 12 + } 13 + 14 + public function getTransactionTypes() { 15 + $types = parent::getTransactionTypes(); 16 + 17 + $types[] = PhabricatorOwnersPackageTransaction::TYPE_NAME; 18 + $types[] = PhabricatorOwnersPackageTransaction::TYPE_PRIMARY; 19 + $types[] = PhabricatorOwnersPackageTransaction::TYPE_OWNERS; 20 + $types[] = PhabricatorOwnersPackageTransaction::TYPE_AUDITING; 21 + $types[] = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION; 22 + $types[] = PhabricatorOwnersPackageTransaction::TYPE_PATHS; 23 + 24 + return $types; 25 + } 26 + 27 + protected function getCustomTransactionOldValue( 28 + PhabricatorLiskDAO $object, 29 + PhabricatorApplicationTransaction $xaction) { 30 + 31 + switch ($xaction->getTransactionType()) { 32 + case PhabricatorOwnersPackageTransaction::TYPE_NAME: 33 + return $object->getName(); 34 + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: 35 + return $object->getPrimaryOwnerPHID(); 36 + case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: 37 + // TODO: needOwners() this on the Query. 38 + $phids = mpull($object->loadOwners(), 'getUserPHID'); 39 + $phids = array_values($phids); 40 + return $phids; 41 + case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: 42 + return (int)$object->getAuditingEnabled(); 43 + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: 44 + return $object->getDescription(); 45 + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: 46 + // TODO: needPaths() this on the query 47 + $paths = $object->loadPaths(); 48 + return mpull($paths, 'getRef'); 49 + } 50 + } 51 + 52 + protected function getCustomTransactionNewValue( 53 + PhabricatorLiskDAO $object, 54 + PhabricatorApplicationTransaction $xaction) { 55 + 56 + switch ($xaction->getTransactionType()) { 57 + case PhabricatorOwnersPackageTransaction::TYPE_NAME: 58 + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: 59 + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: 60 + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: 61 + return $xaction->getNewValue(); 62 + case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: 63 + return (int)$xaction->getNewValue(); 64 + case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: 65 + $phids = $xaction->getNewValue(); 66 + $phids = array_unique($phids); 67 + $phids = array_values($phids); 68 + return $phids; 69 + } 70 + } 71 + 72 + protected function transactionHasEffect( 73 + PhabricatorLiskDAO $object, 74 + PhabricatorApplicationTransaction $xaction) { 75 + 76 + switch ($xaction->getTransactionType()) { 77 + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: 78 + $old = $xaction->getOldValue(); 79 + $new = $xaction->getNewValue(); 80 + 81 + $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); 82 + list($rem, $add) = $diffs; 83 + 84 + return ($rem || $add); 85 + } 86 + 87 + return parent::transactionHasEffect($object, $xaction); 88 + } 89 + 90 + protected function applyCustomInternalTransaction( 91 + PhabricatorLiskDAO $object, 92 + PhabricatorApplicationTransaction $xaction) { 93 + 94 + switch ($xaction->getTransactionType()) { 95 + case PhabricatorOwnersPackageTransaction::TYPE_NAME: 96 + $object->setName($xaction->getNewValue()); 97 + return; 98 + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: 99 + $object->setPrimaryOwnerPHID($xaction->getNewValue()); 100 + return; 101 + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: 102 + $object->setDescription($xaction->getNewValue()); 103 + return; 104 + case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: 105 + $object->setAuditingEnabled($xaction->getNewValue()); 106 + return; 107 + case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: 108 + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: 109 + return; 110 + } 111 + 112 + return parent::applyCustomInternalTransaction($object, $xaction); 113 + } 114 + 115 + protected function applyCustomExternalTransaction( 116 + PhabricatorLiskDAO $object, 117 + PhabricatorApplicationTransaction $xaction) { 118 + 119 + switch ($xaction->getTransactionType()) { 120 + case PhabricatorOwnersPackageTransaction::TYPE_NAME: 121 + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: 122 + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: 123 + case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: 124 + return; 125 + case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: 126 + $old = $xaction->getOldValue(); 127 + $new = $xaction->getNewValue(); 128 + 129 + // TODO: needOwners this 130 + $owners = $object->loadOwners(); 131 + $owners = mpull($owners, null, 'getUserPHID'); 132 + 133 + $rem = array_diff($old, $new); 134 + foreach ($rem as $phid) { 135 + if (isset($owners[$phid])) { 136 + $owners[$phid]->delete(); 137 + unset($owners[$phid]); 138 + } 139 + } 140 + 141 + $add = array_diff($new, $old); 142 + foreach ($add as $phid) { 143 + $owners[$phid] = id(new PhabricatorOwnersOwner()) 144 + ->setPackageID($object->getID()) 145 + ->setUserPHID($phid) 146 + ->save(); 147 + } 148 + 149 + // TODO: Attach owners here 150 + return; 151 + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: 152 + $old = $xaction->getOldValue(); 153 + $new = $xaction->getNewValue(); 154 + 155 + // TODO: needPaths this 156 + $paths = $object->loadPaths(); 157 + 158 + $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); 159 + list($rem, $add) = $diffs; 160 + 161 + $set = PhabricatorOwnersPath::getSetFromTransactionValue($rem); 162 + foreach ($paths as $path) { 163 + $ref = $path->getRef(); 164 + if (PhabricatorOwnersPath::isRefInSet($ref, $set)) { 165 + $path->delete(); 166 + } 167 + } 168 + 169 + foreach ($add as $ref) { 170 + $path = PhabricatorOwnersPath::newFromRef($ref) 171 + ->setPackageID($object->getID()) 172 + ->save(); 173 + } 174 + 175 + return; 176 + } 177 + 178 + return parent::applyCustomExternalTransaction($object, $xaction); 179 + } 180 + 181 + protected function validateTransaction( 182 + PhabricatorLiskDAO $object, 183 + $type, 184 + array $xactions) { 185 + 186 + $errors = parent::validateTransaction($object, $type, $xactions); 187 + 188 + switch ($type) { 189 + case PhabricatorOwnersPackageTransaction::TYPE_NAME: 190 + $missing = $this->validateIsEmptyTextField( 191 + $object->getName(), 192 + $xactions); 193 + 194 + if ($missing) { 195 + $error = new PhabricatorApplicationTransactionValidationError( 196 + $type, 197 + pht('Required'), 198 + pht('Package name is required.'), 199 + nonempty(last($xactions), null)); 200 + 201 + $error->setIsMissingFieldError(true); 202 + $errors[] = $error; 203 + } 204 + break; 205 + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: 206 + $missing = $this->validateIsEmptyTextField( 207 + $object->getPrimaryOwnerPHID(), 208 + $xactions); 209 + 210 + if ($missing) { 211 + $error = new PhabricatorApplicationTransactionValidationError( 212 + $type, 213 + pht('Required'), 214 + pht('Packages must have a primary owner.'), 215 + nonempty(last($xactions), null)); 216 + 217 + $error->setIsMissingFieldError(true); 218 + $errors[] = $error; 219 + } 220 + break; 221 + } 222 + 223 + return $errors; 224 + } 225 + 226 + protected function extractFilePHIDsFromCustomTransaction( 227 + PhabricatorLiskDAO $object, 228 + PhabricatorApplicationTransaction $xaction) { 229 + 230 + switch ($xaction->getTransactionType()) { 231 + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: 232 + return array($xaction->getNewValue()); 233 + } 234 + 235 + return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); 236 + } 237 + 238 + protected function shouldSendMail( 239 + PhabricatorLiskDAO $object, 240 + array $xactions) { 241 + return true; 242 + } 243 + 244 + protected function getMailSubjectPrefix() { 245 + return PhabricatorEnv::getEnvConfig('metamta.package.subject-prefix'); 246 + } 247 + 248 + protected function getMailTo(PhabricatorLiskDAO $object) { 249 + return array( 250 + $object->getPrimaryOwnerPHID(), 251 + $this->requireActor()->getPHID(), 252 + ); 253 + } 254 + 255 + protected function getMailCC(PhabricatorLiskDAO $object) { 256 + // TODO: needOwners() this 257 + return mpull($object->loadOwners(), 'getUserPHID'); 258 + } 259 + 260 + protected function buildReplyHandler(PhabricatorLiskDAO $object) { 261 + return id(new OwnersPackageReplyHandler()) 262 + ->setMailReceiver($object); 263 + } 264 + 265 + protected function buildMailTemplate(PhabricatorLiskDAO $object) { 266 + $id = $object->getID(); 267 + $name = $object->getName(); 268 + 269 + return id(new PhabricatorMetaMTAMail()) 270 + ->setSubject($name) 271 + ->addHeader('Thread-Topic', $object->getPHID()); 272 + } 273 + 274 + protected function buildMailBody( 275 + PhabricatorLiskDAO $object, 276 + array $xactions) { 277 + 278 + $body = parent::buildMailBody($object, $xactions); 279 + 280 + $detail_uri = PhabricatorEnv::getProductionURI( 281 + '/owners/package/'.$object->getID().'/'); 282 + 283 + $body->addLinkSection( 284 + pht('PACKAGE DETAIL'), 285 + $detail_uri); 286 + 287 + return $body; 288 + } 289 + 290 + }
-12
src/applications/owners/mail/PackageCreateMail.php
··· 1 - <?php 2 - 3 - final class PackageCreateMail extends PackageMail { 4 - 5 - protected function isNewThread() { 6 - return true; 7 - } 8 - 9 - protected function getVerb() { 10 - return pht('Created'); 11 - } 12 - }
-13
src/applications/owners/mail/PackageDeleteMail.php
··· 1 - <?php 2 - 3 - final class PackageDeleteMail extends PackageMail { 4 - 5 - protected function getVerb() { 6 - return pht('Deleted'); 7 - } 8 - 9 - protected function isNewThread() { 10 - return false; 11 - } 12 - 13 - }
-212
src/applications/owners/mail/PackageMail.php
··· 1 - <?php 2 - 3 - abstract class PackageMail extends PhabricatorMail { 4 - 5 - protected $package; 6 - protected $handles; 7 - protected $owners; 8 - protected $paths; 9 - protected $mailTo; 10 - 11 - public function __construct(PhabricatorOwnersPackage $package) { 12 - $this->package = $package; 13 - } 14 - 15 - abstract protected function getVerb(); 16 - 17 - abstract protected function isNewThread(); 18 - 19 - final protected function getPackage() { 20 - return $this->package; 21 - } 22 - 23 - final protected function getHandles() { 24 - return $this->handles; 25 - } 26 - 27 - final protected function getOwners() { 28 - return $this->owners; 29 - } 30 - 31 - final protected function getPaths() { 32 - return $this->paths; 33 - } 34 - 35 - final protected function getMailTo() { 36 - return $this->mailTo; 37 - } 38 - 39 - final protected function renderPackageTitle() { 40 - return $this->getPackage()->getName(); 41 - } 42 - 43 - final protected function renderRepoSubSection($repository_phid, $paths) { 44 - $handles = $this->getHandles(); 45 - $section = array(); 46 - $section[] = ' '. 47 - pht('In repository %s', $handles[$repository_phid]->getName()). 48 - ' - '.PhabricatorEnv::getProductionURI($handles[$repository_phid] 49 - ->getURI()); 50 - foreach ($paths as $path => $excluded) { 51 - $section[] = ' '. 52 - ($excluded ? pht('Excluded') : pht('Included')).' '.$path; 53 - } 54 - 55 - return implode("\n", $section); 56 - } 57 - 58 - protected function needSend() { 59 - return true; 60 - } 61 - 62 - protected function loadData() { 63 - $package = $this->getPackage(); 64 - $owners = $package->loadOwners(); 65 - $this->owners = $owners; 66 - 67 - $owner_phids = mpull($owners, 'getUserPHID'); 68 - $primary_owner_phid = $package->getPrimaryOwnerPHID(); 69 - $mail_to = $owner_phids; 70 - if (!in_array($primary_owner_phid, $owner_phids)) { 71 - $mail_to[] = $primary_owner_phid; 72 - } 73 - $this->mailTo = $mail_to; 74 - 75 - $this->paths = array(); 76 - $repository_paths = mgroup($package->loadPaths(), 'getRepositoryPHID'); 77 - foreach ($repository_paths as $repository_phid => $paths) { 78 - $this->paths[$repository_phid] = mpull($paths, 'getExcluded', 'getPath'); 79 - } 80 - 81 - $phids = array_merge( 82 - $this->mailTo, 83 - array($package->getActorPHID()), 84 - array_keys($this->paths)); 85 - $this->handles = id(new PhabricatorHandleQuery()) 86 - ->setViewer($this->getActor()) 87 - ->withPHIDs($phids) 88 - ->execute(); 89 - } 90 - 91 - final protected function renderSummarySection() { 92 - $package = $this->getPackage(); 93 - $handles = $this->getHandles(); 94 - $section = array(); 95 - $section[] = $handles[$package->getActorPHID()]->getName().' '. 96 - strtolower($this->getVerb()).' '.$this->renderPackageTitle().'.'; 97 - $section[] = ''; 98 - 99 - $section[] = pht('PACKAGE DETAIL'); 100 - $section[] = ' '.PhabricatorEnv::getProductionURI( 101 - '/owners/package/'.$package->getID().'/'); 102 - 103 - return implode("\n", $section); 104 - } 105 - 106 - protected function renderDescriptionSection() { 107 - return pht('PACKAGE DESCRIPTION')."\n ". 108 - $this->getPackage()->getDescription(); 109 - } 110 - 111 - protected function renderPrimaryOwnerSection() { 112 - $handles = $this->getHandles(); 113 - return pht('PRIMARY OWNER')."\n ". 114 - $handles[$this->getPackage()->getPrimaryOwnerPHID()]->getName(); 115 - } 116 - 117 - protected function renderOwnersSection() { 118 - $handles = $this->getHandles(); 119 - $owners = $this->getOwners(); 120 - if (!$owners) { 121 - return null; 122 - } 123 - 124 - $owners = mpull($owners, 'getUserPHID'); 125 - $owners = array_select_keys($handles, $owners); 126 - $owners = mpull($owners, 'getName'); 127 - return pht('OWNERS')."\n ".implode(', ', $owners); 128 - } 129 - 130 - protected function renderAuditingEnabledSection() { 131 - return pht('AUDITING ENABLED STATUS')."\n ". 132 - ($this->getPackage()->getAuditingEnabled() 133 - ? pht('Enabled') 134 - : pht('Disabled')); 135 - } 136 - 137 - protected function renderPathsSection() { 138 - $section = array(); 139 - $section[] = pht('PATHS'); 140 - foreach ($this->paths as $repository_phid => $paths) { 141 - $section[] = $this->renderRepoSubSection($repository_phid, $paths); 142 - } 143 - 144 - return implode("\n", $section); 145 - } 146 - 147 - final protected function renderBody() { 148 - $body = array(); 149 - $body[] = $this->renderSummarySection(); 150 - $body[] = $this->renderDescriptionSection(); 151 - $body[] = $this->renderPrimaryOwnerSection(); 152 - $body[] = $this->renderOwnersSection(); 153 - $body[] = $this->renderAuditingEnabledSection(); 154 - $body[] = $this->renderPathsSection(); 155 - $body = array_filter($body); 156 - return implode("\n\n", $body)."\n"; 157 - } 158 - 159 - final public function send() { 160 - $mails = $this->prepareMails(); 161 - 162 - foreach ($mails as $mail) { 163 - $mail->saveAndSend(); 164 - } 165 - } 166 - 167 - final public function prepareMails() { 168 - if (!$this->needSend()) { 169 - return array(); 170 - } 171 - 172 - $this->loadData(); 173 - 174 - $package = $this->getPackage(); 175 - $prefix = PhabricatorEnv::getEnvConfig('metamta.package.subject-prefix'); 176 - $verb = $this->getVerb(); 177 - $threading = $this->getMailThreading(); 178 - list($thread_id, $thread_topic) = $threading; 179 - 180 - $template = id(new PhabricatorMetaMTAMail()) 181 - ->setSubject($this->renderPackageTitle()) 182 - ->setSubjectPrefix($prefix) 183 - ->setVarySubjectPrefix("[{$verb}]") 184 - ->setFrom($package->getActorPHID()) 185 - ->setThreadID($thread_id, $this->isNewThread()) 186 - ->addHeader('Thread-Topic', $thread_topic) 187 - ->setRelatedPHID($package->getPHID()) 188 - ->setIsBulk(true) 189 - ->setBody($this->renderBody()); 190 - 191 - $reply_handler = $this->newReplyHandler(); 192 - $mails = $reply_handler->multiplexMail( 193 - $template, 194 - array_select_keys($this->getHandles(), $this->getMailTo()), 195 - array()); 196 - return $mails; 197 - } 198 - 199 - private function getMailThreading() { 200 - return array( 201 - 'package-'.$this->getPackage()->getPHID(), 202 - 'Package '.$this->getPackage()->getOriginalName(), 203 - ); 204 - } 205 - 206 - private function newReplyHandler() { 207 - $reply_handler = new OwnersPackageReplyHandler(); 208 - $reply_handler->setMailReceiver($this->getPackage()); 209 - return $reply_handler; 210 - } 211 - 212 - }
-160
src/applications/owners/mail/PackageModifyMail.php
··· 1 - <?php 2 - 3 - final class PackageModifyMail extends PackageMail { 4 - 5 - protected $addOwners; 6 - protected $removeOwners; 7 - protected $allOwners; 8 - protected $touchedRepos; 9 - protected $addPaths; 10 - protected $removePaths; 11 - 12 - public function __construct( 13 - PhabricatorOwnersPackage $package, 14 - $add_owners, 15 - $remove_owners, 16 - $all_owners, 17 - $touched_repos, 18 - $add_paths, 19 - $remove_paths) { 20 - 21 - $this->package = $package; 22 - 23 - $this->addOwners = $add_owners; 24 - $this->removeOwners = $remove_owners; 25 - $this->allOwners = $all_owners; 26 - $this->touchedRepos = $touched_repos; 27 - $this->addPaths = $add_paths; 28 - $this->removePaths = $remove_paths; 29 - } 30 - 31 - protected function getVerb() { 32 - return pht('Modified'); 33 - } 34 - 35 - protected function isNewThread() { 36 - return false; 37 - } 38 - 39 - protected function needSend() { 40 - $package = $this->getPackage(); 41 - if ($package->getOldPrimaryOwnerPHID() !== $package->getPrimaryOwnerPHID() 42 - || $package->getOldAuditingEnabled() != $package->getAuditingEnabled() 43 - || $this->addOwners 44 - || $this->removeOwners 45 - || $this->addPaths 46 - || $this->removePaths) { 47 - return true; 48 - } else { 49 - return false; 50 - } 51 - } 52 - 53 - protected function loadData() { 54 - $this->mailTo = $this->allOwners; 55 - 56 - $phids = array_merge( 57 - $this->allOwners, 58 - $this->touchedRepos, 59 - array( 60 - $this->getPackage()->getActorPHID(), 61 - )); 62 - $this->handles = id(new PhabricatorHandleQuery()) 63 - ->setViewer($this->getActor()) 64 - ->withPHIDs($phids) 65 - ->execute(); 66 - } 67 - 68 - protected function renderDescriptionSection() { 69 - return null; 70 - } 71 - 72 - protected function renderPrimaryOwnerSection() { 73 - $package = $this->getPackage(); 74 - $handles = $this->getHandles(); 75 - 76 - $old_primary_owner_phid = $package->getOldPrimaryOwnerPHID(); 77 - $primary_owner_phid = $package->getPrimaryOwnerPHID(); 78 - if ($old_primary_owner_phid == $primary_owner_phid) { 79 - return null; 80 - } 81 - 82 - $section = array(); 83 - $section[] = pht('PRIMARY OWNER CHANGE'); 84 - $section[] = ' '.pht('Old owner:').' '. 85 - $handles[$old_primary_owner_phid]->getName(); 86 - $section[] = ' '.pht('New owner:').' '. 87 - $handles[$primary_owner_phid]->getName(); 88 - 89 - return implode("\n", $section); 90 - } 91 - 92 - protected function renderOwnersSection() { 93 - $section = array(); 94 - $add_owners = $this->addOwners; 95 - $remove_owners = $this->removeOwners; 96 - $handles = $this->getHandles(); 97 - 98 - if ($add_owners) { 99 - $add_owners = array_select_keys($handles, $add_owners); 100 - $add_owners = mpull($add_owners, 'getName'); 101 - $section[] = pht('ADDED OWNERS'); 102 - $section[] = ' '.implode(', ', $add_owners); 103 - } 104 - 105 - if ($remove_owners) { 106 - if ($add_owners) { 107 - $section[] = ''; 108 - } 109 - $remove_owners = array_select_keys($handles, $remove_owners); 110 - $remove_owners = mpull($remove_owners, 'getName'); 111 - $section[] = pht('REMOVED OWNERS'); 112 - $section[] = ' '.implode(', ', $remove_owners); 113 - } 114 - 115 - if ($section) { 116 - return implode("\n", $section); 117 - } else { 118 - return null; 119 - } 120 - } 121 - 122 - protected function renderAuditingEnabledSection() { 123 - $package = $this->getPackage(); 124 - $old_auditing_enabled = $package->getOldAuditingEnabled(); 125 - $auditing_enabled = $package->getAuditingEnabled(); 126 - if ($old_auditing_enabled == $auditing_enabled) { 127 - return null; 128 - } 129 - 130 - $section = array(); 131 - $section[] = pht('AUDITING ENABLED STATUS CHANGE'); 132 - $section[] = ' '.pht('Old value:').' '. 133 - ($old_auditing_enabled ? pht('Enabled') : pht('Disabled')); 134 - $section[] = ' '.pht('New value:').' '. 135 - ($auditing_enabled ? pht('Enabled') : pht('Disabled')); 136 - return implode("\n", $section); 137 - } 138 - 139 - protected function renderPathsSection() { 140 - $section = array(); 141 - if ($this->addPaths) { 142 - $section[] = pht('ADDED PATHS'); 143 - foreach ($this->addPaths as $repository_phid => $paths) { 144 - $section[] = $this->renderRepoSubSection($repository_phid, $paths); 145 - } 146 - } 147 - 148 - if ($this->removePaths) { 149 - if ($this->addPaths) { 150 - $section[] = ''; 151 - } 152 - $section[] = pht('REMOVED PATHS'); 153 - foreach ($this->removePaths as $repository_phid => $paths) { 154 - $section[] = $this->renderRepoSubSection($repository_phid, $paths); 155 - } 156 - } 157 - return implode("\n", $section); 158 - } 159 - 160 - }
+36 -3
src/applications/owners/query/PhabricatorOwnersPackageQuery.php
··· 3 3 final class PhabricatorOwnersPackageQuery 4 4 extends PhabricatorCursorPagedPolicyAwareQuery { 5 5 6 + private $ids; 6 7 private $phids; 7 8 private $ownerPHIDs; 9 + private $repositoryPHIDs; 8 10 9 11 /** 10 12 * Owners are direct owners, and members of owning projects. ··· 19 21 return $this; 20 22 } 21 23 24 + public function withIDs(array $ids) { 25 + $this->ids = $ids; 26 + return $this; 27 + } 28 + 29 + public function withRepositoryPHIDs(array $phids) { 30 + $this->repositoryPHIDs = $phids; 31 + return $this; 32 + } 33 + 22 34 protected function loadPage() { 23 35 $table = new PhabricatorOwnersPackage(); 24 36 $conn_r = $table->establishConnection('r'); ··· 38 50 protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { 39 51 $joins = array(); 40 52 41 - if ($this->ownerPHIDs) { 53 + if ($this->ownerPHIDs !== null) { 42 54 $joins[] = qsprintf( 43 55 $conn_r, 44 56 'JOIN %T o ON o.packageID = p.id', 45 57 id(new PhabricatorOwnersOwner())->getTableName()); 46 58 } 47 59 60 + if ($this->repositoryPHIDs !== null) { 61 + $joins[] = qsprintf( 62 + $conn_r, 63 + 'JOIN %T rpath ON rpath.packageID = p.id', 64 + id(new PhabricatorOwnersPath())->getTableName()); 65 + } 66 + 48 67 return implode(' ', $joins); 49 68 } 50 69 51 70 protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { 52 71 $where = array(); 53 72 54 - if ($this->phids) { 73 + if ($this->phids !== null) { 55 74 $where[] = qsprintf( 56 75 $conn_r, 57 76 'p.phid IN (%Ls)', 58 77 $this->phids); 59 78 } 60 79 61 - if ($this->ownerPHIDs) { 80 + if ($this->ids !== null) { 81 + $where[] = qsprintf( 82 + $conn_r, 83 + 'p.id IN (%Ld)', 84 + $this->ids); 85 + } 86 + 87 + if ($this->repositoryPHIDs !== null) { 88 + $where[] = qsprintf( 89 + $conn_r, 90 + 'rpath.repositoryPHID IN (%Ls)', 91 + $this->repositoryPHIDs); 92 + } 93 + 94 + if ($this->ownerPHIDs !== null) { 62 95 $base_phids = $this->ownerPHIDs; 63 96 64 97 $query = new PhabricatorProjectQuery();
+134
src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorOwnersPackageSearchEngine 4 + extends PhabricatorApplicationSearchEngine { 5 + 6 + public function getResultTypeDescription() { 7 + return pht('Owners Packages'); 8 + } 9 + 10 + public function getApplicationClassName() { 11 + return 'PhabricatorOwnersApplication'; 12 + } 13 + 14 + public function buildSavedQueryFromRequest(AphrontRequest $request) { 15 + $saved = new PhabricatorSavedQuery(); 16 + 17 + $saved->setParameter( 18 + 'ownerPHIDs', 19 + $this->readUsersFromRequest( 20 + $request, 21 + 'owners', 22 + array( 23 + PhabricatorProjectProjectPHIDType::TYPECONST, 24 + ))); 25 + 26 + $saved->setParameter( 27 + 'repositoryPHIDs', 28 + $this->readPHIDsFromRequest( 29 + $request, 30 + 'repositories', 31 + array( 32 + PhabricatorRepositoryRepositoryPHIDType::TYPECONST, 33 + ))); 34 + 35 + return $saved; 36 + } 37 + 38 + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { 39 + $query = id(new PhabricatorOwnersPackageQuery()); 40 + 41 + $owner_phids = $saved->getParameter('ownerPHIDs', array()); 42 + if ($owner_phids) { 43 + $query->withOwnerPHIDs($owner_phids); 44 + } 45 + 46 + $repository_phids = $saved->getParameter('repositoryPHIDs', array()); 47 + if ($repository_phids) { 48 + $query->withRepositoryPHIDs($repository_phids); 49 + } 50 + 51 + return $query; 52 + } 53 + 54 + public function buildSearchForm( 55 + AphrontFormView $form, 56 + PhabricatorSavedQuery $saved) { 57 + 58 + $owner_phids = $saved->getParameter('ownerPHIDs', array()); 59 + $repository_phids = $saved->getParameter('repositoryPHIDs', array()); 60 + 61 + $form 62 + ->appendControl( 63 + id(new AphrontFormTokenizerControl()) 64 + ->setDatasource(new PhabricatorProjectOrUserDatasource()) 65 + ->setName('owners') 66 + ->setLabel(pht('Owners')) 67 + ->setValue($owner_phids)) 68 + ->appendControl( 69 + id(new AphrontFormTokenizerControl()) 70 + ->setDatasource(new DiffusionRepositoryDatasource()) 71 + ->setName('repositories') 72 + ->setLabel(pht('Repositories')) 73 + ->setValue($repository_phids)); 74 + } 75 + 76 + protected function getURI($path) { 77 + return '/owners/'.$path; 78 + } 79 + 80 + protected function getBuiltinQueryNames() { 81 + $names = array(); 82 + 83 + if ($this->requireViewer()->isLoggedIn()) { 84 + $names['owned'] = pht('Owned'); 85 + } 86 + 87 + $names += array( 88 + 'all' => pht('All Packages'), 89 + ); 90 + 91 + return $names; 92 + } 93 + 94 + public function buildSavedQueryFromBuiltin($query_key) { 95 + $query = $this->newSavedQuery(); 96 + $query->setQueryKey($query_key); 97 + 98 + switch ($query_key) { 99 + case 'all': 100 + return $query; 101 + case 'owned': 102 + return $query->setParameter( 103 + 'ownerPHIDs', 104 + array($this->requireViewer()->getPHID())); 105 + } 106 + 107 + return parent::buildSavedQueryFromBuiltin($query_key); 108 + } 109 + 110 + protected function renderResultList( 111 + array $packages, 112 + PhabricatorSavedQuery $query, 113 + array $handles) { 114 + assert_instances_of($packages, 'PhabricatorOwnersPackage'); 115 + 116 + $viewer = $this->requireViewer(); 117 + 118 + $list = id(new PHUIObjectItemListView()) 119 + ->setUser($viewer); 120 + foreach ($packages as $package) { 121 + $id = $package->getID(); 122 + 123 + $item = id(new PHUIObjectItemView()) 124 + ->setObject($package) 125 + ->setObjectName(pht('Package %d', $id)) 126 + ->setHeader($package->getName()) 127 + ->setHref('/owners/package/'.$id.'/'); 128 + 129 + $list->addItem($item); 130 + } 131 + 132 + return $list; 133 + } 134 + }
+10
src/applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorOwnersPackageTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new PhabricatorOwnersPackageTransaction(); 8 + } 9 + 10 + }
+46 -54
src/applications/owners/storage/PhabricatorOwnersPackage.php
··· 1 1 <?php 2 2 3 - final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO 4 - implements PhabricatorPolicyInterface { 3 + final class PhabricatorOwnersPackage 4 + extends PhabricatorOwnersDAO 5 + implements 6 + PhabricatorPolicyInterface, 7 + PhabricatorApplicationTransactionInterface { 5 8 6 9 protected $name; 7 10 protected $originalName; 8 11 protected $auditingEnabled; 9 12 protected $description; 10 13 protected $primaryOwnerPHID; 14 + protected $mailKey; 11 15 12 - private $unsavedOwners = self::ATTACHABLE; 13 - private $unsavedPaths = self::ATTACHABLE; 14 - private $actorPHID; 15 - private $oldPrimaryOwnerPHID; 16 - private $oldAuditingEnabled; 16 + public static function initializeNewPackage(PhabricatorUser $actor) { 17 + return id(new PhabricatorOwnersPackage()) 18 + ->setAuditingEnabled(0) 19 + ->setPrimaryOwnerPHID($actor->getPHID()); 20 + } 17 21 18 22 public function getCapabilities() { 19 23 return array( ··· 37 41 return array( 38 42 // This information is better available from the history table. 39 43 self::CONFIG_TIMESTAMPS => false, 40 - self::CONFIG_AUX_PHID => true, 44 + self::CONFIG_AUX_PHID => true, 41 45 self::CONFIG_COLUMN_SCHEMA => array( 42 46 'name' => 'text128', 43 47 'originalName' => 'text255', 44 48 'description' => 'text', 45 49 'primaryOwnerPHID' => 'phid?', 46 50 'auditingEnabled' => 'bool', 51 + 'mailKey' => 'bytes20', 47 52 ), 48 53 self::CONFIG_KEY_SCHEMA => array( 49 54 'key_phid' => null, ··· 60 65 } 61 66 62 67 public function generatePHID() { 63 - return PhabricatorPHID::generateNewPHID('OPKG'); 64 - } 65 - 66 - public function attachUnsavedOwners(array $owners) { 67 - $this->unsavedOwners = $owners; 68 - return $this; 69 - } 70 - 71 - public function getUnsavedOwners() { 72 - return $this->assertAttached($this->unsavedOwners); 73 - } 74 - 75 - public function attachUnsavedPaths(array $paths) { 76 - $this->unsavedPaths = $paths; 77 - return $this; 78 - } 79 - 80 - public function getUnsavedPaths() { 81 - return $this->assertAttached($this->unsavedPaths); 82 - } 83 - 84 - public function attachActorPHID($actor_phid) { 85 - $this->actorPHID = $actor_phid; 86 - return $this; 87 - } 88 - 89 - public function getActorPHID() { 90 - return $this->actorPHID; 91 - } 92 - 93 - public function attachOldPrimaryOwnerPHID($old_primary) { 94 - $this->oldPrimaryOwnerPHID = $old_primary; 95 - return $this; 68 + return PhabricatorPHID::generateNewPHID( 69 + PhabricatorOwnersPackagePHIDType::TYPECONST); 96 70 } 97 71 98 - public function getOldPrimaryOwnerPHID() { 99 - return $this->oldPrimaryOwnerPHID; 100 - } 72 + public function save() { 73 + if (!$this->getMailKey()) { 74 + $this->setMailKey(Filesystem::readRandomCharacters(20)); 75 + } 101 76 102 - public function attachOldAuditingEnabled($auditing_enabled) { 103 - $this->oldAuditingEnabled = $auditing_enabled; 104 - return $this; 105 - } 106 - 107 - public function getOldAuditingEnabled() { 108 - return $this->oldAuditingEnabled; 77 + return parent::save(); 109 78 } 110 79 111 80 public function setName($name) { ··· 143 112 } 144 113 145 114 return self::loadPackagesForPaths($repository, $paths); 146 - } 115 + } 147 116 148 - public static function loadOwningPackages($repository, $path) { 117 + public static function loadOwningPackages($repository, $path) { 149 118 if (empty($path)) { 150 119 return array(); 151 120 } 152 121 153 122 return self::loadPackagesForPaths($repository, array($path), 1); 154 - } 123 + } 155 124 156 125 private static function loadPackagesForPaths( 157 126 PhabricatorRepository $repository, ··· 268 237 } 269 238 return $result; 270 239 } 240 + 241 + 242 + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 243 + 244 + 245 + public function getApplicationTransactionEditor() { 246 + return new PhabricatorOwnersPackageTransactionEditor(); 247 + } 248 + 249 + public function getApplicationTransactionObject() { 250 + return $this; 251 + } 252 + 253 + public function getApplicationTransactionTemplate() { 254 + return new PhabricatorOwnersPackageTransaction(); 255 + } 256 + 257 + public function willRenderTimeline( 258 + PhabricatorApplicationTransactionView $timeline, 259 + AphrontRequest $request) { 260 + return $timeline; 261 + } 262 + 271 263 }
+211
src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorOwnersPackageTransaction 4 + extends PhabricatorApplicationTransaction { 5 + 6 + const TYPE_NAME = 'owners.name'; 7 + const TYPE_PRIMARY = 'owners.primary'; 8 + const TYPE_OWNERS = 'owners.owners'; 9 + const TYPE_AUDITING = 'owners.auditing'; 10 + const TYPE_DESCRIPTION = 'owners.description'; 11 + const TYPE_PATHS = 'owners.paths'; 12 + 13 + public function getApplicationName() { 14 + return 'owners'; 15 + } 16 + 17 + public function getApplicationTransactionType() { 18 + return PhabricatorOwnersPackagePHIDType::TYPECONST; 19 + } 20 + 21 + public function getRequiredHandlePHIDs() { 22 + $phids = parent::getRequiredHandlePHIDs(); 23 + 24 + $old = $this->getOldValue(); 25 + $new = $this->getNewValue(); 26 + 27 + switch ($this->getTransactionType()) { 28 + case self::TYPE_PRIMARY: 29 + if ($old) { 30 + $phids[] = $old; 31 + } 32 + if ($new) { 33 + $phids[] = $new; 34 + } 35 + break; 36 + case self::TYPE_OWNERS: 37 + $add = array_diff($new, $old); 38 + foreach ($add as $phid) { 39 + $phids[] = $phid; 40 + } 41 + $rem = array_diff($old, $new); 42 + foreach ($rem as $phid) { 43 + $phids[] = $phid; 44 + } 45 + break; 46 + } 47 + 48 + return $phids; 49 + } 50 + 51 + public function shouldHide() { 52 + $old = $this->getOldValue(); 53 + $new = $this->getNewValue(); 54 + 55 + switch ($this->getTransactionType()) { 56 + case self::TYPE_DESCRIPTION: 57 + return ($old === null); 58 + } 59 + } 60 + 61 + public function getTitle() { 62 + $old = $this->getOldValue(); 63 + $new = $this->getNewValue(); 64 + $author_phid = $this->getAuthorPHID(); 65 + 66 + switch ($this->getTransactionType()) { 67 + case self::TYPE_NAME: 68 + if ($old === null) { 69 + return pht( 70 + '%s created this package.', 71 + $this->renderHandleLink($author_phid)); 72 + } else { 73 + return pht( 74 + '%s renamed this package from "%s" to "%s".', 75 + $this->renderHandleLink($author_phid), 76 + $old, 77 + $new); 78 + } 79 + case self::TYPE_PRIMARY: 80 + return pht( 81 + '%s changed the primary owner for this package from %s to %s.', 82 + $this->renderHandleLink($author_phid), 83 + $this->renderHandleLink($old), 84 + $this->renderHandleLink($new)); 85 + case self::TYPE_OWNERS: 86 + $add = array_diff($new, $old); 87 + $rem = array_diff($old, $new); 88 + if ($add && !$rem) { 89 + return pht( 90 + '%s added %s owner(s): %s.', 91 + $this->renderHandleLink($author_phid), 92 + count($add), 93 + $this->renderHandleList($add)); 94 + } else if ($rem && !$add) { 95 + return pht( 96 + '%s removed %s owner(s): %s.', 97 + $this->renderHandleLink($author_phid), 98 + count($rem), 99 + $this->renderHandleList($rem)); 100 + } else { 101 + return pht( 102 + '%s changed %s package owner(s), added %s: %s; removed %s: %s.', 103 + $this->renderHandleLink($author_phid), 104 + count($add) + count($rem), 105 + count($add), 106 + $this->renderHandleList($add), 107 + count($rem), 108 + $this->renderHandleList($rem)); 109 + } 110 + case self::TYPE_AUDITING: 111 + if ($new) { 112 + return pht( 113 + '%s enabled auditing for this package.', 114 + $this->renderHandleLink($author_phid)); 115 + } else { 116 + return pht( 117 + '%s disabled auditing for this package.', 118 + $this->renderHandleLink($author_phid)); 119 + } 120 + case self::TYPE_DESCRIPTION: 121 + return pht( 122 + '%s updated the description for this package.', 123 + $this->renderHandleLink($author_phid)); 124 + case self::TYPE_PATHS: 125 + // TODO: Flesh this out. 126 + return pht( 127 + '%s updated paths for this package.', 128 + $this->renderHandleLink($author_phid)); 129 + } 130 + 131 + return parent::getTitle(); 132 + } 133 + 134 + public function hasChangeDetails() { 135 + switch ($this->getTransactionType()) { 136 + case self::TYPE_DESCRIPTION: 137 + return ($this->getOldValue() !== null); 138 + case self::TYPE_PATHS: 139 + return true; 140 + } 141 + 142 + return parent::hasChangeDetails(); 143 + } 144 + 145 + public function renderChangeDetails(PhabricatorUser $viewer) { 146 + switch ($this->getTransactionType()) { 147 + case self::TYPE_DESCRIPTION: 148 + $old = $this->getOldValue(); 149 + $new = $this->getNewValue(); 150 + 151 + return $this->renderTextCorpusChangeDetails( 152 + $viewer, 153 + $old, 154 + $new); 155 + case self::TYPE_PATHS: 156 + $old = $this->getOldValue(); 157 + $new = $this->getNewValue(); 158 + 159 + $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); 160 + list($rem, $add) = $diffs; 161 + 162 + $rows = array(); 163 + foreach ($rem as $ref) { 164 + $rows[] = array( 165 + 'class' => 'diff-removed', 166 + 'change' => '-', 167 + ) + $ref; 168 + } 169 + 170 + foreach ($add as $ref) { 171 + $rows[] = array( 172 + 'class' => 'diff-added', 173 + 'change' => '+', 174 + ) + $ref; 175 + } 176 + 177 + $rowc = array(); 178 + foreach ($rows as $key => $row) { 179 + $rowc[] = $row['class']; 180 + $rows[$key] = array( 181 + $row['change'], 182 + $row['excluded'] ? pht('Exclude') : pht('Include'), 183 + $viewer->renderHandle($row['repositoryPHID']), 184 + $row['path'], 185 + ); 186 + } 187 + 188 + $table = id(new AphrontTableView($rows)) 189 + ->setRowClasses($rowc) 190 + ->setHeaders( 191 + array( 192 + null, 193 + pht('Type'), 194 + pht('Repository'), 195 + pht('Path'), 196 + )) 197 + ->setColumnClasses( 198 + array( 199 + null, 200 + null, 201 + null, 202 + 'wide', 203 + )); 204 + 205 + return $table; 206 + } 207 + 208 + return parent::renderChangeDetails($viewer); 209 + } 210 + 211 + }
+48
src/applications/owners/storage/PhabricatorOwnersPath.php
··· 22 22 ) + parent::getConfiguration(); 23 23 } 24 24 25 + 26 + public static function newFromRef(array $ref) { 27 + $path = new PhabricatorOwnersPath(); 28 + $path->repositoryPHID = $ref['repositoryPHID']; 29 + $path->path = $ref['path']; 30 + $path->excluded = $ref['excluded']; 31 + return $path; 32 + } 33 + 34 + public function getRef() { 35 + return array( 36 + 'repositoryPHID' => $this->getRepositoryPHID(), 37 + 'path' => $this->getPath(), 38 + 'excluded' => (int)$this->getExcluded(), 39 + ); 40 + } 41 + 42 + public static function getTransactionValueChanges(array $old, array $new) { 43 + return array( 44 + self::getTransactionValueDiff($old, $new), 45 + self::getTransactionValueDiff($new, $old), 46 + ); 47 + } 48 + 49 + private static function getTransactionValueDiff(array $u, array $v) { 50 + $set = self::getSetFromTransactionValue($v); 51 + 52 + foreach ($u as $key => $ref) { 53 + if (self::isRefInSet($ref, $set)) { 54 + unset($u[$key]); 55 + } 56 + } 57 + 58 + return $u; 59 + } 60 + 61 + public static function getSetFromTransactionValue(array $v) { 62 + $set = array(); 63 + foreach ($v as $ref) { 64 + $set[$ref['repositoryPHID']][$ref['path']][$ref['excluded']] = true; 65 + } 66 + return $set; 67 + } 68 + 69 + public static function isRefInSet(array $ref, array $set) { 70 + return isset($set[$ref['repositoryPHID']][$ref['path']][$ref['excluded']]); 71 + } 72 + 25 73 }
-100
src/applications/releeph/conduit/ReleephProjectInfoConduitAPIMethod.php
··· 1 - <?php 2 - 3 - final class ReleephProjectInfoConduitAPIMethod extends ReleephConduitAPIMethod { 4 - 5 - public function getAPIMethodName() { 6 - return 'releeph.projectinfo'; 7 - } 8 - 9 - public function getMethodDescription() { 10 - return pht( 11 - 'Fetch information about all Releeph projects '. 12 - 'for a given Arcanist project.'); 13 - } 14 - 15 - protected function defineParamTypes() { 16 - return array( 17 - 'arcProjectName' => 'optional string', 18 - ); 19 - } 20 - 21 - protected function defineReturnType() { 22 - return 'dict<string, wild>'; 23 - } 24 - 25 - protected function defineErrorTypes() { 26 - return array( 27 - 'ERR_UNKNOWN_ARC' => pht( 28 - "The given Arcanist project name doesn't exist in the ". 29 - "installation of Phabricator you are accessing."), 30 - ); 31 - } 32 - 33 - protected function execute(ConduitAPIRequest $request) { 34 - $arc_project_name = $request->getValue('arcProjectName'); 35 - if ($arc_project_name) { 36 - $arc_project = id(new PhabricatorRepositoryArcanistProject()) 37 - ->loadOneWhere('name = %s', $arc_project_name); 38 - if (!$arc_project) { 39 - throw id(new ConduitException('ERR_UNKNOWN_ARC')) 40 - ->setErrorDescription( 41 - pht( 42 - "Unknown Arcanist project '%s': ". 43 - "are you using the correct Conduit URI?", 44 - $arc_project_name)); 45 - } 46 - 47 - $releeph_projects = id(new ReleephProject()) 48 - ->loadAllWhere('arcanistProjectID = %d', $arc_project->getID()); 49 - } else { 50 - $releeph_projects = id(new ReleephProject())->loadAll(); 51 - } 52 - 53 - $releeph_projects = mfilter($releeph_projects, 'getIsActive'); 54 - 55 - $result = array(); 56 - foreach ($releeph_projects as $releeph_project) { 57 - $selector = $releeph_project->getReleephFieldSelector(); 58 - $fields = $selector->getFieldSpecifications(); 59 - 60 - $fields_info = array(); 61 - foreach ($fields as $field) { 62 - $field->setReleephProject($releeph_project); 63 - if ($field->isEditable()) { 64 - $key = $field->getKeyForConduit(); 65 - $fields_info[$key] = array( 66 - 'class' => get_class($field), 67 - 'name' => $field->getName(), 68 - 'key' => $key, 69 - 'arcHelp' => $field->renderHelpForArcanist(), 70 - ); 71 - } 72 - } 73 - 74 - $releeph_branches = mfilter( 75 - id(new ReleephBranch()) 76 - ->loadAllWhere('releephProjectID = %d', $releeph_project->getID()), 77 - 'getIsActive'); 78 - 79 - $releeph_branches_struct = array(); 80 - foreach ($releeph_branches as $branch) { 81 - $releeph_branches_struct[] = array( 82 - 'branchName' => $branch->getName(), 83 - 'projectName' => $releeph_project->getName(), 84 - 'projectPHID' => $releeph_project->getPHID(), 85 - 'branchPHID' => $branch->getPHID(), 86 - ); 87 - } 88 - 89 - $result[] = array( 90 - 'projectName' => $releeph_project->getName(), 91 - 'projectPHID' => $releeph_project->getPHID(), 92 - 'branches' => $releeph_branches_struct, 93 - 'fields' => $fields_info, 94 - ); 95 - } 96 - 97 - return $result; 98 - } 99 - 100 - }
+2 -2
src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php
··· 13 13 $template = ReleephBranchTemplate::getDefaultTemplate(); 14 14 } 15 15 16 - $arc_project_id = $request->getInt('arcProjectID'); 16 + $repository_phid = $request->getInt('repositoryPHID'); 17 17 $fake_commit_handle = 18 18 ReleephBranchTemplate::getFakeCommitHandleFor( 19 - $arc_project_id, 19 + $repository_phid, 20 20 $request->getUser()); 21 21 22 22 list($name, $errors) = id(new ReleephBranchTemplate())
+22 -58
src/applications/releeph/controller/product/ReleephProductCreateController.php
··· 6 6 $request = $this->getRequest(); 7 7 $name = trim($request->getStr('name')); 8 8 $trunk_branch = trim($request->getStr('trunkBranch')); 9 - $arc_pr_id = $request->getInt('arcPrID'); 10 - 11 - $arc_projects = $this->loadArcProjects(); 9 + $repository_phid = $request->getStr('repositoryPHID'); 12 10 13 11 $e_name = true; 14 12 $e_trunk_branch = true; ··· 27 25 'You must specify which branch you will be picking from.'); 28 26 } 29 27 30 - $arc_project = $arc_projects[$arc_pr_id]; 31 - $pr_repository = null; 32 - if ($arc_project->getRepositoryID()) { 33 - $pr_repository = id(new PhabricatorRepositoryQuery()) 34 - ->setViewer($request->getUser()) 35 - ->withIDs(array($arc_project->getRepositoryID())) 36 - ->executeOne(); 37 - } 28 + $pr_repository = id(new PhabricatorRepositoryQuery()) 29 + ->setViewer($request->getUser()) 30 + ->withPHIDs(array($repository_phid)) 31 + ->executeOne(); 38 32 39 33 40 34 if (!$errors) { ··· 42 36 ->setName($name) 43 37 ->setTrunkBranch($trunk_branch) 44 38 ->setRepositoryPHID($pr_repository->getPHID()) 45 - ->setArcanistProjectID($arc_project->getID()) 46 39 ->setCreatedByUserPHID($request->getUser()->getPHID()) 47 40 ->setIsActive(1); 48 41 ··· 58 51 } 59 52 } 60 53 61 - $arc_project_options = $this->getArcProjectSelectOptions($arc_projects); 54 + $repo_options = $this->getRepositorySelectOptions(); 62 55 63 56 $product_name_input = id(new AphrontFormTextControl()) 64 57 ->setLabel(pht('Name')) ··· 68 61 ->setError($e_name) 69 62 ->setCaption(pht('A name like "Thrift" but not "Thrift releases".')); 70 63 71 - $arc_project_input = id(new AphrontFormSelectControl()) 72 - ->setLabel(pht('Arc Project')) 73 - ->setName('arcPrID') 74 - ->setValue($arc_pr_id) 75 - ->setCaption(pht( 76 - "If your Arc project isn't listed, associate it with a repository %s.", 77 - phutil_tag( 78 - 'a', 79 - array( 80 - 'href' => '/repository/', 81 - 'target' => '_blank', 82 - ), 83 - 'here'))) 84 - ->setOptions($arc_project_options); 64 + $repository_input = id(new AphrontFormSelectControl()) 65 + ->setLabel(pht('Repository')) 66 + ->setName('repositoryPHID') 67 + ->setValue($repository_phid) 68 + ->setOptions($repo_options); 85 69 86 70 $branch_name_preview = id(new ReleephBranchPreviewView()) 87 71 ->setLabel(pht('Example Branch')) 88 72 ->addControl('projectName', $product_name_input) 89 - ->addControl('arcProjectID', $arc_project_input) 73 + ->addControl('repositoryPHID', $repository_input) 90 74 ->addStatic('template', '') 91 75 ->addStatic('isSymbolic', false); 92 76 93 77 $form = id(new AphrontFormView()) 94 78 ->setUser($request->getUser()) 95 79 ->appendChild($product_name_input) 96 - ->appendChild($arc_project_input) 80 + ->appendChild($repository_input) 97 81 ->appendChild( 98 82 id(new AphrontFormTextControl()) 99 83 ->setLabel(pht('Trunk')) ··· 126 110 )); 127 111 } 128 112 129 - private function loadArcProjects() { 130 - $viewer = $this->getRequest()->getUser(); 131 - 132 - $projects = id(new PhabricatorRepositoryArcanistProjectQuery()) 133 - ->setViewer($viewer) 134 - ->needRepositories(true) 113 + private function getRepositorySelectOptions() { 114 + $repos = id(new PhabricatorRepositoryQuery()) 115 + ->setViewer($this->getRequest()->getUser()) 135 116 ->execute(); 136 117 137 - $projects = mfilter($projects, 'getRepository'); 138 - $projects = msort($projects, 'getName'); 139 - 140 - return $projects; 141 - } 142 - 143 - private function getArcProjectSelectOptions(array $arc_projects) { 144 - assert_instances_of($arc_projects, 'PhabricatorRepositoryArcanistProject'); 145 - 146 - $repos = mpull($arc_projects, 'getRepository'); 118 + $repos = msort($repos, 'getName'); 147 119 $repos = mpull($repos, null, 'getID'); 148 120 149 - $groups = array(); 150 - foreach ($arc_projects as $arc_project) { 151 - $id = $arc_project->getID(); 152 - $repo_id = $arc_project->getRepository()->getID(); 153 - $groups[$repo_id][$id] = $arc_project->getName(); 154 - } 155 - 156 121 $choices = array(); 157 - foreach ($groups as $repo_id => $group) { 158 - $repo_name = $repos[$repo_id]->getName(); 159 - $callsign = $repos[$repo_id]->getCallsign(); 160 - $name = "r{$callsign} ({$repo_name})"; 161 - $choices[$name] = $group; 122 + 123 + foreach ($repos as $repo_id => $repo) { 124 + $repo_name = $repo->getName(); 125 + $callsign = $repo->getCallsign(); 126 + $choices[$repo->getPHID()] = "r{$callsign} ({$repo_name})"; 162 127 } 163 128 164 129 ksort($choices); 165 - 166 130 return $choices; 167 131 } 168 132
+7 -7
src/applications/releeph/controller/product/ReleephProductEditController.php
··· 15 15 $product = id(new ReleephProductQuery()) 16 16 ->setViewer($viewer) 17 17 ->withIDs(array($this->productID)) 18 - ->needArcanistProjects(true) 19 18 ->requireCapabilities( 20 19 array( 21 20 PhabricatorPolicyCapability::CAN_VIEW, ··· 48 47 $test_paths = $product->getDetail('testPaths', array()); 49 48 } 50 49 51 - $arc_project_id = $product->getArcanistProjectID(); 50 + $repository_phid = $product->getRepositoryPHID(); 52 51 53 52 if ($request->isFormPost()) { 54 53 $pusher_phids = $request->getArr('pushers'); ··· 92 91 ->setDetail('branchTemplate', $branch_template) 93 92 ->setDetail('testPaths', $test_paths); 94 93 95 - $fake_commit_handle = 96 - ReleephBranchTemplate::getFakeCommitHandleFor($arc_project_id, $viewer); 94 + $fake_commit_handle = ReleephBranchTemplate::getFakeCommitHandleFor( 95 + $repository_phid, 96 + $viewer); 97 97 98 98 if ($branch_template) { 99 99 list($branch_name, $template_errors) = id(new ReleephBranchTemplate()) ··· 136 136 $product->getRepository()->getName())) 137 137 ->appendChild( 138 138 id(new AphrontFormStaticControl()) 139 - ->setLabel(pht('Arc Project')) 139 + ->setLabel(pht('Repository')) 140 140 ->setValue( 141 - $product->getArcanistProject()->getName())) 141 + $product->getRepository()->getName())) 142 142 ->appendChild( 143 143 id(new AphrontFormStaticControl()) 144 144 ->setLabel(pht('Releeph Project PHID')) ··· 179 179 $branch_template_preview = id(new ReleephBranchPreviewView()) 180 180 ->setLabel(pht('Preview')) 181 181 ->addControl('template', $branch_template_input) 182 - ->addStatic('arcProjectID', $arc_project_id) 182 + ->addStatic('repositoryPHID', $repository_phid) 183 183 ->addStatic('isSymbolic', false) 184 184 ->addStatic('projectName', $product->getName()); 185 185
+5 -6
src/applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php
··· 25 25 } 26 26 $this->revision = $diff_rev; 27 27 28 - $arc_project = id(new PhabricatorRepositoryArcanistProject()) 29 - ->loadOneWhere('phid = %s', $this->revision->getArcanistProjectPHID()); 28 + $repository = $this->revision->getRepository(); 30 29 31 30 $projects = id(new ReleephProject())->loadAllWhere( 32 - 'arcanistProjectID = %d AND isActive = 1', 33 - $arc_project->getID()); 31 + 'repositoryPHID = %s AND isActive = 1', 32 + $repository->getPHID()); 34 33 if (!$projects) { 35 34 throw new Exception( 36 35 pht( 37 - "%s belongs to the '%s' Arcanist project, ". 36 + "%s belongs to the '%s' repository, ". 38 37 "which is not part of any Releeph project!", 39 38 'D'.$this->revision->getID(), 40 - $arc_project->getName())); 39 + $repository->getMonogram())); 41 40 } 42 41 43 42 $branches = id(new ReleephBranch())->loadAllWhere(
-28
src/applications/releeph/query/ReleephProductQuery.php
··· 8 8 private $phids; 9 9 private $repositoryPHIDs; 10 10 11 - private $needArcanistProjects; 12 - 13 11 const ORDER_ID = 'order-id'; 14 12 const ORDER_NAME = 'order-name'; 15 13 ··· 44 42 45 43 public function withRepositoryPHIDs(array $repository_phids) { 46 44 $this->repositoryPHIDs = $repository_phids; 47 - return $this; 48 - } 49 - 50 - public function needArcanistProjects($need) { 51 - $this->needArcanistProjects = $need; 52 45 return $this; 53 46 } 54 47 ··· 88 81 } 89 82 90 83 return $projects; 91 - } 92 - 93 - protected function didFilterPage(array $products) { 94 - if ($this->needArcanistProjects) { 95 - $project_ids = array_filter(mpull($products, 'getArcanistProjectID')); 96 - if ($project_ids) { 97 - $projects = id(new PhabricatorRepositoryArcanistProject()) 98 - ->loadAllWhere('id IN (%Ld)', $project_ids); 99 - $projects = mpull($projects, null, 'getID'); 100 - } else { 101 - $projects = array(); 102 - } 103 - 104 - foreach ($products as $product) { 105 - $project_id = $product->getArcanistProjectID(); 106 - $project = idx($projects, $project_id); 107 - $product->attachArcanistProject($project); 108 - } 109 - } 110 - 111 - return $products; 112 84 } 113 85 114 86 protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+1 -7
src/applications/releeph/query/ReleephProductSearchEngine.php
··· 21 21 22 22 public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { 23 23 $query = id(new ReleephProductQuery()) 24 - ->setOrder(ReleephProductQuery::ORDER_NAME) 25 - ->needArcanistProjects(true); 24 + ->setOrder(ReleephProductQuery::ORDER_NAME); 26 25 27 26 $active = $saved->getParameter('active'); 28 27 $value = idx($this->getActiveValues(), $active); ··· 118 117 'href' => '/diffusion/'.$repo->getCallsign().'/', 119 118 ), 120 119 'r'.$repo->getCallsign())); 121 - 122 - $arc = $product->getArcanistProject(); 123 - if ($arc) { 124 - $item->addAttribute($arc->getName()); 125 - } 126 120 127 121 $list->addItem($item); 128 122 }
+1
src/applications/releeph/storage/ReleephProject.php
··· 35 35 'name' => 'text128', 36 36 'trunkBranch' => 'text255', 37 37 'isActive' => 'bool', 38 + 'arcanistProjectID' => 'id?', 38 39 ), 39 40 self::CONFIG_KEY_SCHEMA => array( 40 41 'projectName' => array(
+4 -4
src/applications/releeph/view/branch/ReleephBranchPreviewView.php
··· 24 24 25 25 protected function renderInput() { 26 26 static $required_params = array( 27 - 'arcProjectID', 27 + 'repositoryPHID', 28 28 'projectName', 29 29 'isSymbolic', 30 30 'template', ··· 43 43 $output_id = celerity_generate_unique_node_id(); 44 44 45 45 Javelin::initBehavior('releeph-preview-branch', array( 46 - 'uri' => '/releeph/branch/preview/', 47 - 'outputID' => $output_id, 48 - 'params' => array( 46 + 'uri' => '/releeph/branch/preview/', 47 + 'outputID' => $output_id, 48 + 'params' => array( 49 49 'static' => $this->statics, 50 50 'dynamic' => $this->dynamics, 51 51 ),
+5 -16
src/applications/releeph/view/branch/ReleephBranchTemplate.php
··· 20 20 } 21 21 22 22 public static function getFakeCommitHandleFor( 23 - $arc_project_id, 23 + $repository_phid, 24 24 PhabricatorUser $viewer) { 25 25 26 - $arc_project = id(new PhabricatorRepositoryArcanistProject()) 27 - ->load($arc_project_id); 28 - if (!$arc_project) { 29 - throw new Exception( 30 - pht( 31 - "No Arc project found with id '%s'!", 32 - $arc_project_id)); 33 - } 26 + $repository = id(new PhabricatorRepositoryQuery()) 27 + ->setViewer($viewer) 28 + ->withPHIDs(array($repository_phid)) 29 + ->executeOne(); 34 30 35 - $repository = null; 36 - if ($arc_project->getRepositoryID()) { 37 - $repository = id(new PhabricatorRepositoryQuery()) 38 - ->setViewer($viewer) 39 - ->withIDs(array($arc_project->getRepositoryID())) 40 - ->executeOne(); 41 - } 42 31 $fake_handle = 'SOFAKE'; 43 32 if ($repository) { 44 33 $fake_handle = id(new PhabricatorObjectHandle())
-4
src/applications/repository/application/PhabricatorRepositoriesApplication.php
··· 30 30 return array( 31 31 '/repository/' => array( 32 32 '' => 'PhabricatorRepositoryListController', 33 - 'project/edit/(?P<id>[1-9]\d*)/' 34 - => 'PhabricatorRepositoryArcanistProjectEditController', 35 - 'project/delete/(?P<id>[1-9]\d*)/' 36 - => 'PhabricatorRepositoryArcanistProjectDeleteController', 37 33 ), 38 34 ); 39 35 }
-42
src/applications/repository/controller/PhabricatorRepositoryArcanistProjectDeleteController.php
··· 1 - <?php 2 - 3 - final class PhabricatorRepositoryArcanistProjectDeleteController 4 - extends PhabricatorRepositoryController { 5 - 6 - private $id; 7 - 8 - public function willProcessRequest(array $data) { 9 - $this->id = $data['id']; 10 - } 11 - 12 - public function processRequest() { 13 - 14 - $arc_project = 15 - id(new PhabricatorRepositoryArcanistProject())->load($this->id); 16 - if (!$arc_project) { 17 - return new Aphront404Response(); 18 - } 19 - 20 - $request = $this->getRequest(); 21 - 22 - if ($request->isDialogFormPost()) { 23 - $arc_project->delete(); 24 - return id(new AphrontRedirectResponse())->setURI('/repository/'); 25 - } 26 - 27 - $dialog = new AphrontDialogView(); 28 - $dialog 29 - ->setUser($request->getUser()) 30 - ->setTitle(pht('Really delete this arcanist project?')) 31 - ->appendChild( 32 - pht( 33 - 'Really delete the "%s" arcanist project? '. 34 - 'This operation can not be undone.', 35 - $arc_project->getName())) 36 - ->setSubmitURI('/repository/project/delete/'.$this->id.'/') 37 - ->addSubmitButton(pht('Delete Arcanist Project')) 38 - ->addCancelButton('/repository/'); 39 - 40 - return id(new AphrontDialogResponse())->setDialog($dialog); 41 - } 42 - }
-85
src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php
··· 1 - <?php 2 - 3 - final class PhabricatorRepositoryArcanistProjectEditController 4 - extends PhabricatorRepositoryController { 5 - 6 - private $id; 7 - 8 - public function willProcessRequest(array $data) { 9 - $this->id = $data['id']; 10 - } 11 - 12 - public function processRequest() { 13 - 14 - $request = $this->getRequest(); 15 - $user = $request->getUser(); 16 - 17 - $project = id(new PhabricatorRepositoryArcanistProject())->load($this->id); 18 - if (!$project) { 19 - return new Aphront404Response(); 20 - } 21 - 22 - $repositories = id(new PhabricatorRepositoryQuery()) 23 - ->setViewer($user) 24 - ->execute(); 25 - $repos = array( 26 - 0 => 'None', 27 - ); 28 - foreach ($repositories as $repository) { 29 - $callsign = $repository->getCallsign(); 30 - $name = $repository->getname(); 31 - $repos[$repository->getID()] = "r{$callsign} ({$name})"; 32 - } 33 - // note "None" will still be first thanks to 'r' prefix 34 - asort($repos); 35 - 36 - if ($request->isFormPost()) { 37 - $repo_id = $request->getInt('repository', 0); 38 - if (isset($repos[$repo_id])) { 39 - $project->setRepositoryID($repo_id); 40 - $project->save(); 41 - 42 - return id(new AphrontRedirectResponse()) 43 - ->setURI('/repository/'); 44 - } 45 - } 46 - 47 - $form = id(new AphrontFormView()) 48 - ->setUser($user) 49 - ->appendChild( 50 - id(new AphrontFormStaticControl()) 51 - ->setLabel(pht('Name')) 52 - ->setValue($project->getName())) 53 - ->appendChild( 54 - id(new AphrontFormStaticControl()) 55 - ->setLabel('PHID') 56 - ->setValue($project->getPHID())) 57 - ->appendChild( 58 - id(new AphrontFormSelectControl()) 59 - ->setLabel(pht('Repository')) 60 - ->setOptions($repos) 61 - ->setName('repository') 62 - ->setValue($project->getRepositoryID())) 63 - ->appendChild( 64 - id(new AphrontFormSubmitControl()) 65 - ->addCancelButton('/repository/') 66 - ->setValue(pht('Save'))); 67 - 68 - $panel = new PHUIObjectBoxView(); 69 - $panel->setHeaderText(pht('Edit Arcanist Project')); 70 - $panel->setForm($form); 71 - 72 - $crumbs = $this->buildApplicationCrumbs(); 73 - $crumbs->addTextCrumb(pht('Edit Project')); 74 - 75 - return $this->buildApplicationPage( 76 - array( 77 - $crumbs, 78 - $panel, 79 - ), 80 - array( 81 - 'title' => pht('Edit Project'), 82 - )); 83 - } 84 - 85 - }
-63
src/applications/repository/controller/PhabricatorRepositoryListController.php
··· 85 85 $panel->setHeader($header); 86 86 $panel->setTable($table); 87 87 88 - $projects = id(new PhabricatorRepositoryArcanistProject())->loadAll(); 89 - 90 - $rows = array(); 91 - foreach ($projects as $project) { 92 - $repo = idx($repos, $project->getRepositoryID()); 93 - if ($repo) { 94 - $repo_name = $repo->getName(); 95 - } else { 96 - $repo_name = '-'; 97 - } 98 - 99 - $rows[] = array( 100 - $project->getName(), 101 - $repo_name, 102 - phutil_tag( 103 - 'a', 104 - array( 105 - 'href' => '/repository/project/edit/'.$project->getID().'/', 106 - 'class' => 'button grey small', 107 - ), 108 - pht('Edit')), 109 - javelin_tag( 110 - 'a', 111 - array( 112 - 'href' => '/repository/project/delete/'.$project->getID().'/', 113 - 'class' => 'button grey small', 114 - 'sigil' => 'workflow', 115 - ), 116 - pht('Delete')), 117 - ); 118 - 119 - } 120 - 121 - $project_table = new AphrontTableView($rows); 122 - $project_table->setNoDataString(pht('No Arcanist Projects')); 123 - $project_table->setHeaders( 124 - array( 125 - pht('Project ID'), 126 - pht('Repository'), 127 - '', 128 - '', 129 - )); 130 - $project_table->setColumnClasses( 131 - array( 132 - '', 133 - 'wide', 134 - 'action', 135 - 'action', 136 - )); 137 - 138 - $project_table->setColumnVisibility( 139 - array( 140 - true, 141 - true, 142 - $is_admin, 143 - $is_admin, 144 - )); 145 - 146 - $project_panel = new PHUIObjectBoxView(); 147 - $project_panel->setHeaderText(pht('Arcanist Projects')); 148 - $project_panel->setTable($project_table); 149 - 150 88 $crumbs = $this->buildApplicationCrumbs(); 151 89 $crumbs->addTextCrumb(pht('Repository List')); 152 90 ··· 154 92 array( 155 93 $crumbs, 156 94 $panel, 157 - $project_panel, 158 95 ), 159 96 array( 160 97 'title' => pht('Repository List'),
+13 -4
src/applications/repository/customfield/PhabricatorCommitBranchesField.php
··· 7 7 return 'diffusion:branches'; 8 8 } 9 9 10 - public function shouldAppearInApplicationTransactions() { 10 + public function getFieldName() { 11 + return pht('Branches'); 12 + } 13 + 14 + public function getFieldDescription() { 15 + return pht('Shows branches a commit appears on in email.'); 16 + } 17 + 18 + public function shouldAppearInTransactionMail() { 11 19 return true; 12 20 } 13 21 14 - public function buildApplicationTransactionMailBody( 15 - PhabricatorApplicationTransaction $xaction, 16 - PhabricatorMetaMTAMailBody $body) { 22 + public function updateTransactionMailBody( 23 + PhabricatorMetaMTAMailBody $body, 24 + PhabricatorApplicationTransactionEditor $editor, 25 + array $xactions) { 17 26 18 27 $params = array( 19 28 'contains' => $this->getObject()->getCommitIdentifier(),
+74
src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php
··· 1 + <?php 2 + 3 + final class PhabricatorCommitMergedCommitsField 4 + extends PhabricatorCommitCustomField { 5 + 6 + public function getFieldKey() { 7 + return 'diffusion:mergedcommits'; 8 + } 9 + 10 + public function getFieldName() { 11 + return pht('Merged Commits'); 12 + } 13 + 14 + public function getFieldDescription() { 15 + return pht('For merge commits, shows merged changes in email.'); 16 + } 17 + 18 + public function shouldDisableByDefault() { 19 + return true; 20 + } 21 + 22 + public function shouldAppearInTransactionMail() { 23 + return true; 24 + } 25 + 26 + public function updateTransactionMailBody( 27 + PhabricatorMetaMTAMailBody $body, 28 + PhabricatorApplicationTransactionEditor $editor, 29 + array $xactions) { 30 + 31 + // Put all the merged commits info int the mail body if this is a merge 32 + $merges_caption = ''; 33 + // TODO: Make this limit configurable after T6030 34 + $limit = 50; 35 + $commit = $this->getObject(); 36 + 37 + try { 38 + $merges = DiffusionPathChange::newFromConduit( 39 + id(new ConduitCall('diffusion.mergedcommitsquery', array( 40 + 'commit' => $commit->getCommitIdentifier(), 41 + 'limit' => $limit + 1, 42 + 'callsign' => $commit->getRepository()->getCallsign(), 43 + ))) 44 + ->setUser($this->getViewer()) 45 + ->execute()); 46 + 47 + if (count($merges) > $limit) { 48 + $merges = array_slice($merges, 0, $limit); 49 + $merges_caption = 50 + pht("This commit merges more than %d changes. Only the first ". 51 + "%d are shown.\n", $limit, $limit); 52 + } 53 + 54 + if ($merges) { 55 + $merge_commits = array(); 56 + foreach ($merges as $merge) { 57 + $merge_commits[] = $merge->getAuthorName(). 58 + ': '. 59 + $merge->getSummary(); 60 + } 61 + $body->addTextSection( 62 + pht('MERGED COMMITS'), 63 + $merges_caption.implode("\n", $merge_commits)); 64 + } 65 + } catch (ConduitException $ex) { 66 + // Log the exception into the email body 67 + $body->addTextSection( 68 + pht('MERGED COMMITS'), 69 + pht('Error generating merged commits: ').$ex->getMessage()); 70 + } 71 + 72 + } 73 + 74 + }
+38
src/applications/repository/customfield/PhabricatorCommitRepositoryField.php
··· 1 + <?php 2 + 3 + final class PhabricatorCommitRepositoryField 4 + extends PhabricatorCommitCustomField { 5 + 6 + public function getFieldKey() { 7 + return 'diffusion:repository'; 8 + } 9 + 10 + public function getFieldName() { 11 + return pht('Repository'); 12 + } 13 + 14 + public function getFieldDescription() { 15 + return pht('Shows repository in email.'); 16 + } 17 + 18 + public function shouldDisableByDefault() { 19 + return true; 20 + } 21 + 22 + public function shouldAppearInTransactionMail() { 23 + return true; 24 + } 25 + 26 + public function updateTransactionMailBody( 27 + PhabricatorMetaMTAMailBody $body, 28 + PhabricatorApplicationTransactionEditor $editor, 29 + array $xactions) { 30 + 31 + $repository = $this->getObject()->getRepository(); 32 + 33 + $body->addTextSection( 34 + pht('REPOSITORY'), 35 + $repository->getMonogram().' '.$repository->getName()); 36 + } 37 + 38 + }
+13 -4
src/applications/repository/customfield/PhabricatorCommitTagsField.php
··· 7 7 return 'diffusion:tags'; 8 8 } 9 9 10 - public function shouldAppearInApplicationTransactions() { 10 + public function getFieldName() { 11 + return pht('Tags'); 12 + } 13 + 14 + public function getFieldDescription() { 15 + return pht('Shows commit tags in email.'); 16 + } 17 + 18 + public function shouldAppearInTransactionMail() { 11 19 return true; 12 20 } 13 21 14 - public function buildApplicationTransactionMailBody( 15 - PhabricatorApplicationTransaction $xaction, 16 - PhabricatorMetaMTAMailBody $body) { 22 + public function updateTransactionMailBody( 23 + PhabricatorMetaMTAMailBody $body, 24 + PhabricatorApplicationTransactionEditor $editor, 25 + array $xactions) { 17 26 18 27 $params = array( 19 28 'commit' => $this->getObject()->getCommitIdentifier(),
+8
src/applications/repository/editor/PhabricatorRepositoryEditor.php
··· 43 43 $types[] = PhabricatorRepositoryTransaction::TYPE_SERVICE; 44 44 $types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE; 45 45 $types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES; 46 + $types[] = PhabricatorRepositoryTransaction::TYPE_STAGING_URI; 46 47 47 48 $types[] = PhabricatorTransactions::TYPE_EDGE; 48 49 $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; ··· 104 105 return $object->getSymbolLanguages(); 105 106 case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: 106 107 return $object->getSymbolSources(); 108 + case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: 109 + return $object->getDetail('staging-uri'); 107 110 } 108 111 } 109 112 ··· 139 142 case PhabricatorRepositoryTransaction::TYPE_SERVICE: 140 143 case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: 141 144 case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: 145 + case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: 142 146 return $xaction->getNewValue(); 143 147 case PhabricatorRepositoryTransaction::TYPE_NOTIFY: 144 148 case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: ··· 219 223 case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: 220 224 $object->setDetail('symbol-sources', $xaction->getNewValue()); 221 225 return; 226 + case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: 227 + $object->setDetail('staging-uri', $xaction->getNewValue()); 228 + return; 222 229 case PhabricatorRepositoryTransaction::TYPE_ENCODING: 223 230 // Make sure the encoding is valid by converting to UTF-8. This tests 224 231 // that the user has mbstring installed, and also that they didn't type ··· 330 337 case PhabricatorRepositoryTransaction::TYPE_SERVICE: 331 338 case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: 332 339 case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: 340 + case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: 333 341 PhabricatorPolicyFilter::requireCapability( 334 342 $this->requireActor(), 335 343 $object,
+2 -2
src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php
··· 241 241 '**NOTE**: This script will queue tasks to reparse the data. Once the '. 242 242 'tasks have been queued, you need to run Taskmaster daemons to '. 243 243 'execute them.'."\n\n". 244 - "QUEUEING TASKS (%d Commits):", 245 - number_format(count($commits)))); 244 + "QUEUEING TASKS (%s Commits):", 245 + new PhutilNumber(count($commits)))); 246 246 } 247 247 248 248 $progress = new PhutilConsoleProgressBar();
+22
src/applications/repository/storage/PhabricatorRepository.php
··· 135 135 'isActive' => $this->isTracked(), 136 136 'isHosted' => $this->isHosted(), 137 137 'isImporting' => $this->isImporting(), 138 + 'encoding' => $this->getDetail('encoding'), 139 + 'staging' => array( 140 + 'supported' => $this->supportsStaging(), 141 + 'prefix' => 'phabricator', 142 + 'uri' => $this->getStagingURI(), 143 + ), 138 144 ); 139 145 } 140 146 ··· 1793 1799 1794 1800 public function getSymbolLanguages() { 1795 1801 return $this->getDetail('symbol-languages', array()); 1802 + } 1803 + 1804 + 1805 + /* -( Staging )-------------------------------------------------------------*/ 1806 + 1807 + 1808 + public function supportsStaging() { 1809 + return $this->isGit(); 1810 + } 1811 + 1812 + 1813 + public function getStagingURI() { 1814 + if (!$this->supportsStaging()) { 1815 + return null; 1816 + } 1817 + return $this->getDetail('staging-uri', null); 1796 1818 } 1797 1819 1798 1820
+1 -3
src/applications/repository/storage/PhabricatorRepositoryCommit.php
··· 349 349 350 350 351 351 public function getCustomFieldSpecificationForRole($role) { 352 - // TODO: We could make this configurable eventually, but just use the 353 - // defaults for now. 354 - return array(); 352 + return PhabricatorEnv::getEnvConfig('diffusion.fields'); 355 353 } 356 354 357 355 public function getCustomFieldBaseClass() {
+23 -2
src/applications/repository/storage/PhabricatorRepositoryTransaction.php
··· 27 27 const TYPE_SERVICE = 'repo:service'; 28 28 const TYPE_SYMBOLS_SOURCES = 'repo:symbol-source'; 29 29 const TYPE_SYMBOLS_LANGUAGE = 'repo:symbol-language'; 30 + const TYPE_STAGING_URI = 'repo:staging-uri'; 30 31 31 32 // TODO: Clean up these legacy transaction types. 32 33 const TYPE_SSH_LOGIN = 'repo:ssh-login'; ··· 412 413 413 414 case self::TYPE_SYMBOLS_LANGUAGE: 414 415 return pht('%s changed indexed languages from %s to %s.', 416 + $this->renderHandleLink($author_phid), 417 + $old ? implode(', ', $old) : pht('Any'), 418 + $new ? implode(', ', $new) : pht('Any')); 419 + 420 + case self::TYPE_STAGING_URI: 421 + if (!$old) { 422 + return pht( 423 + '%s set "%s" as the staging area for this repository.', 415 424 $this->renderHandleLink($author_phid), 416 - $old ? implode(', ', $old) : pht('Any'), 417 - $new ? implode(', ', $new) : pht('Any')); 425 + $new); 426 + } else if (!$new) { 427 + return pht( 428 + '%s removed "%s" as the staging area for this repository.', 429 + $this->renderHandleLink($author_phid), 430 + $old); 431 + } else { 432 + return pht( 433 + '%s changed the staging area for this repository from '. 434 + '"%s" to "%s".', 435 + $this->renderHandleLink($author_phid), 436 + $old, 437 + $new); 438 + } 418 439 } 419 440 420 441 return parent::getTitle();
-108
src/applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php
··· 1 - <?php 2 - 3 - final class PhabricatorOwnersPackagePathValidator { 4 - 5 - /* 6 - * If a file/directory was moved the paths in owners package become stale. 7 - * This method updates the stale paths in the owners packages to their new 8 - * paths. 9 - */ 10 - public static function updateOwnersPackagePaths( 11 - PhabricatorRepositoryCommit $commit, 12 - PhabricatorUser $actor) { 13 - 14 - $repository = id(new PhabricatorRepositoryQuery()) 15 - ->setViewer($actor) 16 - ->withIDs(array($commit->getRepositoryID())) 17 - ->executeOne(); 18 - if (!$repository) { 19 - return; 20 - } 21 - $changes = self::loadDiffusionChangesForCommit( 22 - $repository, 23 - $commit, 24 - $actor); 25 - 26 - if (!$changes) { 27 - return; 28 - } 29 - 30 - $move_map = array(); 31 - foreach ($changes as $change) { 32 - if ($change->getChangeType() == DifferentialChangeType::TYPE_MOVE_HERE) { 33 - $from_path = '/'.$change->getTargetPath(); 34 - $to_path = '/'.$change->getPath(); 35 - if ($change->getFileType() == DifferentialChangeType::FILE_DIRECTORY) { 36 - $to_path = $to_path.'/'; 37 - $from_path = $from_path.'/'; 38 - } 39 - $move_map[$from_path] = $to_path; 40 - } 41 - } 42 - 43 - if ($move_map) { 44 - self::updateAffectedPackages($repository, $move_map); 45 - } 46 - } 47 - 48 - private static function updateAffectedPackages($repository, array $move_map) { 49 - $paths = array_keys($move_map); 50 - if ($paths) { 51 - $packages = PhabricatorOwnersPackage::loadAffectedPackages($repository, 52 - $paths); 53 - foreach ($packages as $package) { 54 - self::updatePackagePaths($package, $move_map); 55 - } 56 - } 57 - } 58 - 59 - private static function updatePackagePaths($package, array $move_map) { 60 - $paths = array_keys($move_map); 61 - $pkg_paths = $package->loadPaths(); 62 - $new_paths = array(); 63 - foreach ($pkg_paths as $pkg_path) { 64 - $path_changed = false; 65 - 66 - foreach ($paths as $old_path) { 67 - if (strncmp($pkg_path->getPath(), $old_path, strlen($old_path)) === 0) { 68 - $new_paths[] = array ( 69 - 'packageID' => $package->getID(), 70 - 'repositoryPHID' => $pkg_path->getRepositoryPHID(), 71 - 'path' => str_replace($pkg_path->getPath(), $old_path, 72 - $move_map[$old_path]), 73 - ); 74 - $path_changed = true; 75 - } 76 - } 77 - 78 - if (!$path_changed) { 79 - $new_paths[] = array ( 80 - 'packageID' => $package->getID(), 81 - 'repositoryPHID' => $pkg_path->getRepositoryPHID(), 82 - 'path' => $pkg_path->getPath(), 83 - ); 84 - } 85 - } 86 - 87 - if ($new_paths) { 88 - $package->attachOldPrimaryOwnerPHID($package->getPrimaryOwnerPHID()); 89 - $package->attachUnsavedPaths($new_paths); 90 - $package->save(); // save the changes and notify the owners. 91 - } 92 - } 93 - 94 - private static function loadDiffusionChangesForCommit( 95 - PhabricatorRepository $repository, 96 - PhabricatorRepositoryCommit $commit, 97 - PhabricatorUser $actor) { 98 - $data = array( 99 - 'user' => $actor, 100 - 'repository' => $repository, 101 - 'commit' => $commit->getCommitIdentifier(), 102 - ); 103 - $drequest = DiffusionRequest::newFromDictionary($data); 104 - $change_query = 105 - DiffusionPathChangeQuery::newFromDiffusionRequest($drequest); 106 - return $change_query->loadChanges(); 107 - } 108 - }
-3
src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php
··· 96 96 id(new PhabricatorSearchIndexer()) 97 97 ->queueDocumentForIndexing($commit->getPHID()); 98 98 99 - PhabricatorOwnersPackagePathValidator::updateOwnersPackagePaths( 100 - $commit, 101 - PhabricatorUser::getOmnipotentUser()); 102 99 if ($this->shouldQueueFollowupTasks()) { 103 100 $this->queueTask( 104 101 'PhabricatorRepositoryCommitOwnersWorker',
-4
src/docs/user/userguide/arcanist_new_project.diviner
··· 49 49 See below for details about path resolution, or see 50 50 @{article:libphutil Libraries User Guide} for a general introduction to 51 51 libphutil libraries. 52 - - **project.name**: name an "Arcanist Project" to associate this working 53 - copy (Git, Mercurial) or directory (SVN) with. Previously, this was a 54 - required option, but `arc` can now usually operate without it in Git and 55 - Mercurial. This option was previously called `project_id`. 56 52 - **https.cabundle**: specifies the path to an alternate certificate bundle 57 53 for use when making HTTPS connections. 58 54 - **lint.engine**: the name of a subclass of
-3
src/docs/user/userguide/arcanist_quick_start.diviner
··· 44 44 yourproject/ $ $EDITOR .arcconfig 45 45 yourproject/ $ cat .arcconfig 46 46 { 47 - "project.name" : "yourprojectname", 48 47 "phabricator.uri" : "https://phabricator.example.com/" 49 48 } 50 - 51 - Set `project.name` to a string that identifies the project. 52 49 53 50 Set `phabricator.uri` to the URI for your Phabricator install (where `arc` 54 51 should send changes to).
+1 -2
src/docs/user/userguide/libraries.diviner
··· 83 83 `libcustom/.arcconfig`: 84 84 85 85 { 86 - "project.name" : "libcustom", 87 - "load" : [ 86 + "load": [ 88 87 "phabricator/src/" 89 88 ] 90 89 }
+8 -14
src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php
··· 12 12 $storage_value = $request->getStr('value'); 13 13 14 14 $in_value = phutil_json_decode($storage_value); 15 - if (!is_array($in_value)) { 16 - $in_value = array(); 17 - } 18 15 19 16 // When we submit from JS, we submit a list (since maps are not guaranteed 20 17 // to retain order). Convert it into a map for storage (since it's far more ··· 37 34 $field_spec = PhabricatorEnv::getEnvConfig($option->getKey()); 38 35 } 39 36 40 - // Get all of the fields (including disabled fields) by querying for them 41 - // with a faux spec where no fields are disabled. 42 - $faux_spec = $field_spec; 43 - foreach ($faux_spec as $key => $spec) { 44 - unset($faux_spec[$key]['disabled']); 45 - } 46 - 47 37 // TODO: We might need to build a real object here eventually. 48 38 $faux_object = null; 49 39 50 40 $fields = PhabricatorCustomField::buildFieldList( 51 41 $field_base_class, 52 - $faux_spec, 53 - $faux_object); 42 + $field_spec, 43 + $faux_object, 44 + array( 45 + 'withDisabled' => true, 46 + )); 54 47 55 48 $list_id = celerity_generate_unique_node_id(); 56 49 $input_id = celerity_generate_unique_node_id(); ··· 66 59 ->addAttribute($field->getFieldDescription()) 67 60 ->setHeader($field->getFieldName()); 68 61 69 - $is_disabled = !empty($field_spec[$key]['disabled']); 62 + $spec = idx($field_spec, $key, array()); 63 + $is_disabled = idx($spec, 'disabled', $field->shouldDisableByDefault()); 70 64 71 65 $disabled_item = clone $item; 72 66 $enabled_item = clone $item; ··· 113 107 'id' => $input_id, 114 108 'type' => 'hidden', 115 109 'name' => 'value', 116 - 'value' => json_encode($display_value), 110 + 'value' => '', 117 111 )); 118 112 119 113 Javelin::initBehavior(
+22 -24
src/infrastructure/customfield/field/PhabricatorCustomField.php
··· 105 105 /** 106 106 * @task apps 107 107 */ 108 - public static function buildFieldList($base_class, array $spec, $object) { 108 + public static function buildFieldList( 109 + $base_class, 110 + array $spec, 111 + $object, 112 + array $options = array()) { 113 + 114 + PhutilTypeSpec::checkMap( 115 + $options, 116 + array( 117 + 'withDisabled' => 'optional bool', 118 + )); 119 + 109 120 $field_objects = id(new PhutilSymbolLoader()) 110 121 ->setAncestorClass($base_class) 111 122 ->loadObjects(); ··· 135 146 136 147 $fields = array_select_keys($fields, array_keys($spec)) + $fields; 137 148 138 - foreach ($spec as $key => $config) { 139 - if (empty($fields[$key])) { 140 - continue; 141 - } 142 - if (!empty($config['disabled'])) { 143 - if ($fields[$key]->canDisableField()) { 144 - unset($fields[$key]); 149 + if (empty($options['withDisabled'])) { 150 + foreach ($fields as $key => $field) { 151 + $config = idx($spec, $key, array()) + array( 152 + 'disabled' => $field->shouldDisableByDefault(), 153 + ); 154 + 155 + if (!empty($config['disabled'])) { 156 + if ($field->canDisableField()) { 157 + unset($fields[$key]); 158 + } 145 159 } 146 160 } 147 161 } ··· 1053 1067 return $this->proxy->shouldHideInApplicationTransactions($xaction); 1054 1068 } 1055 1069 return false; 1056 - } 1057 - 1058 - /** 1059 - * TODO: this is only used by Diffusion right now and everything is completely 1060 - * faked since Diffusion doesn't use ApplicationTransactions yet. This should 1061 - * get fleshed out as we have more use cases. 1062 - * 1063 - * @task appxaction 1064 - */ 1065 - public function buildApplicationTransactionMailBody( 1066 - PhabricatorApplicationTransaction $xaction, 1067 - PhabricatorMetaMTAMailBody $body) { 1068 - if ($this->proxy) { 1069 - return $this->proxy->buildApplicationTransactionMailBody($xaction, $body); 1070 - } 1071 - return; 1072 1070 } 1073 1071 1074 1072
+24
src/infrastructure/diff/PhabricatorInlineCommentController.php
··· 15 15 abstract protected function saveComment( 16 16 PhabricatorInlineCommentInterface $inline); 17 17 18 + protected function hideComments(array $ids) { 19 + throw new PhutilMethodNotImplementedException(); 20 + } 21 + 22 + protected function showComments(array $ids) { 23 + throw new PhutilMethodNotImplementedException(); 24 + } 25 + 18 26 private $changesetID; 19 27 private $isNewFile; 20 28 private $isOnRight; ··· 84 92 85 93 $op = $this->getOperation(); 86 94 switch ($op) { 95 + case 'hide': 96 + case 'show': 97 + if (!$request->validateCSRF()) { 98 + return new Aphront404Response(); 99 + } 100 + 101 + $ids = $request->getStrList('ids'); 102 + if ($ids) { 103 + if ($op == 'hide') { 104 + $this->hideComments($ids); 105 + } else { 106 + $this->showComments($ids); 107 + } 108 + } 109 + 110 + return id(new AphrontAjaxResponse())->setContent(array()); 87 111 case 'done': 88 112 if (!$request->validateCSRF()) { 89 113 return new Aphront404Response();
+3
src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php
··· 57 57 public function setIsGhost($is_ghost); 58 58 public function getIsGhost(); 59 59 60 + public function supportsHiding(); 61 + public function isHidden(); 62 + 60 63 }
+18
src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php
··· 18 18 return $this; 19 19 } 20 20 21 + public function isHidden() { 22 + return $this->inlineComment->isHidden(); 23 + } 24 + 21 25 public function setHandles(array $handles) { 22 26 assert_instances_of($handles, 'PhabricatorObjectHandle'); 23 27 $this->handles = $handles; ··· 192 196 if (!$this->preview) { 193 197 $nextprev = new PHUIButtonBarView(); 194 198 $nextprev->addClass('mml'); 199 + 200 + 195 201 $up = id(new PHUIButtonView()) 196 202 ->setTag('a') 197 203 ->setColor(PHUIButtonView::SIMPLE) ··· 207 213 ->setIconFont('fa-chevron-down') 208 214 ->addSigil('differential-inline-next') 209 215 ->setMustCapture(true); 216 + 217 + $hide = id(new PHUIButtonView()) 218 + ->setTag('a') 219 + ->setColor(PHUIButtonView::SIMPLE) 220 + ->setTooltip(pht('Hide Comment')) 221 + ->setIconFont('fa-times') 222 + ->addSigil('hide-inline') 223 + ->setMustCapture(true); 224 + 225 + if ($viewer_phid && $inline->getID() && $inline->supportsHiding()) { 226 + $nextprev->addButton($hide); 227 + } 210 228 211 229 $nextprev->addButton($up); 212 230 $nextprev->addButton($down);
+10
src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php
··· 23 23 protected function getRowAttributes() { 24 24 // TODO: This is semantic information used by the JS when placing comments 25 25 // and using keyboard navigation; we should move it out of class names. 26 + 27 + $style = null; 28 + foreach ($this->getInlineViews() as $view) { 29 + if ($view->isHidden()) { 30 + $style = 'display: none'; 31 + } 32 + } 33 + 26 34 return array( 27 35 'class' => 'inline', 36 + 'sigil' => 'inline-row', 37 + 'style' => $style, 28 38 ); 29 39 } 30 40
+4
src/infrastructure/diff/view/PHUIDiffInlineCommentView.php
··· 17 17 return null; 18 18 } 19 19 20 + public function isHidden() { 21 + return false; 22 + } 23 + 20 24 }
+1 -1
src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php
··· 28 28 phutil_tag('td', $attrs, $inline), 29 29 ); 30 30 31 - return phutil_tag('tr', $this->getRowAttributes(), $cells); 31 + return javelin_tag('tr', $this->getRowAttributes(), $cells); 32 32 } 33 33 34 34 }
+27
src/infrastructure/diff/view/PHUIDiffRevealIconView.php
··· 1 + <?php 2 + 3 + final class PHUIDiffRevealIconView extends AphrontView { 4 + 5 + public function render() { 6 + $icon = id(new PHUIIconView()) 7 + ->setIconFont('fa-comment') 8 + ->addSigil('has-tooltip') 9 + ->setMetadata( 10 + array( 11 + 'tip' => pht('Show Hidden Comments'), 12 + 'align' => 'E', 13 + 'size' => 275, 14 + )); 15 + 16 + return javelin_tag( 17 + 'a', 18 + array( 19 + 'href' => '#', 20 + 'class' => 'reveal-inlines', 21 + 'sigil' => 'reveal-inlines', 22 + 'mustcapture' => true, 23 + ), 24 + $icon); 25 + } 26 + 27 + }
+1 -1
src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php
··· 68 68 phutil_tag('td', $right_attrs, $right_side), 69 69 ); 70 70 71 - return phutil_tag('tr', $this->getRowAttributes(), $cells); 71 + return javelin_tag('tr', $this->getRowAttributes(), $cells); 72 72 } 73 73 74 74 }
+18
src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
··· 1078 1078 'Are you absolutely certain you want to destroy these objects?', 1079 1079 ), 1080 1080 1081 + '%s added %s owner(s): %s.' => array( 1082 + array( 1083 + '%s added an owner: %3$s.', 1084 + '%s added owners: %3$s.', 1085 + ), 1086 + ), 1087 + 1088 + '%s removed %s owner(s): %s.' => array( 1089 + array( 1090 + '%s removed an owner: %3$s.', 1091 + '%s removed owners: %3$s.', 1092 + ), 1093 + ), 1094 + 1095 + '%s changed %s package owner(s), added %s: %s; removed %s: %s.' => array( 1096 + '%s changed package owners, added: %4$s; removed: %6$s.', 1097 + ), 1098 + 1081 1099 ); 1082 1100 } 1083 1101
+6
src/infrastructure/time/PhabricatorTime.php
··· 72 72 return $today; 73 73 } 74 74 75 + public static function getDateTimeFromEpoch($epoch, PhabricatorUser $viewer) { 76 + $datetime = new DateTime('@'.$epoch); 77 + $datetime->setTimeZone($viewer->getTimeZone()); 78 + return $datetime; 79 + } 80 + 75 81 }
+33 -104
src/view/form/control/AphrontFormDateControl.php
··· 5 5 private $initialTime; 6 6 private $zone; 7 7 8 - private $valueDay; 9 - private $valueMonth; 10 - private $valueYear; 8 + private $valueDate; 11 9 private $valueTime; 12 10 private $allowNull; 13 11 private $continueOnInvalidDate = false; ··· 41 39 } 42 40 43 41 public function readValueFromRequest(AphrontRequest $request) { 44 - $day = $request->getInt($this->getDayInputName()); 45 - $month = $request->getInt($this->getMonthInputName()); 46 - $year = $request->getInt($this->getYearInputName()); 42 + $date = $request->getStr($this->getDateInputName()); 47 43 $time = $request->getStr($this->getTimeInputName()); 48 44 $enabled = $request->getBool($this->getCheckboxInputName()); 49 45 ··· 55 51 56 52 $err = $this->getError(); 57 53 58 - if ($day || $month || $year || $time) { 59 - $this->valueDay = $day; 60 - $this->valueMonth = $month; 61 - $this->valueYear = $year; 54 + if ($date || $time) { 55 + $this->valueDate = $date; 62 56 $this->valueTime = $time; 63 57 64 58 // Assume invalid. ··· 67 61 $zone = $this->getTimezone(); 68 62 69 63 try { 70 - $date = new DateTime("{$year}-{$month}-{$day} {$time}", $zone); 71 - $value = $date->format('U'); 64 + $datetime = new DateTime("{$date} {$time}", $zone); 65 + $value = $datetime->format('U'); 72 66 } catch (Exception $ex) { 73 67 $value = null; 74 68 } ··· 100 94 public function setValue($epoch) { 101 95 if ($epoch instanceof AphrontFormDateControlValue) { 102 96 $this->continueOnInvalidDate = true; 103 - $this->valueYear = $epoch->getValueYear(); 104 - $this->valueMonth = $epoch->getValueMonth(); 105 - $this->valueDay = $epoch->getValueDay(); 97 + $this->valueDate = $epoch->getValueDate(); 106 98 $this->valueTime = $epoch->getValueTime(); 107 99 $this->allowNull = $epoch->getOptional(); 108 100 $this->isDisabled = $epoch->isDisabled(); ··· 119 111 $readable = $this->formatTime($epoch, 'Y!m!d!g:i A'); 120 112 $readable = explode('!', $readable, 4); 121 113 122 - $this->valueYear = $readable[0]; 123 - $this->valueMonth = $readable[1]; 124 - $this->valueDay = $readable[2]; 114 + $year = $readable[0]; 115 + $month = $readable[1]; 116 + $day = $readable[2]; 117 + 118 + $this->valueDate = $month.'/'.$day.'/'.$year; 125 119 $this->valueTime = $readable[3]; 126 120 127 121 return $result; 128 122 } 129 123 130 - private function getMinYear() { 131 - $cur_year = $this->formatTime( 132 - time(), 133 - 'Y'); 134 - $val_year = $this->getYearInputValue(); 135 - 136 - return min($cur_year, $val_year) - 3; 137 - } 138 - 139 - private function getMaxYear() { 140 - $cur_year = $this->formatTime( 141 - time(), 142 - 'Y'); 143 - $val_year = $this->getYearInputValue(); 144 - 145 - return max($cur_year, $val_year) + 3; 146 - } 147 - 148 - private function getDayInputValue() { 149 - return $this->valueDay; 150 - } 151 - 152 - private function getMonthInputValue() { 153 - return $this->valueMonth; 154 - } 155 - 156 - private function getYearInputValue() { 157 - return $this->valueYear; 124 + private function getDateInputValue() { 125 + return $this->valueDate; 158 126 } 159 127 160 128 private function getTimeInputValue() { ··· 168 136 $fmt); 169 137 } 170 138 171 - private function getDayInputName() { 139 + private function getDateInputName() { 172 140 return $this->getName().'_d'; 173 141 } 174 142 175 - private function getMonthInputName() { 176 - return $this->getName().'_m'; 177 - } 178 - 179 - private function getYearInputName() { 180 - return $this->getName().'_y'; 181 - } 182 - 183 143 private function getTimeInputName() { 184 144 return $this->getName().'_t'; 185 145 } ··· 202 162 $disabled = 'disabled'; 203 163 } 204 164 205 - $min_year = $this->getMinYear(); 206 - $max_year = $this->getMaxYear(); 207 - 208 - $days = range(1, 31); 209 - $days = array_fuse($days); 210 - 211 - $months = array( 212 - 1 => pht('Jan'), 213 - 2 => pht('Feb'), 214 - 3 => pht('Mar'), 215 - 4 => pht('Apr'), 216 - 5 => pht('May'), 217 - 6 => pht('Jun'), 218 - 7 => pht('Jul'), 219 - 8 => pht('Aug'), 220 - 9 => pht('Sep'), 221 - 10 => pht('Oct'), 222 - 11 => pht('Nov'), 223 - 12 => pht('Dec'), 224 - ); 225 - 226 165 $checkbox = null; 227 166 if ($this->allowNull) { 228 167 $checkbox = javelin_tag( ··· 237 176 )); 238 177 } 239 178 240 - $years = range($this->getMinYear(), $this->getMaxYear()); 241 - $years = array_fuse($years); 242 - 243 - $days_sel = AphrontFormSelectControl::renderSelectTag( 244 - $this->getDayInputValue(), 245 - $days, 246 - array( 247 - 'name' => $this->getDayInputName(), 248 - 'sigil' => 'day-input', 249 - )); 250 - 251 - $months_sel = AphrontFormSelectControl::renderSelectTag( 252 - $this->getMonthInputValue(), 253 - $months, 179 + $date_sel = javelin_tag( 180 + 'input', 254 181 array( 255 - 'name' => $this->getMonthInputName(), 256 - 'sigil' => 'month-input', 257 - )); 182 + 'autocomplete' => 'off', 183 + 'name' => $this->getDateInputName(), 184 + 'sigil' => 'date-input', 185 + 'value' => $this->getDateInputValue(), 186 + 'type' => 'text', 187 + 'class' => 'aphront-form-date-input', 188 + ), 189 + ''); 258 190 259 - $years_sel = AphrontFormSelectControl::renderSelectTag( 260 - $this->getYearInputValue(), 261 - $years, 191 + $date_div = javelin_tag( 192 + 'div', 262 193 array( 263 - 'name' => $this->getYearInputName(), 264 - 'sigil' => 'year-input', 265 - )); 194 + 'class' => 'aphront-form-date-input-container', 195 + ), 196 + $date_sel); 266 197 267 198 $cicon = id(new PHUIIconView()) 268 199 ->setIconFont('fa-calendar'); ··· 294 225 'sigil' => 'time-input', 295 226 'value' => $this->getTimeInputValue(), 296 227 'type' => 'text', 297 - 'class' => 'aphront-form-date-time-input', 228 + 'class' => 'aphront-form-time-input', 298 229 ), 299 230 ''); 300 231 ··· 302 233 'div', 303 234 array( 304 235 'id' => $time_id, 305 - 'class' => 'aphront-form-date-time-input-container', 236 + 'class' => 'aphront-form-time-input-container', 306 237 ), 307 238 $time_sel); 308 239 ··· 329 260 ), 330 261 array( 331 262 $checkbox, 332 - $days_sel, 333 - $months_sel, 334 - $years_sel, 263 + $date_div, 335 264 $cal_icon, 336 265 $time_div, 337 266 ));
+30 -43
src/view/form/control/AphrontFormDateControlValue.php
··· 2 2 3 3 final class AphrontFormDateControlValue extends Phobject { 4 4 5 - private $valueDay; 6 - private $valueMonth; 7 - private $valueYear; 5 + private $valueDate; 8 6 private $valueTime; 9 7 private $valueEnabled; 10 8 ··· 12 10 private $zone; 13 11 private $optional; 14 12 15 - public function getValueDay() { 16 - return $this->valueDay; 17 - } 18 - 19 - public function getValueMonth() { 20 - return $this->valueMonth; 21 - } 22 - 23 - public function getValueYear() { 24 - return $this->valueYear; 13 + public function getValueDate() { 14 + return $this->valueDate; 25 15 } 26 16 27 17 public function getValueTime() { ··· 36 26 } 37 27 38 28 public function isEmpty() { 39 - if ($this->valueDay) { 40 - return false; 41 - } 42 - 43 - if ($this->valueMonth) { 44 - return false; 45 - } 46 - 47 - if ($this->valueYear) { 29 + if ($this->valueDate) { 48 30 return false; 49 31 } 50 32 ··· 83 65 84 66 $value = new AphrontFormDateControlValue(); 85 67 $value->viewer = $viewer; 86 - $value->valueYear = $year; 87 - $value->valueMonth = $month; 88 - $value->valueDay = $day; 68 + $value->valueDate = $month.'/'.$day.'/'.$year; 89 69 $value->valueTime = coalesce($time, '12:00 AM'); 90 70 $value->valueEnabled = $enabled; 91 71 ··· 95 75 public static function newFromRequest(AphrontRequest $request, $key) { 96 76 $value = new AphrontFormDateControlValue(); 97 77 $value->viewer = $request->getViewer(); 98 - 99 - $value->valueDay = $request->getInt($key.'_d'); 100 - $value->valueMonth = $request->getInt($key.'_m'); 101 - $value->valueYear = $request->getInt($key.'_y'); 78 + $value->valueDate = $request->getStr($key.'_d'); 102 79 $value->valueTime = $request->getStr($key.'_t'); 103 80 $value->valueEnabled = $request->getStr($key.'_e'); 104 81 ··· 111 88 $readable = $value->formatTime($epoch, 'Y!m!d!g:i A'); 112 89 $readable = explode('!', $readable, 4); 113 90 114 - $value->valueYear = $readable[0]; 115 - $value->valueMonth = $readable[1]; 116 - $value->valueDay = $readable[2]; 91 + $year = $readable[0]; 92 + $month = $readable[1]; 93 + $day = $readable[2]; 94 + 95 + $value->valueDate = $month.'/'.$day.'/'.$year; 117 96 $value->valueTime = $readable[3]; 97 + 118 98 119 99 return $value; 120 100 } ··· 125 105 $value = new AphrontFormDateControlValue(); 126 106 $value->viewer = $viewer; 127 107 128 - $value->valueYear = idx($dictionary, 'y'); 129 - $value->valueMonth = idx($dictionary, 'm'); 130 - $value->valueDay = idx($dictionary, 'd'); 108 + $value->valueDate = idx($dictionary, 'd'); 131 109 $value->valueTime = idx($dictionary, 't'); 132 110 $value->valueEnabled = idx($dictionary, 'e'); 133 111 ··· 149 127 150 128 public function getDictionary() { 151 129 return array( 152 - 'y' => $this->valueYear, 153 - 'm' => $this->valueMonth, 154 - 'd' => $this->valueDay, 130 + 'd' => $this->valueDate, 155 131 't' => $this->valueTime, 156 132 'e' => $this->valueEnabled, 157 133 ); ··· 176 152 return null; 177 153 } 178 154 179 - $year = $this->valueYear; 180 - $month = $this->valueMonth; 181 - $day = $this->valueDay; 155 + $date = $this->valueDate; 182 156 $time = $this->valueTime; 183 157 $zone = $this->getTimezone(); 184 158 ··· 203 177 } 204 178 205 179 try { 206 - $date = new DateTime("{$year}-{$month}-{$day} {$time}", $zone); 207 - $value = $date->format('U'); 180 + $datetime = new DateTime("{$date} {$time}", $zone); 181 + $value = $datetime->format('U'); 208 182 } catch (Exception $ex) { 209 183 $value = null; 210 184 } 211 185 return $value; 186 + } 187 + 188 + public function getDateTime() { 189 + $epoch = $this->getEpoch(); 190 + $date = null; 191 + 192 + if ($epoch) { 193 + $zone = $this->getTimezone(); 194 + $date = new DateTime('@'.$epoch); 195 + $date->setTimeZone($zone); 196 + } 197 + 198 + return $date; 212 199 } 213 200 214 201 private function getTimezone() {
+1
src/view/phui/PHUICrumbsView.php
··· 84 84 'class' => implode(' ', $action_classes), 85 85 'sigil' => implode(' ', $action_sigils), 86 86 'style' => $action->getStyle(), 87 + 'meta' => $action->getMetadata(), 87 88 ), 88 89 array( 89 90 $icon,
+12
src/view/phui/PHUIListItemView.php
··· 29 29 private $aural; 30 30 private $profileImage; 31 31 32 + public function setDropdownMenu(PhabricatorActionListView $actions) { 33 + Javelin::initBehavior('phui-dropdown-menu'); 34 + 35 + $this->addSigil('phui-dropdown-menu'); 36 + $this->setMetadata( 37 + array( 38 + 'items' => $actions, 39 + )); 40 + 41 + return $this; 42 + } 43 + 32 44 public function setAural($aural) { 33 45 $this->aural = $aural; 34 46 return $this;
+92 -166
src/view/phui/calendar/PHUICalendarDayView.php
··· 8 8 private $month; 9 9 private $year; 10 10 private $browseURI; 11 + private $query; 11 12 private $events = array(); 12 - private $todayEvents = array(); 13 13 14 14 private $allDayEvents = array(); 15 15 ··· 26 26 return $this->browseURI; 27 27 } 28 28 29 + public function setQuery($query) { 30 + $this->query = $query; 31 + return $this; 32 + } 33 + private function getQuery() { 34 + return $this->query; 35 + } 36 + 29 37 public function __construct( 30 38 $range_start, 31 39 $range_end, ··· 44 52 public function render() { 45 53 require_celerity_resource('phui-calendar-day-css'); 46 54 55 + $viewer = $this->getUser(); 56 + 47 57 $hours = $this->getHoursOfDay(); 48 - $hourly_events = array(); 58 + $js_hours = array(); 59 + $js_today_events = array(); 60 + 61 + foreach ($hours as $hour) { 62 + $js_hours[] = array( 63 + 'hour' => $hour->format('G'), 64 + 'hour_meridian' => $hour->format('g A'), 65 + ); 66 + } 49 67 50 68 $first_event_hour = null; 51 - 69 + $js_today_all_day_events = array(); 52 70 $all_day_events = $this->getAllDayEvents(); 53 - $today_all_day_events = array(); 54 71 55 72 $day_start = $this->getDateTime(); 56 73 $day_end = id(clone $day_start)->modify('+1 day'); ··· 63 80 $all_day_end = $all_day_event->getEpochEnd(); 64 81 65 82 if ($all_day_start < $day_end_epoch && $all_day_end > $day_start_epoch) { 66 - $today_all_day_events[] = $all_day_event; 83 + $js_today_all_day_events[] = array( 84 + 'name' => $all_day_event->getName(), 85 + 'id' => $all_day_event->getEventID(), 86 + 'viewerIsInvited' => $all_day_event->getViewerIsInvited(), 87 + 'uri' => $all_day_event->getURI(), 88 + ); 67 89 } 68 90 } 69 91 70 - foreach ($hours as $hour) { 71 - $current_hour_events = array(); 72 - $hour_start = $hour->format('U'); 73 - $hour_end = id(clone $hour)->modify('+1 hour')->format('U'); 92 + $this->events = msort($this->events, 'getEpochStart'); 93 + $first_event_hour = $this->getDateTime()->setTime(8, 0, 0); 94 + $midnight = $this->getDateTime()->setTime(0, 0, 0); 74 95 75 - foreach ($this->events as $event) { 76 - if ($event->getIsAllDay()) { 77 - continue; 96 + foreach ($this->events as $event) { 97 + if ($event->getIsAllDay()) { 98 + continue; 99 + } 100 + if ($event->getEpochStart() <= $day_end_epoch && 101 + $event->getEpochEnd() > $day_start_epoch) { 102 + 103 + if ($event->getEpochStart() < $midnight->format('U') && 104 + $event->getEpochEnd() > $midnight->format('U')) { 105 + $first_event_hour = clone $midnight; 78 106 } 79 - if (($hour == $day_start && 80 - $event->getEpochStart() <= $hour_start && 81 - $event->getEpochEnd() > $day_start_epoch) || 82 - ($event->getEpochStart() >= $hour_start 83 - && $event->getEpochStart() < $hour_end)) { 84 - $current_hour_events[] = $event; 85 - $this->todayEvents[] = $event; 107 + 108 + if ($event->getEpochStart() < $first_event_hour->format('U') && 109 + $event->getEpochStart() > $midnight->format('U')) { 110 + $first_event_hour = PhabricatorTime::getDateTimeFromEpoch( 111 + $event->getEpochStart(), 112 + $viewer); 113 + $first_event_hour->setTime($first_event_hour->format('h'), 0, 0); 86 114 } 87 - } 88 - foreach ($current_hour_events as $event) { 89 - $day_start_epoch = $this->getDateTime()->format('U'); 115 + 90 116 $event_start = max($event->getEpochStart(), $day_start_epoch); 91 117 $event_end = min($event->getEpochEnd(), $day_end_epoch); 92 118 93 - $top = (($event_start - $hour_start) / ($hour_end - $hour_start)) 94 - * 100; 95 - $top = max(0, $top); 119 + $day_duration = ($day_end_epoch - $first_event_hour->format('U')) / 60; 96 120 97 - $height = (($event_end - $event_start) / ($hour_end - $hour_start)) 98 - * 100; 99 - $height = min(2400, $height); 121 + $top = (($event_start - $first_event_hour->format('U')) 122 + / ($day_end_epoch - $first_event_hour->format('U'))) 123 + * $day_duration; 124 + $top = max(0, $top); 100 125 101 - if ($first_event_hour === null) { 102 - $first_event_hour = $hour; 103 - } 126 + $height = (($event_end - $event_start) 127 + / ($day_end_epoch - $first_event_hour->format('U'))) 128 + * $day_duration; 129 + $height = min($day_duration, $height); 104 130 105 - $hourly_events[$event->getEventID()] = array( 106 - 'hour' => $hour, 107 - 'event' => $event, 131 + $js_today_events[] = array( 132 + 'eventStartEpoch' => $event->getEpochStart(), 133 + 'eventEndEpoch' => $event->getEpochEnd(), 134 + 'eventName' => $event->getName(), 135 + 'eventID' => $event->getEventID(), 136 + 'viewerIsInvited' => $event->getViewerIsInvited(), 137 + 'uri' => $event->getURI(), 108 138 'offset' => '0', 109 139 'width' => '100%', 110 - 'top' => $top.'%', 111 - 'height' => $height.'%', 140 + 'top' => $top.'px', 141 + 'height' => $height.'px', 142 + 'canEdit' => $event->getCanEdit(), 112 143 ); 113 144 } 114 145 } 115 146 116 - $clusters = $this->findTodayClusters(); 117 - foreach ($clusters as $cluster) { 118 - $hourly_events = $this->updateEventsFromCluster( 119 - $cluster, 120 - $hourly_events); 121 - } 122 - 123 - $rows = array(); 124 - 125 - foreach ($hours as $hour) { 126 - $early_hours = array(8); 127 - if ($first_event_hour) { 128 - $early_hours[] = $first_event_hour->format('G'); 129 - } 130 - if ($hour->format('G') < min($early_hours)) { 131 - continue; 132 - } 133 - 134 - $drawn_hourly_events = array(); 135 - $cell_time = phutil_tag( 136 - 'td', 137 - array('class' => 'phui-calendar-day-hour'), 138 - $hour->format('g A')); 139 - 140 - foreach ($hourly_events as $hourly_event) { 141 - if ($hourly_event['hour'] == $hour) { 142 - 143 - $drawn_hourly_events[] = $this->drawEvent( 144 - $hourly_event['event'], 145 - $hourly_event['offset'], 146 - $hourly_event['width'], 147 - $hourly_event['top'], 148 - $hourly_event['height']); 149 - } 150 - } 151 - $cell_event = phutil_tag( 152 - 'td', 153 - array('class' => 'phui-calendar-day-events'), 154 - $drawn_hourly_events); 155 - 156 - $row = phutil_tag( 157 - 'tr', 158 - array(), 159 - array($cell_time, $cell_event)); 160 - 161 - $rows[] = $row; 162 - } 163 - 164 - $table = phutil_tag( 165 - 'table', 166 - array('class' => 'phui-calendar-day-view'), 167 - $rows); 168 - 169 - $all_day_event_box = new PHUIBoxView(); 170 - foreach ($today_all_day_events as $all_day_event) { 171 - $all_day_event_box->appendChild( 172 - $this->drawAllDayEvent($all_day_event)); 173 - } 174 - 175 147 $header = $this->renderDayViewHeader(); 176 148 $sidebar = $this->renderSidebar(); 177 149 $warnings = $this->getQueryRangeWarning(); 178 150 151 + $table_id = celerity_generate_unique_node_id(); 152 + 153 + $table_wrapper = phutil_tag( 154 + 'div', 155 + array( 156 + 'id' => $table_id, 157 + ), 158 + ''); 159 + 160 + Javelin::initBehavior( 161 + 'day-view', 162 + array( 163 + 'year' => $first_event_hour->format('Y'), 164 + 'month' => $first_event_hour->format('m'), 165 + 'day' => $first_event_hour->format('d'), 166 + 'query' => $this->getQuery(), 167 + 'allDayEvents' => $js_today_all_day_events, 168 + 'todayEvents' => $js_today_events, 169 + 'hours' => $js_hours, 170 + 'firstEventHour' => $first_event_hour->format('G'), 171 + 'firstEventHourEpoch' => $first_event_hour->format('U'), 172 + 'tableID' => $table_id, 173 + )); 174 + 179 175 $table_box = id(new PHUIObjectBoxView()) 180 176 ->setHeader($header) 181 - ->appendChild($all_day_event_box) 182 - ->appendChild($table) 177 + ->appendChild($table_wrapper) 183 178 ->setFormErrors($warnings) 184 179 ->setFlush(true); 185 180 ··· 389 384 } 390 385 391 386 return $hourly_events; 392 - } 393 - 394 - private function drawAllDayEvent(AphrontCalendarEventView $event) { 395 - $class = 'day-view-all-day'; 396 - if ($event->getViewerIsInvited()) { 397 - $class = $class.' viewer-invited-day-event'; 398 - } 399 - 400 - $name = phutil_tag( 401 - 'a', 402 - array( 403 - 'class' => $class, 404 - 'href' => $event->getURI(), 405 - ), 406 - $event->getName()); 407 - 408 - $all_day_label = phutil_tag( 409 - 'span', 410 - array( 411 - 'class' => 'phui-calendar-all-day-label', 412 - ), 413 - pht('All Day')); 414 - 415 - $div = phutil_tag( 416 - 'div', 417 - array( 418 - 'class' => 'phui-calendar-day-event', 419 - ), 420 - array( 421 - $all_day_label, 422 - $name, 423 - )); 424 - 425 - return $div; 426 - } 427 - 428 - private function drawEvent( 429 - AphrontCalendarEventView $event, 430 - $offset, 431 - $width, 432 - $top, 433 - $height) { 434 - 435 - $class = 'phui-calendar-day-event-link'; 436 - if ($event->getViewerIsInvited()) { 437 - $class = $class.' viewer-invited-day-event'; 438 - } 439 - 440 - $name = phutil_tag( 441 - 'a', 442 - array( 443 - 'class' => $class, 444 - 'href' => $event->getURI(), 445 - ), 446 - $event->getName()); 447 - 448 - $div = phutil_tag( 449 - 'div', 450 - array( 451 - 'class' => 'phui-calendar-day-event', 452 - 'style' => 'left: '.$offset 453 - .'; width: '.$width 454 - .'; top: '.$top 455 - .'; height: '.$height 456 - .';', 457 - ), 458 - $name); 459 - 460 - return $div; 461 387 } 462 388 463 389 // returns DateTime of each hour in the day
+5 -4
src/view/phui/calendar/PHUICalendarListView.php
··· 150 150 $this->getUser(), 151 151 $event->getEpochEnd())); 152 152 153 + $start_date = $start->getDateTime()->format('m d Y'); 154 + $end_date = $end->getDateTime()->format('m d Y'); 155 + 153 156 if ($event->getIsAllDay()) { 154 - if ($start->getValueDay() == $end->getValueDay()) { 157 + if ($start_date == $end_date) { 155 158 $tip = pht('All day'); 156 159 } else { 157 160 $tip = pht( ··· 160 163 $end->getValueAsFormat('M j, Y')); 161 164 } 162 165 } else { 163 - if ($start->getValueDay() == $end->getValueDay() && 164 - $start->getValueMonth() == $end->getValueMonth() && 165 - $start->getValueYear() == $end->getValueYear()) { 166 + if ($start->getValueDate() == $end->getValueDate()) { 166 167 $tip = pht( 167 168 '%s - %s', 168 169 $start->getValueAsFormat('g:i A'),
+10
webroot/rsrc/css/aphront/table-view.css
··· 219 219 background: {$sh-yellowbackground}; 220 220 } 221 221 222 + .aphront-table-view tr.diff-removed, 223 + .aphront-table-view tr.alt-diff-removed { 224 + background: {$lightred} 225 + } 226 + 227 + .aphront-table-view tr.diff-added, 228 + .aphront-table-view tr.alt-diff-added { 229 + background: {$lightgreen} 230 + } 231 + 222 232 .aphront-table-view tr.no-data td { 223 233 padding: 12px; 224 234 text-align: center;
+18
webroot/rsrc/css/application/differential/phui-inline-comment.css
··· 434 434 border-color: {$lightgreyborder}; 435 435 color: {$lightgreytext}; 436 436 } 437 + 438 + 439 + /* - Hiding Inlines ------------------------------------------------------------ 440 + */ 441 + 442 + .reveal-inlines { 443 + float: left; 444 + margin-left: 4px; 445 + color: {$lightbluetext}; 446 + } 447 + 448 + .reveal-inlines span.phui-icon-view { 449 + color: {$lightbluetext}; 450 + } 451 + 452 + .reveal-inlines:hover span.phui-icon-view { 453 + color: {$darkbluetext}; 454 + }
+4
webroot/rsrc/css/core/z-index.css
··· 32 32 z-index: 2; 33 33 } 34 34 35 + div.phui-calendar-day-event { 36 + z-index: 2; 37 + } 38 + 35 39 .slowvote-above-the-bar { 36 40 z-index: 3; 37 41 }
+19 -2
webroot/rsrc/css/phui/calendar/phui-calendar-day.css
··· 29 29 30 30 .phui-calendar-day-view td { 31 31 position: relative; 32 + cursor: pointer; 33 + } 34 + 35 + .phui-calendar-day-view td:hover { 36 + background: {$lightbluebackground}; 32 37 } 33 38 34 39 .phui-calendar-day-view tr + tr td.phui-calendar-day-events { 35 40 border-top: 1px solid {$lightgreyborder}; 36 41 } 37 42 38 - .phui-calendar-day-view td div.phui-calendar-day-event { 43 + .phui-drag { 44 + opacity: .25; 45 + } 46 + 47 + div.phui-calendar-day-event { 39 48 width: 100%; 40 49 position: absolute; 41 50 top: 0; ··· 43 52 min-height: 30px; 44 53 } 45 54 55 + .can-drag a { 56 + cursor: move; 57 + } 58 + 59 + div.phui-calendar-day-event.all-day { 60 + position: relative; 61 + } 62 + 46 63 .phui-calendar-day-event-link { 47 64 padding: 8px; 48 65 border: 1px solid {$greyborder}; 49 66 background-color: {$darkgreybackground}; 50 - margin: 0 4px; 67 + margin: 0 1px; 51 68 position: absolute; 52 69 left: 0; 53 70 right: 0;
+6 -4
webroot/rsrc/css/phui/phui-form-view.css
··· 334 334 font-size: 16px; 335 335 } 336 336 337 - .aphront-form-date-container .aphront-form-date-time-input-container { 337 + .aphront-form-date-container .aphront-form-time-input-container, 338 + .aphront-form-date-container .aphront-form-date-input-container { 338 339 position: relative; 339 340 display: inline-block; 340 341 width: 7em; 341 342 } 342 343 343 - .aphront-form-date-container input.aphront-form-date-time-input { 344 + .aphront-form-date-container input.aphront-form-time-input, 345 + .aphront-form-date-container input.aphront-form-date-input { 344 346 width: 7em; 345 347 } 346 348 347 - .aphront-form-date-time-input-container div.jx-typeahead-results a.jx-result { 349 + .aphront-form-time-input-container div.jx-typeahead-results a.jx-result { 348 350 border: none; 349 351 } 350 352 ··· 470 472 opacity: 0.5; 471 473 } 472 474 473 - .aphront-form-date-container.no-time .aphront-form-date-time-input{ 475 + .aphront-form-date-container.no-time .aphront-form-time-input{ 474 476 display: none; 475 477 } 476 478
+362
webroot/rsrc/js/application/calendar/behavior-day-view.js
··· 1 + /** 2 + * @provides javelin-behavior-day-view 3 + */ 4 + 5 + 6 + JX.behavior('day-view', function(config) { 7 + 8 + function findTodayClusters() { 9 + var events = today_events.sort(function(x, y){ 10 + return (x.eventStartEpoch - y.eventStartEpoch); 11 + }); 12 + 13 + var clusters = []; 14 + 15 + for (var i=0; i < events.length; i++) { 16 + var today_event = events[i]; 17 + 18 + var destination_cluster_index = null; 19 + var event_start = today_event.eventStartEpoch - (60); 20 + var event_end = today_event.eventEndEpoch + (60); 21 + 22 + for (var j=0; j < clusters.length; j++) { 23 + var cluster = clusters[j]; 24 + 25 + for(var k=0; k < cluster.length; k++) { 26 + var clustered_event = cluster[k]; 27 + var compare_event_start = clustered_event.eventStartEpoch; 28 + var compare_event_end = clustered_event.eventEndEpoch; 29 + 30 + if (event_start < compare_event_end && 31 + event_end > compare_event_start) { 32 + destination_cluster_index = j; 33 + break; 34 + } 35 + } 36 + 37 + if (destination_cluster_index !== null) { 38 + break; 39 + } 40 + } 41 + 42 + if (destination_cluster_index !== null) { 43 + clusters[destination_cluster_index].push(today_event); 44 + destination_cluster_index = null; 45 + } else { 46 + var next_cluster = []; 47 + next_cluster.push(today_event); 48 + clusters.push(next_cluster); 49 + } 50 + } 51 + 52 + return clusters; 53 + } 54 + 55 + function updateEventsFromCluster(cluster) { 56 + var cluster_size = cluster.length; 57 + var n = 0; 58 + for(var i=0; i < cluster.length; i++) { 59 + var cluster_member = cluster[i]; 60 + 61 + var event_id = cluster_member.eventID; 62 + var offset = ((n / cluster_size) * 100) + '%'; 63 + var width = ((1 / cluster_size) * 100) + '%'; 64 + 65 + for (var j=0; j < today_events.length; j++) { 66 + if (today_events[j].eventID == event_id) { 67 + 68 + today_events[j]['offset'] = offset; 69 + today_events[j]['width'] = width; 70 + } 71 + } 72 + n++; 73 + } 74 + 75 + return today_events; 76 + } 77 + 78 + function drawEvent(e) { 79 + var name = e['eventName']; 80 + var eventID = e['eventID']; 81 + var viewerIsInvited = e['viewerIsInvited']; 82 + var offset = e['offset']; 83 + var width = e['width']; 84 + var top = e['top']; 85 + var height = e['height']; 86 + var uri = e['uri']; 87 + 88 + var sigil = 'phui-calendar-day-event'; 89 + var link_class = 'phui-calendar-day-event-link'; 90 + 91 + if (viewerIsInvited) { 92 + link_class = link_class + ' viewer-invited-day-event'; 93 + } 94 + 95 + var name_link = JX.$N( 96 + 'a', 97 + { 98 + className : link_class, 99 + href: uri 100 + }, 101 + name); 102 + 103 + var class_name = 'phui-calendar-day-event'; 104 + if (e.canEdit) { 105 + class_name = class_name + ' can-drag'; 106 + } 107 + 108 + var div = JX.$N( 109 + 'div', 110 + { 111 + className: class_name, 112 + sigil: sigil, 113 + meta: {eventID: eventID, record: e, uri: uri}, 114 + style: { 115 + left: offset, 116 + width: width, 117 + top: top, 118 + height: height 119 + } 120 + }, 121 + name_link); 122 + 123 + return div; 124 + } 125 + 126 + function drawAllDayEvent( 127 + viewerIsInvited, 128 + uri, 129 + name) { 130 + var class_name = 'day-view-all-day'; 131 + if (viewerIsInvited) { 132 + class_name = class_name + ' viewer-invited-day-event'; 133 + } 134 + 135 + name = JX.$N( 136 + 'a', 137 + { 138 + className: class_name, 139 + href: uri 140 + }, 141 + name); 142 + 143 + var all_day_label = JX.$N( 144 + 'span', 145 + {className: 'phui-calendar-all-day-label'}, 146 + 'All Day'); 147 + 148 + var div_all_day = JX.$N( 149 + 'div', 150 + {className: 'phui-calendar-day-event all-day'}, 151 + [all_day_label, name]); 152 + 153 + return div_all_day; 154 + } 155 + 156 + function drawRows() { 157 + var rows = []; 158 + var early_hours = [8]; 159 + if (first_event_hour) { 160 + early_hours.push(first_event_hour); 161 + } 162 + var min_early_hour = Math.min(early_hours[0], early_hours[1]); 163 + 164 + 165 + for(var i=0; i < hours.length; i++) { 166 + if (hours[i]['hour'] < min_early_hour) { 167 + continue; 168 + } 169 + var cell_time = JX.$N( 170 + 'td', 171 + {className: 'phui-calendar-day-hour'}, 172 + hours[i]['hour_meridian']); 173 + 174 + var cell_event = JX.$N( 175 + 'td', 176 + { 177 + meta: { 178 + time: hours[i]['hour_meridian'] 179 + }, 180 + className: 'phui-calendar-day-events', 181 + sigil: 'phui-calendar-day-event-cell' 182 + }); 183 + 184 + var row = JX.$N( 185 + 'tr', 186 + {}, 187 + [cell_time, cell_event]); 188 + rows.push(row); 189 + } 190 + return rows; 191 + } 192 + 193 + function clusterAndDrawEvents() { 194 + var today_clusters = findTodayClusters(); 195 + for(var i=0; i < today_clusters.length; i++) { 196 + today_events = updateEventsFromCluster(today_clusters[i]); 197 + } 198 + var drawn_hourly_events = []; 199 + for (i=0; i < today_events.length; i++) { 200 + drawn_hourly_events.push(drawEvent(today_events[i])); 201 + } 202 + 203 + JX.DOM.setContent(hourly_events_wrapper, drawn_hourly_events); 204 + 205 + } 206 + 207 + var year = config.year; 208 + var month = config.month; 209 + var day = config.day; 210 + var query = config.query; 211 + 212 + var hours = config.hours; 213 + var first_event_hour = config.firstEventHour; 214 + var first_event_hour_epoch = parseInt(config.firstEventHourEpoch, 10); 215 + var today_events = config.todayEvents; 216 + var today_all_day_events = config.allDayEvents; 217 + var table_wrapper = JX.$(config.tableID); 218 + var rows = drawRows(); 219 + 220 + var all_day_events = []; 221 + for(i=0; i < today_all_day_events.length; i++) { 222 + var all_day_event = today_all_day_events[i]; 223 + all_day_events.push(drawAllDayEvent( 224 + all_day_event['viewerIsInvited'], 225 + all_day_event['uri'], 226 + all_day_event['name'])); 227 + } 228 + 229 + var table = JX.$N( 230 + 'table', 231 + {className: 'phui-calendar-day-view'}, 232 + rows); 233 + 234 + var dragging = false; 235 + var origin = null; 236 + 237 + var offset_top = null; 238 + var new_top = null; 239 + 240 + var click_time = null; 241 + 242 + JX.DOM.listen( 243 + table_wrapper, 244 + 'mousedown', 245 + 'phui-calendar-day-event', 246 + function(e){ 247 + 248 + if (!e.isNormalMouseEvent()) { 249 + return; 250 + } 251 + var data = e.getNodeData('phui-calendar-day-event'); 252 + if (!data.record.canEdit) { 253 + return; 254 + } 255 + e.kill(); 256 + dragging = e.getNode('phui-calendar-day-event'); 257 + JX.DOM.alterClass(dragging, 'phui-drag', true); 258 + 259 + click_time = new Date(); 260 + 261 + origin = JX.$V(e); 262 + 263 + var outer = JX.Vector.getPos(table); 264 + var inner = JX.Vector.getPos(dragging); 265 + 266 + offset_top = inner.y - outer.y; 267 + new_top = offset_top; 268 + 269 + dragging.style.top = offset_top + 'px'; 270 + }); 271 + JX.Stratcom.listen('mousemove', null, function(e){ 272 + if (!dragging) { 273 + return; 274 + } 275 + var cursor = JX.$V(e); 276 + 277 + new_top = cursor.y - origin.y + offset_top; 278 + new_top = Math.min(new_top, 1320); 279 + new_top = Math.max(new_top, 0); 280 + new_top = Math.floor(new_top/15) * 15; 281 + 282 + dragging.style.top = new_top + 'px'; 283 + }); 284 + JX.Stratcom.listen('mouseup', null, function(){ 285 + if (!dragging) { 286 + return; 287 + } 288 + 289 + var data = JX.Stratcom.getData(dragging); 290 + var record = data.record; 291 + 292 + if (new_top == offset_top) { 293 + var now = new Date(); 294 + if (now.getTime() - click_time.getTime() < 250) { 295 + JX.$U(record.uri).go(); 296 + } 297 + 298 + JX.DOM.alterClass(dragging, 'phui-drag', false); 299 + dragging = false; 300 + return; 301 + } 302 + var new_time = first_event_hour_epoch + (new_top * 60); 303 + var id = data.eventID; 304 + var duration = record.eventEndEpoch - record.eventStartEpoch; 305 + record.eventStartEpoch = new_time; 306 + record.eventEndEpoch = new_time + duration; 307 + record.top = new_top + 'px'; 308 + 309 + new JX.Workflow( 310 + '/calendar/event/drag/' + id + '/', 311 + {start: new_time}) 312 + .start(); 313 + 314 + JX.DOM.alterClass(dragging, 'phui-drag', false); 315 + dragging = false; 316 + 317 + clusterAndDrawEvents(); 318 + }); 319 + 320 + JX.DOM.listen(table_wrapper, 'click', 'phui-calendar-day-event', function(e){ 321 + if (e.isNormalClick()) { 322 + e.kill(); 323 + } 324 + }); 325 + 326 + JX.DOM.listen(table, 'click', 'phui-calendar-day-event-cell', function(e){ 327 + if (!e.isNormalClick()) { 328 + return; 329 + } 330 + var data = e.getNodeData('phui-calendar-day-event-cell'); 331 + var time = data.time; 332 + new JX.Workflow( 333 + '/calendar/event/create/', 334 + { 335 + year: year, 336 + month: month, 337 + day: day, 338 + time: time, 339 + next: 'day', 340 + query: query 341 + }) 342 + .start(); 343 + }); 344 + 345 + var hourly_events_wrapper = JX.$N( 346 + 'div', 347 + {style: { 348 + position: 'absolute', 349 + left: '69px', 350 + right: 0 351 + }}); 352 + 353 + clusterAndDrawEvents(); 354 + 355 + var daily_wrapper = JX.$N( 356 + 'div', 357 + {style: {position: 'relative'}}, 358 + [hourly_events_wrapper, table]); 359 + 360 + JX.DOM.setContent(table_wrapper, [all_day_events, daily_wrapper]); 361 + 362 + });
webroot/rsrc/js/application/calendar/event-all-day.js webroot/rsrc/js/application/calendar/behavior-event-all-day.js
+1
webroot/rsrc/js/application/config/behavior-reorder-fields.js
··· 54 54 JX.$(config.inputID).value = JX.JSON.stringify(order); 55 55 }; 56 56 57 + write_state_to_form(); 57 58 });
+12 -1
webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js
··· 447 447 }, 448 448 449 449 sendMessage: function(form, params) { 450 + var inputs = JX.DOM.scry(form, 'input'); 451 + var block_empty = true; 452 + for (var i = 0; i < inputs.length; i++) { 453 + if (inputs[i].type != 'hidden') { 454 + continue; 455 + } 456 + if (inputs[i].name == 'action' && inputs[i].value == 'join_room') { 457 + block_empty = false; 458 + continue; 459 + } 460 + } 450 461 // don't bother sending up text if there is nothing to submit 451 462 var textarea = JX.DOM.find(form, 'textarea'); 452 - if (!textarea.value.length) { 463 + if (block_empty && !textarea.value.length) { 453 464 return; 454 465 } 455 466 params = this._getParams(params);
+83
webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js
··· 395 395 handle_inline_action(data.node, data.op); 396 396 }); 397 397 398 + // Respond to the user clicking the "Hide Inline" button on an inline 399 + // comment. 400 + JX.Stratcom.listen('click', 'hide-inline', function(e) { 401 + e.kill(); 402 + 403 + var row = e.getNode('inline-row'); 404 + JX.DOM.hide(row); 405 + 406 + var prev = row.previousSibling; 407 + while (prev && JX.Stratcom.hasSigil(prev, 'inline-row')) { 408 + prev = prev.previousSibling; 409 + } 410 + 411 + if (!prev) { 412 + return; 413 + } 414 + 415 + var comment = e.getNodeData('differential-inline-comment'); 416 + 417 + var slots = []; 418 + for (var ii = 0; ii < prev.childNodes.length; ii++) { 419 + if (JX.DOM.isType(prev.childNodes[ii], 'th')) { 420 + slots.push(prev.childNodes[ii]); 421 + } 422 + } 423 + 424 + // Select the right-hand side if the comment is on the right. 425 + var slot = (comment.on_right && slots[1]) || slots[0]; 426 + 427 + var reveal = JX.DOM.scry(slot, 'a', 'reveal-inlines')[0]; 428 + if (!reveal) { 429 + reveal = JX.$N( 430 + 'a', 431 + { 432 + className: 'reveal-inlines', 433 + sigil: 'reveal-inlines' 434 + }, 435 + JX.$H(config.revealIcon)); 436 + 437 + JX.DOM.prependContent(slot, reveal); 438 + } 439 + 440 + new JX.Workflow(config.uri, {op: 'hide', ids: comment.id}) 441 + .setHandler(JX.bag) 442 + .start(); 443 + }); 444 + 445 + JX.Stratcom.listen('click', 'reveal-inlines', function(e) { 446 + e.kill(); 447 + 448 + var row = e.getNode('tag:tr'); 449 + var next = row.nextSibling; 450 + 451 + var ids = []; 452 + var ii; 453 + 454 + // Show any hidden inline comment rows directly below this one. 455 + while (next && JX.Stratcom.hasSigil(next, 'inline-row')) { 456 + JX.DOM.show(next); 457 + 458 + var comments = JX.DOM.scry(next, 'div', 'differential-inline-comment'); 459 + for (ii = 0; ii < comments.length; ii++) { 460 + var id = JX.Stratcom.getData(comments[ii]).id; 461 + if (id) { 462 + ids.push(id); 463 + } 464 + } 465 + 466 + next = next.nextSibling; 467 + } 468 + 469 + // Remove any "reveal" icons on the row. 470 + var reveals = JX.DOM.scry(row, 'a', 'reveal-inlines'); 471 + for (ii = 0; ii < reveals.length; ii++) { 472 + JX.DOM.remove(reveals[ii]); 473 + } 474 + 475 + new JX.Workflow(config.uri, {op: 'show', ids: ids.join(',')}) 476 + .setHandler(JX.bag) 477 + .start(); 478 + }); 479 + 480 + 398 481 });
-1
webroot/rsrc/js/application/herald/HeraldRuleEditor.js
··· 220 220 case 'buildplan': 221 221 case 'taskpriority': 222 222 case 'taskstatus': 223 - case 'arcanistprojects': 224 223 case 'legaldocuments': 225 224 case 'applicationemail': 226 225 var tokenizer = this._newTokenizer(type);
+59 -18
webroot/rsrc/js/core/behavior-fancy-datepicker.js
··· 64 64 JX.DOM.remove(picker); 65 65 picker = null; 66 66 JX.DOM.alterClass(root, 'picker-open', false); 67 - e.kill(); 67 + if (e) { 68 + e.kill(); 69 + } 68 70 69 71 root = null; 70 72 }; ··· 78 80 79 81 var get_inputs = function() { 80 82 return { 81 - y: JX.DOM.find(root, 'select', 'year-input'), 82 - m: JX.DOM.find(root, 'select', 'month-input'), 83 - d: JX.DOM.find(root, 'select', 'day-input'), 83 + d: JX.DOM.find(root, 'input', 'date-input'), 84 84 t: JX.DOM.find(root, 'input', 'time-input') 85 85 }; 86 86 }; 87 87 88 88 var read_date = function() { 89 89 var i = get_inputs(); 90 - value_y = +i.y.value; 91 - value_m = +i.m.value; 92 - value_d = +i.d.value; 90 + var date = i.d.value; 91 + var parts = date.split('/'); 92 + value_y = +parts[2]; 93 + value_m = +parts[0]; 94 + value_d = +parts[1]; 93 95 }; 94 96 95 97 var write_date = function() { 96 98 var i = get_inputs(); 97 - i.y.value = value_y; 98 - i.m.value = value_m; 99 - i.d.value = value_d; 99 + i.d.value = value_m + '/' + value_d + '/' + value_y; 100 100 }; 101 101 102 102 var render = function() { ··· 133 133 return JX.$N('td', {meta: {value: value}, className: class_name}, label); 134 134 }; 135 135 136 - 137 136 // Render the top bar which allows you to pick a month and year. 138 137 var render_month = function() { 138 + var valid_date = getValidDate(); 139 + var month = valid_date.getMonth(); 140 + var year = valid_date.getYear() + 1900; 141 + 139 142 var months = [ 140 143 'January', 141 144 'February', ··· 152 155 153 156 var buttons = [ 154 157 cell('\u25C0', 'm:-1', false, 'lrbutton'), 155 - cell(months[value_m - 1] + ' ' + value_y, null), 158 + cell(months[month] + ' ' + year, null), 156 159 cell('\u25B6', 'm:1', false, 'lrbutton')]; 157 160 158 161 return JX.$N( ··· 161 164 JX.$N('tr', {}, buttons)); 162 165 }; 163 166 167 + function getValidDate() { 168 + var written_date = new Date(value_y, value_m-1, value_d); 169 + if (isNaN(written_date.getTime())) { 170 + return new Date(); 171 + } else { 172 + //year 01 should be 2001, not 1901 173 + if (written_date.getYear() < 70) { 174 + value_y += 2000; 175 + written_date = new Date(value_y, value_m-1, value_d); 176 + } 177 + return written_date; 178 + } 179 + } 180 + 164 181 165 182 // Render the day-of-week and calendar views. 166 183 var render_day = function() { 184 + var today = new Date(); 185 + var valid_date = getValidDate(); 186 + 167 187 var weeks = []; 168 188 169 189 // First, render the weekday names. ··· 179 199 // Render the calendar itself. NOTE: Javascript uses 0-based month indexes 180 200 // while we use 1-based month indexes, so we have to adjust for that. 181 201 var days = []; 182 - var start = new Date(value_y, value_m - 1, 1).getDay(); 202 + var start = new Date( 203 + valid_date.getYear() + 1900, 204 + valid_date.getMonth(), 205 + 1).getDay(); 206 + 183 207 while (start--) { 184 208 days.push(cell('', null, false, 'day-placeholder')); 185 209 } 186 210 187 - var today = new Date(); 188 - 189 211 for (ii = 1; ii <= 31; ii++) { 190 - var date = new Date(value_y, value_m - 1, ii); 191 - if (date.getMonth() != (value_m - 1)) { 212 + var date = new Date( 213 + valid_date.getYear() + 1900, 214 + valid_date.getMonth(), 215 + ii); 216 + if (date.getMonth() != (valid_date.getMonth())) { 192 217 // We've spilled over into the next month, so stop rendering. 193 218 break; 194 219 } ··· 206 231 classes.push('weekend'); 207 232 } 208 233 209 - days.push(cell(ii, 'd:'+ii, value_d == ii, classes.join(' '))); 234 + days.push(cell( 235 + ii, 236 + 'd:'+ii, 237 + valid_date.getDate() == ii, 238 + classes.join(' '))); 210 239 } 211 240 212 241 // Slice the days into weeks. ··· 232 261 return; 233 262 } 234 263 264 + var valid_date = getValidDate(); 265 + value_y = valid_date.getYear() + 1900; 266 + value_m = valid_date.getMonth() + 1; 267 + value_d = valid_date.getDate(); 268 + 235 269 var p = data.value.split(':'); 236 270 switch (p[0]) { 237 271 case 'm': ··· 262 296 263 297 render(); 264 298 }); 299 + 300 + JX.Stratcom.listen('click', null, function(e){ 301 + if (e.getNode('phabricator-datepicker')) { 302 + return; 303 + } 304 + onclose(); 305 + }); 265 306 266 307 });