Page MenuHomePhabricator

D7339.largetrue.diff
No OneTemporary

D7339.largetrue.diff

This file is larger than 256 KB, so syntax highlighting was skipped.
Index: resources/sprite/manifest/icons.json
===================================================================
--- resources/sprite/manifest/icons.json
+++ resources/sprite/manifest/icons.json
@@ -76,6 +76,21 @@
"rule" : ".icons-blame-white, .device-desktop .phabricator-action-view:hover .icons-blame, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-blame",
"hash" : "9255bd2e32868c2a5a44018139cb4356"
},
+ "icons-calendar" : {
+ "name" : "icons-calendar",
+ "rule" : ".icons-calendar",
+ "hash" : "285eb4632a0c568aad1b49b83afd77a2"
+ },
+ "icons-calendar-grey" : {
+ "name" : "icons-calendar-grey",
+ "rule" : ".icons-calendar-grey",
+ "hash" : "dd72ee17639a18cfc8f96f9923e90b42"
+ },
+ "icons-calendar-white" : {
+ "name" : "icons-calendar-white",
+ "rule" : ".icons-calendar-white, .device-desktop .phabricator-action-view:hover .icons-calendar, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-calendar",
+ "hash" : "71bab8f6723d1005afa0a18553ce03d5"
+ },
"icons-check" : {
"name" : "icons-check",
"rule" : ".icons-check",
@@ -136,6 +151,21 @@
"rule" : ".icons-create-white, .device-desktop .phabricator-action-view:hover .icons-create, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-create",
"hash" : "3b2d0928b2c867a65b95d12177ad8705"
},
+ "icons-data" : {
+ "name" : "icons-data",
+ "rule" : ".icons-data",
+ "hash" : "f4c8df3b0290d1c2be1f206430ba83b4"
+ },
+ "icons-data-grey" : {
+ "name" : "icons-data-grey",
+ "rule" : ".icons-data-grey",
+ "hash" : "80c011b2d499c339deae602053107112"
+ },
+ "icons-data-white" : {
+ "name" : "icons-data-white",
+ "rule" : ".icons-data-white, .device-desktop .phabricator-action-view:hover .icons-data, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-data",
+ "hash" : "0fb59ace98fa5b8cb5adc278061c538d"
+ },
"icons-delete" : {
"name" : "icons-delete",
"rule" : ".icons-delete",
@@ -256,6 +286,21 @@
"rule" : ".icons-file-white, .device-desktop .phabricator-action-view:hover .icons-file, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-file",
"hash" : "ba72c9acee3f815d209175a36f2e514e"
},
+ "icons-film" : {
+ "name" : "icons-film",
+ "rule" : ".icons-film",
+ "hash" : "3e43750881b0dbd8bf1326b8b225b2ea"
+ },
+ "icons-film-grey" : {
+ "name" : "icons-film-grey",
+ "rule" : ".icons-film-grey",
+ "hash" : "b586d438ea1d87246fe9f17f230d8536"
+ },
+ "icons-film-white" : {
+ "name" : "icons-film-white",
+ "rule" : ".icons-film-white, .device-desktop .phabricator-action-view:hover .icons-film, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-film",
+ "hash" : "ba18a336b9dafbfb0102e4128a79e660"
+ },
"icons-flag" : {
"name" : "icons-flag",
"rule" : ".icons-flag",
@@ -691,6 +736,21 @@
"rule" : ".icons-move-white, .device-desktop .phabricator-action-view:hover .icons-move, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-move",
"hash" : "8d3312ff594541a0017778f77ab3ca41"
},
+ "icons-music" : {
+ "name" : "icons-music",
+ "rule" : ".icons-music",
+ "hash" : "7d9e85fb6f4eeb9e7763493d00c7e1ba"
+ },
+ "icons-music-grey" : {
+ "name" : "icons-music-grey",
+ "rule" : ".icons-music-grey",
+ "hash" : "400564df73fd8a2815d71c50f89be20a"
+ },
+ "icons-music-white" : {
+ "name" : "icons-music-white",
+ "rule" : ".icons-music-white, .device-desktop .phabricator-action-view:hover .icons-music, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-music",
+ "hash" : "d16b17e0a125fae84b2d515874019c03"
+ },
"icons-new" : {
"name" : "icons-new",
"rule" : ".icons-new",
@@ -1006,6 +1066,36 @@
"rule" : ".icons-world-white, .device-desktop .phabricator-action-view:hover .icons-world, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-world",
"hash" : "a226ba8b9c49ed9fa54643fe77e49dd2"
},
+ "icons-wrench" : {
+ "name" : "icons-wrench",
+ "rule" : ".icons-wrench",
+ "hash" : "8b9b5d7b4e2923cdf5c9440ee7cb2ee2"
+ },
+ "icons-wrench-grey" : {
+ "name" : "icons-wrench-grey",
+ "rule" : ".icons-wrench-grey",
+ "hash" : "e6effa08f3bf34393a4787c1354d4a29"
+ },
+ "icons-wrench-white" : {
+ "name" : "icons-wrench-white",
+ "rule" : ".icons-wrench-white, .device-desktop .phabricator-action-view:hover .icons-wrench, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-wrench",
+ "hash" : "a0f1447733a5a39d2c7a64d774ccf113"
+ },
+ "icons-zip" : {
+ "name" : "icons-zip",
+ "rule" : ".icons-zip",
+ "hash" : "15f454e97fc353c1fa5a614073782c46"
+ },
+ "icons-zip-grey" : {
+ "name" : "icons-zip-grey",
+ "rule" : ".icons-zip-grey",
+ "hash" : "2e633a5027156a60d302395784d26062"
+ },
+ "icons-zip-white" : {
+ "name" : "icons-zip-white",
+ "rule" : ".icons-zip-white, .device-desktop .phabricator-action-view:hover .icons-zip, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-zip",
+ "hash" : "e006963c22a6be6fb72a0eefecf83649"
+ },
"remarkup-assist-text_b" : {
"name" : "remarkup-assist-text_b",
"rule" : ".remarkup-assist-b",
Index: resources/sprite/manifest/login.json
===================================================================
--- resources/sprite/manifest/login.json
+++ resources/sprite/manifest/login.json
@@ -66,6 +66,11 @@
"rule" : ".login-Openid",
"hash" : "9267ffbb8d4e6dee409c4d8fa2d50c0a"
},
+ "login-Persona" : {
+ "name" : "login-Persona",
+ "rule" : ".login-Persona",
+ "hash" : "949faf7bf5ed377c06aa480da1f42b74"
+ },
"login-Phabricator" : {
"name" : "login-Phabricator",
"rule" : ".login-Phabricator",
Index: resources/sprite/manifest/projects.json
===================================================================
--- /dev/null
+++ resources/sprite/manifest/projects.json
@@ -0,0 +1,291 @@
+{
+ "version" : 1,
+ "sprites" : {
+ "projects_8ball" : {
+ "name" : "projects_8ball",
+ "rule" : ".projects_8ball",
+ "hash" : "1571c4d51926d3af7711b825c5816e2e"
+ },
+ "projects_alien" : {
+ "name" : "projects_alien",
+ "rule" : ".projects_alien",
+ "hash" : "384f920ae335dca04edaf29663d3a074"
+ },
+ "projects_annouce" : {
+ "name" : "projects_annouce",
+ "rule" : ".projects_annouce",
+ "hash" : "38abd2ff32e7c145e44c020ee4e6f2f1"
+ },
+ "projects_art" : {
+ "name" : "projects_art",
+ "rule" : ".projects_art",
+ "hash" : "85c545e5130f00ff1b93c0af0d540974"
+ },
+ "projects_award" : {
+ "name" : "projects_award",
+ "rule" : ".projects_award",
+ "hash" : "fad6d89e4938e16f22f3c9db7cf5d696"
+ },
+ "projects_bacon" : {
+ "name" : "projects_bacon",
+ "rule" : ".projects_bacon",
+ "hash" : "f6300cdfa5a96a223f53f13dd0d3acc3"
+ },
+ "projects_bandaid" : {
+ "name" : "projects_bandaid",
+ "rule" : ".projects_bandaid",
+ "hash" : "c463dffa161997277fc6697155f4085b"
+ },
+ "projects_beer" : {
+ "name" : "projects_beer",
+ "rule" : ".projects_beer",
+ "hash" : "81c7580f322d9fb40c77db56cd92d61d"
+ },
+ "projects_bomb" : {
+ "name" : "projects_bomb",
+ "rule" : ".projects_bomb",
+ "hash" : "1123da7cc56313891c9979b004cc02f7"
+ },
+ "projects_briefcase" : {
+ "name" : "projects_briefcase",
+ "rule" : ".projects_briefcase",
+ "hash" : "9b4b413ddb250ce1d3fbe18a5a5698cd"
+ },
+ "projects_bug" : {
+ "name" : "projects_bug",
+ "rule" : ".projects_bug",
+ "hash" : "9678702aed00c4779759ebbdfe97fe48"
+ },
+ "projects_calendar" : {
+ "name" : "projects_calendar",
+ "rule" : ".projects_calendar",
+ "hash" : "e7dc5d1b11fc55ed239fcbfe527ed0e7"
+ },
+ "projects_cloud" : {
+ "name" : "projects_cloud",
+ "rule" : ".projects_cloud",
+ "hash" : "d38bf58580b3c36fbd3149a13f7d0e5e"
+ },
+ "projects_coffee" : {
+ "name" : "projects_coffee",
+ "rule" : ".projects_coffee",
+ "hash" : "a9c10862139d8e7f56c9f892496f9666"
+ },
+ "projects_creditcard" : {
+ "name" : "projects_creditcard",
+ "rule" : ".projects_creditcard",
+ "hash" : "db2c179cb4935da8b9950ac30da8c0d1"
+ },
+ "projects_death" : {
+ "name" : "projects_death",
+ "rule" : ".projects_death",
+ "hash" : "cdea72dfdcb3fc64873b9fff78addb3c"
+ },
+ "projects_desktop" : {
+ "name" : "projects_desktop",
+ "rule" : ".projects_desktop",
+ "hash" : "19d2ef34e3dd53615cdad91eb987d6fe"
+ },
+ "projects_dropbox" : {
+ "name" : "projects_dropbox",
+ "rule" : ".projects_dropbox",
+ "hash" : "10231bf468769b96ed40cf983abfa269"
+ },
+ "projects_education" : {
+ "name" : "projects_education",
+ "rule" : ".projects_education",
+ "hash" : "ce3d0ca75d519b2ac427a690d30475f8"
+ },
+ "projects_experimental" : {
+ "name" : "projects_experimental",
+ "rule" : ".projects_experimental",
+ "hash" : "311ef712f8daca057c20c8fd78fa77ce"
+ },
+ "projects_facebook" : {
+ "name" : "projects_facebook",
+ "rule" : ".projects_facebook",
+ "hash" : "16581191e4ce9e0115d447b479c886cb"
+ },
+ "projects_facility" : {
+ "name" : "projects_facility",
+ "rule" : ".projects_facility",
+ "hash" : "d8893f9d2b75ec047b6f3898a386055c"
+ },
+ "projects_film" : {
+ "name" : "projects_film",
+ "rule" : ".projects_film",
+ "hash" : "57497050fa09ba1533d981a9c1550ba9"
+ },
+ "projects_forked" : {
+ "name" : "projects_forked",
+ "rule" : ".projects_forked",
+ "hash" : "f575428e1079981840297bd444e51c43"
+ },
+ "projects_games" : {
+ "name" : "projects_games",
+ "rule" : ".projects_games",
+ "hash" : "b802cff3e76051675b37165bd9702088"
+ },
+ "projects_ghost" : {
+ "name" : "projects_ghost",
+ "rule" : ".projects_ghost",
+ "hash" : "7c8622cad29bddc5179f6a6d5f15fbe9"
+ },
+ "projects_gift" : {
+ "name" : "projects_gift",
+ "rule" : ".projects_gift",
+ "hash" : "f2ca678906a6806f421b60abddaa6cae"
+ },
+ "projects_globe" : {
+ "name" : "projects_globe",
+ "rule" : ".projects_globe",
+ "hash" : "87515a83cc0c840804aca594677d1eae"
+ },
+ "projects_golf" : {
+ "name" : "projects_golf",
+ "rule" : ".projects_golf",
+ "hash" : "1ee7556fab3d46d925deb00322dad858"
+ },
+ "projects_heart" : {
+ "name" : "projects_heart",
+ "rule" : ".projects_heart",
+ "hash" : "3da64839e37ee245333017d0a310cc2e"
+ },
+ "projects_intergalactic" : {
+ "name" : "projects_intergalactic",
+ "rule" : ".projects_intergalactic",
+ "hash" : "94dca756cb267bdb4e0ed58467320780"
+ },
+ "projects_lock" : {
+ "name" : "projects_lock",
+ "rule" : ".projects_lock",
+ "hash" : "9d4c8ad3a4ac4163f284461da7df2763"
+ },
+ "projects_mail" : {
+ "name" : "projects_mail",
+ "rule" : ".projects_mail",
+ "hash" : "963f5ce26c6caf86e72d754f7b6e8865"
+ },
+ "projects_martini" : {
+ "name" : "projects_martini",
+ "rule" : ".projects_martini",
+ "hash" : "24d4d5fb5c334621ece4c35a9196471e"
+ },
+ "projects_medical" : {
+ "name" : "projects_medical",
+ "rule" : ".projects_medical",
+ "hash" : "e0cb3ef5557321d166e8eb49c10d3599"
+ },
+ "projects_mobile" : {
+ "name" : "projects_mobile",
+ "rule" : ".projects_mobile",
+ "hash" : "37dec95d1a4a937743d52acac319c3b6"
+ },
+ "projects_music" : {
+ "name" : "projects_music",
+ "rule" : ".projects_music",
+ "hash" : "e7a814194685ac25be0db05b04074607"
+ },
+ "projects_news" : {
+ "name" : "projects_news",
+ "rule" : ".projects_news",
+ "hash" : "6861f3ee827d09b0592166514f4941e8"
+ },
+ "projects_orgchart" : {
+ "name" : "projects_orgchart",
+ "rule" : ".projects_orgchart",
+ "hash" : "20c51c59788fb2bc8184fdd5687d33dc"
+ },
+ "projects_peoples" : {
+ "name" : "projects_peoples",
+ "rule" : ".projects_peoples",
+ "hash" : "c949ba6d09e68317a9a11482e75e5140"
+ },
+ "projects_piechart" : {
+ "name" : "projects_piechart",
+ "rule" : ".projects_piechart",
+ "hash" : "051138560e30982a029aa5e4ea87bc17"
+ },
+ "projects_poison" : {
+ "name" : "projects_poison",
+ "rule" : ".projects_poison",
+ "hash" : "56ddafd138e421f198b9cb38e5dc7455"
+ },
+ "projects_putabirdonit" : {
+ "name" : "projects_putabirdonit",
+ "rule" : ".projects_putabirdonit",
+ "hash" : "ee298fff82c34341b986a3e1b77bea11"
+ },
+ "projects_radiate" : {
+ "name" : "projects_radiate",
+ "rule" : ".projects_radiate",
+ "hash" : "9cfb918089b3de8506a5d270a119052c"
+ },
+ "projects_savings" : {
+ "name" : "projects_savings",
+ "rule" : ".projects_savings",
+ "hash" : "9e92bc5e64f79d2f4842ac24a8b57fcb"
+ },
+ "projects_search" : {
+ "name" : "projects_search",
+ "rule" : ".projects_search",
+ "hash" : "a42c1c31f2929838b0f181f417c0b6a4"
+ },
+ "projects_shield" : {
+ "name" : "projects_shield",
+ "rule" : ".projects_shield",
+ "hash" : "40c6e1bec7c07c165668ac45c218847a"
+ },
+ "projects_speed" : {
+ "name" : "projects_speed",
+ "rule" : ".projects_speed",
+ "hash" : "2b70c194d07f5a9d95abc51d84fb22ed"
+ },
+ "projects_sprint" : {
+ "name" : "projects_sprint",
+ "rule" : ".projects_sprint",
+ "hash" : "655ef9a3043eab23eac1da21baeb36b3"
+ },
+ "projects_star" : {
+ "name" : "projects_star",
+ "rule" : ".projects_star",
+ "hash" : "a46e3c18f68bc13a65b410496e27b5d7"
+ },
+ "projects_storage" : {
+ "name" : "projects_storage",
+ "rule" : ".projects_storage",
+ "hash" : "bb19baa77bb7596f43f77e5dbbddb006"
+ },
+ "projects_tablet" : {
+ "name" : "projects_tablet",
+ "rule" : ".projects_tablet",
+ "hash" : "830dcf6637288ca122c8f5034cae3769"
+ },
+ "projects_travel" : {
+ "name" : "projects_travel",
+ "rule" : ".projects_travel",
+ "hash" : "86ec4dcd025879a43435b101fd542a1b"
+ },
+ "projects_twitter" : {
+ "name" : "projects_twitter",
+ "rule" : ".projects_twitter",
+ "hash" : "75b8680dd1e4ecce4ca3a39c87e1ed80"
+ },
+ "projects_warning" : {
+ "name" : "projects_warning",
+ "rule" : ".projects_warning",
+ "hash" : "3ac48b6f963675e1f4bb4ac75aad936f"
+ },
+ "projects_whale" : {
+ "name" : "projects_whale",
+ "rule" : ".projects_whale",
+ "hash" : "569b584c7e80a0a9b965280abd27c723"
+ }
+ },
+ "scales" : [
+ 1,
+ 2
+ ],
+ "header" : "\/**\n * @provides sprite-projects-css\n * @generated\n *\/\n\n.sprite-projects {\n background-image: url(\/rsrc\/image\/sprite-projects.png);\n background-repeat: no-repeat;\n}\n\n@media\nonly screen and (min-device-pixel-ratio: 1.5),\nonly screen and (-webkit-min-device-pixel-ratio: 1.5) {\n .sprite-projects {\n background-image: url(\/rsrc\/image\/sprite-projects-X2.png);\n background-size: {X}px {Y}px;\n }\n}\n",
+ "type" : "standard"
+}
Index: resources/sprite/manifest/status.json
===================================================================
--- resources/sprite/manifest/status.json
+++ resources/sprite/manifest/status.json
@@ -154,7 +154,7 @@
"status-oh-closed" : {
"name" : "status-oh-closed",
"rule" : ".status-oh-closed",
- "hash" : "53dc3a09dc2fbd3a546e5f5619a03b30"
+ "hash" : "cbc6a0959ebad3e88cc3ac67743a7f3a"
},
"status-oh-closed-dark" : {
"name" : "status-oh-closed-dark",
@@ -164,7 +164,7 @@
"status-oh-open" : {
"name" : "status-oh-open",
"rule" : ".status-oh-open",
- "hash" : "6ed5dd19324018203085c68155fae53a"
+ "hash" : "e86f83bc40e77664c91c834b27816576"
},
"status-oh-open-green" : {
"name" : "status-oh-open-green",
Index: resources/sql/patches/20130619.authconf.php
===================================================================
--- resources/sql/patches/20130619.authconf.php
+++ resources/sql/patches/20130619.authconf.php
@@ -45,7 +45,7 @@
),
'PhabricatorAuthProviderPassword' => array(
'enabled' => 'auth.password-auth-enabled',
- 'enabled-default' => true,
+ 'enabled-default' => false,
'registration' => false,
'type' => 'password',
'domain' => 'self',
Index: resources/sql/patches/20131004.dxedgekey.sql
===================================================================
--- /dev/null
+++ resources/sql/patches/20131004.dxedgekey.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_differential.edge
+ ADD UNIQUE KEY `key_dst` (dst, type, src);
Index: resources/sql/patches/20131004.dxreviewers.php
===================================================================
--- /dev/null
+++ resources/sql/patches/20131004.dxreviewers.php
@@ -0,0 +1,46 @@
+<?php
+
+$table = new DifferentialRevision();
+$conn_w = $table->establishConnection('w');
+
+// NOTE: We migrate by revision because the relationship table doesn't have
+// an "id" column.
+
+foreach (new LiskMigrationIterator($table) as $revision) {
+ $revision_id = $revision->getID();
+ $revision_phid = $revision->getPHID();
+
+ echo "Migrating reviewers for D{$revision_id}...\n";
+
+ $reviewer_phids = queryfx_all(
+ $conn_w,
+ 'SELECT objectPHID FROM %T WHERE revisionID = %d
+ AND relation = %s ORDER BY sequence',
+ 'differential_relationship',
+ $revision_id,
+ 'revw');
+ $reviewer_phids = ipull($reviewer_phids, 'objectPHID');
+
+ if (!$reviewer_phids) {
+ continue;
+ }
+
+ $editor = id(new PhabricatorEdgeEditor())
+ ->setActor(PhabricatorUser::getOmnipotentUser());
+
+ foreach ($reviewer_phids as $dst) {
+ $editor->addEdge(
+ $revision_phid,
+ PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER,
+ $dst,
+ array(
+ 'data' => array(
+ 'status' => DifferentialReviewerStatus::STATUS_ADDED,
+ ),
+ ));
+ }
+
+ $editor->save();
+}
+
+echo "Done.\n";
Index: resources/sql/patches/20131006.hdisable.sql
===================================================================
--- /dev/null
+++ resources/sql/patches/20131006.hdisable.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_herald.herald_rule
+ ADD isDisabled INT UNSIGNED NOT NULL DEFAULT 0;
Index: resources/sql/patches/20131010.pstorage.sql
===================================================================
--- /dev/null
+++ resources/sql/patches/20131010.pstorage.sql
@@ -0,0 +1,9 @@
+CREATE TABLE {$NAMESPACE}_policy.policy (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ rules LONGTEXT NOT NULL COLLATE utf8_bin,
+ defaultAction VARCHAR(32) NOT NULL COLLATE utf8_bin,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY (phid)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
Index: resources/sql/patches/20131015.cpolicy.sql
===================================================================
--- /dev/null
+++ resources/sql/patches/20131015.cpolicy.sql
@@ -0,0 +1,5 @@
+ALTER TABLE {$NAMESPACE}_countdown.countdown
+ ADD viewPolicy VARCHAR(64) NOT NULL;
+
+UPDATE {$NAMESPACE}_countdown.countdown
+ SET viewPolicy = 'users' WHERE viewPolicy = '';
Index: scripts/celerity/generate_sprites.php
===================================================================
--- scripts/celerity/generate_sprites.php
+++ scripts/celerity/generate_sprites.php
@@ -40,6 +40,7 @@
'gradient' => $generator->buildGradientSheet(),
'login' => $generator->buildLoginSheet(),
'status' => $generator->buildStatusSheet(),
+ 'projects' => $generator->buildProjectsSheet(),
);
list($err) = exec_manual('optipng');
Index: scripts/celerity_mapper.php
===================================================================
--- scripts/celerity_mapper.php
+++ scripts/celerity_mapper.php
@@ -112,7 +112,7 @@
'phabricator-application-launch-view-css',
'phabricator-action-list-view-css',
- 'phabricator-property-list-view-css',
+ 'phui-property-list-view-css',
'phabricator-tag-view-css',
'phui-list-view-css',
),
Index: src/__celerity_resource_map__.php
===================================================================
--- src/__celerity_resource_map__.php
+++ src/__celerity_resource_map__.php
@@ -639,29 +639,29 @@
),
'/rsrc/image/sprite-icons-X2.png' =>
array(
- 'hash' => '73be48fb682888800fd17511814c14cc',
- 'uri' => '/res/73be48fb/rsrc/image/sprite-icons-X2.png',
+ 'hash' => '1498852ad0bf0eeefabe945fe3dfd000',
+ 'uri' => '/res/1498852a/rsrc/image/sprite-icons-X2.png',
'disk' => '/rsrc/image/sprite-icons-X2.png',
'type' => 'png',
),
'/rsrc/image/sprite-icons.png' =>
array(
- 'hash' => '2059f27be8339fe752ed99c29ef9ae1c',
- 'uri' => '/res/2059f27b/rsrc/image/sprite-icons.png',
+ 'hash' => '65cbd600a2475f9c00bd84ce8f7700b5',
+ 'uri' => '/res/65cbd600/rsrc/image/sprite-icons.png',
'disk' => '/rsrc/image/sprite-icons.png',
'type' => 'png',
),
'/rsrc/image/sprite-login-X2.png' =>
array(
- 'hash' => 'cd7eb19a0428c6be90c48cd2329a35fc',
- 'uri' => '/res/cd7eb19a/rsrc/image/sprite-login-X2.png',
+ 'hash' => '7176335e4e1604f94eacdb1790660560',
+ 'uri' => '/res/7176335e/rsrc/image/sprite-login-X2.png',
'disk' => '/rsrc/image/sprite-login-X2.png',
'type' => 'png',
),
'/rsrc/image/sprite-login.png' =>
array(
- 'hash' => '788be2fd8e1f80b9faec9f5cf4bd5f4b',
- 'uri' => '/res/788be2fd/rsrc/image/sprite-login.png',
+ 'hash' => '7d3eee260ee0beb90c12e26fbc48fd9c',
+ 'uri' => '/res/7d3eee26/rsrc/image/sprite-login.png',
'disk' => '/rsrc/image/sprite-login.png',
'type' => 'png',
),
@@ -700,10 +700,24 @@
'disk' => '/rsrc/image/sprite-payments.png',
'type' => 'png',
),
+ '/rsrc/image/sprite-projects-X2.png' =>
+ array(
+ 'hash' => '3bd29905e197068a75ace63880a2b6eb',
+ 'uri' => '/res/3bd29905/rsrc/image/sprite-projects-X2.png',
+ 'disk' => '/rsrc/image/sprite-projects-X2.png',
+ 'type' => 'png',
+ ),
+ '/rsrc/image/sprite-projects.png' =>
+ array(
+ 'hash' => 'd9ec3fa470e6523520726ef75b011a03',
+ 'uri' => '/res/d9ec3fa4/rsrc/image/sprite-projects.png',
+ 'disk' => '/rsrc/image/sprite-projects.png',
+ 'type' => 'png',
+ ),
'/rsrc/image/sprite-status-X2.png' =>
array(
- 'hash' => 'a537049b4500a47af3fc27c626bbe865',
- 'uri' => '/res/a537049b/rsrc/image/sprite-status-X2.png',
+ 'hash' => 'ba4921c45c4de3e624e549bef9465fd9',
+ 'uri' => '/res/ba4921c4/rsrc/image/sprite-status-X2.png',
'disk' => '/rsrc/image/sprite-status-X2.png',
'type' => 'png',
),
@@ -836,7 +850,7 @@
),
'aphront-dialog-view-css' =>
array(
- 'uri' => '/res/609ccc78/rsrc/css/aphront/dialog-view.css',
+ 'uri' => '/res/830fa2de/rsrc/css/aphront/dialog-view.css',
'type' => 'css',
'requires' =>
array(
@@ -1017,7 +1031,7 @@
),
'differential-changeset-view-css' =>
array(
- 'uri' => '/res/5fd9d5c0/rsrc/css/application/differential/changeset-view.css',
+ 'uri' => '/res/0e780c41/rsrc/css/application/differential/changeset-view.css',
'type' => 'css',
'requires' =>
array(
@@ -1059,7 +1073,7 @@
),
'differential-results-table-css' =>
array(
- 'uri' => '/res/7d9150bb/rsrc/css/application/differential/results-table.css',
+ 'uri' => '/res/5e37cf75/rsrc/css/application/differential/results-table.css',
'type' => 'css',
'requires' =>
array(
@@ -1095,7 +1109,7 @@
),
'differential-revision-history-css' =>
array(
- 'uri' => '/res/bb29e0e8/rsrc/css/application/differential/revision-history.css',
+ 'uri' => '/res/13b4c17b/rsrc/css/application/differential/revision-history.css',
'type' => 'css',
'requires' =>
array(
@@ -1122,7 +1136,7 @@
),
'diffusion-commit-view-css' =>
array(
- 'uri' => '/res/b445944e/rsrc/css/application/diffusion/commit-view.css',
+ 'uri' => '/res/a48ea65a/rsrc/css/application/diffusion/commit-view.css',
'type' => 'css',
'requires' =>
array(
@@ -1140,7 +1154,7 @@
),
'diffusion-source-css' =>
array(
- 'uri' => '/res/072800bb/rsrc/css/application/diffusion/diffusion-source.css',
+ 'uri' => '/res/f4a2f867/rsrc/css/application/diffusion/diffusion-source.css',
'type' => 'css',
'requires' =>
array(
@@ -1149,7 +1163,7 @@
),
'diviner-shared-css' =>
array(
- 'uri' => '/res/cba9c99e/rsrc/css/diviner/diviner-shared.css',
+ 'uri' => '/res/2e831eea/rsrc/css/diviner/diviner-shared.css',
'type' => 'css',
'requires' =>
array(
@@ -1176,7 +1190,7 @@
),
'herald-rule-editor' =>
array(
- 'uri' => '/res/c42c0444/rsrc/js/application/herald/HeraldRuleEditor.js',
+ 'uri' => '/res/a561eb19/rsrc/js/application/herald/HeraldRuleEditor.js',
'type' => 'js',
'requires' =>
array(
@@ -1935,6 +1949,20 @@
),
'disk' => '/rsrc/js/application/owners/owners-path-editor.js',
),
+ 'javelin-behavior-persona-login' =>
+ array(
+ 'uri' => '/res/128fdf56/rsrc/js/application/auth/behavior-persona-login.js',
+ 'type' => 'js',
+ 'requires' =>
+ array(
+ 0 => 'javelin-behavior',
+ 1 => 'javelin-resource',
+ 2 => 'javelin-stratcom',
+ 3 => 'javelin-workflow',
+ 4 => 'javelin-util',
+ ),
+ 'disk' => '/rsrc/js/application/auth/behavior-persona-login.js',
+ ),
'javelin-behavior-phabricator-active-nav' =>
array(
'uri' => '/res/9c8d3df8/rsrc/js/core/behavior-active-nav.js',
@@ -2119,16 +2147,17 @@
),
'javelin-behavior-phabricator-remarkup-assist' =>
array(
- 'uri' => '/res/c3f4439f/rsrc/js/core/behavior-phabricator-remarkup-assist.js',
+ 'uri' => '/res/1d8dab1f/rsrc/js/core/behavior-phabricator-remarkup-assist.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-dom',
- 3 => 'phabricator-textareautils',
- 4 => 'javelin-workflow',
- 5 => 'javelin-vector',
+ 3 => 'phabricator-phtize',
+ 4 => 'phabricator-textareautils',
+ 5 => 'javelin-workflow',
+ 6 => 'javelin-vector',
),
'disk' => '/rsrc/js/core/behavior-phabricator-remarkup-assist.js',
),
@@ -2265,6 +2294,39 @@
),
'disk' => '/rsrc/js/application/pholio/behavior-pholio-mock-view.js',
),
+ 'javelin-behavior-policy-control' =>
+ array(
+ 'uri' => '/res/ce9f54c8/rsrc/js/application/policy/behavior-policy-control.js',
+ 'type' => 'js',
+ 'requires' =>
+ array(
+ 0 => 'javelin-behavior',
+ 1 => 'javelin-dom',
+ 2 => 'javelin-util',
+ 3 => 'phabricator-dropdown-menu',
+ 4 => 'phabricator-menu-item',
+ 5 => 'javelin-workflow',
+ ),
+ 'disk' => '/rsrc/js/application/policy/behavior-policy-control.js',
+ ),
+ 'javelin-behavior-policy-rule-editor' =>
+ array(
+ 'uri' => '/res/4665236c/rsrc/js/application/policy/behavior-policy-rule-editor.js',
+ 'type' => 'js',
+ 'requires' =>
+ array(
+ 0 => 'javelin-behavior',
+ 1 => 'multirow-row-manager',
+ 2 => 'javelin-dom',
+ 3 => 'javelin-util',
+ 4 => 'phabricator-prefab',
+ 5 => 'javelin-tokenizer',
+ 6 => 'javelin-typeahead',
+ 7 => 'javelin-typeahead-preloaded-source',
+ 8 => 'javelin-json',
+ ),
+ 'disk' => '/rsrc/js/application/policy/behavior-policy-rule-editor.js',
+ ),
'javelin-behavior-ponder-votebox' =>
array(
'uri' => '/res/c28daa12/rsrc/js/application/ponder/behavior-votebox.js',
@@ -2451,7 +2513,7 @@
),
'javelin-behavior-workflow' =>
array(
- 'uri' => '/res/4a0595c1/rsrc/js/core/behavior-workflow.js',
+ 'uri' => '/res/144d3196/rsrc/js/core/behavior-workflow.js',
'type' => 'js',
'requires' =>
array(
@@ -2568,7 +2630,7 @@
),
'javelin-magical-init' =>
array(
- 'uri' => '/res/7c6c8d5a/rsrc/externals/javelin/core/init.js',
+ 'uri' => '/res/374d1f02/rsrc/externals/javelin/core/init.js',
'type' => 'js',
'requires' =>
array(
@@ -2868,7 +2930,7 @@
),
'javelin-workflow' =>
array(
- 'uri' => '/res/7626494b/rsrc/externals/javelin/lib/Workflow.js',
+ 'uri' => '/res/09a97dda/rsrc/externals/javelin/lib/Workflow.js',
'type' => 'js',
'requires' =>
array(
@@ -3018,7 +3080,7 @@
),
'phabricator-action-list-view-css' =>
array(
- 'uri' => '/res/89534fee/rsrc/css/layout/phabricator-action-list-view.css',
+ 'uri' => '/res/2dce4556/rsrc/css/layout/phabricator-action-list-view.css',
'type' => 'css',
'requires' =>
array(
@@ -3048,7 +3110,7 @@
),
'phabricator-chatlog-css' =>
array(
- 'uri' => '/res/5542e247/rsrc/css/application/chatlog/chatlog.css',
+ 'uri' => '/res/cf9b0aa7/rsrc/css/application/chatlog/chatlog.css',
'type' => 'css',
'requires' =>
array(
@@ -3084,7 +3146,7 @@
),
'phabricator-crumbs-view-css' =>
array(
- 'uri' => '/res/4d722e16/rsrc/css/layout/phabricator-crumbs-view.css',
+ 'uri' => '/res/f3c7068b/rsrc/css/layout/phabricator-crumbs-view.css',
'type' => 'css',
'requires' =>
array(
@@ -3123,7 +3185,7 @@
),
'phabricator-dropdown-menu' =>
array(
- 'uri' => '/res/a248b7f4/rsrc/js/core/DropdownMenu.js',
+ 'uri' => '/res/147ca011/rsrc/js/core/DropdownMenu.js',
'type' => 'js',
'requires' =>
array(
@@ -3253,7 +3315,7 @@
),
'phabricator-menu-item' =>
array(
- 'uri' => '/res/2add4594/rsrc/js/core/DropdownMenuItem.js',
+ 'uri' => '/res/e810b0a1/rsrc/js/core/DropdownMenuItem.js',
'type' => 'js',
'requires' =>
array(
@@ -3369,18 +3431,9 @@
),
'disk' => '/rsrc/css/application/projects/project-tag.css',
),
- 'phabricator-property-list-view-css' =>
- array(
- 'uri' => '/res/7835f74e/rsrc/css/layout/phabricator-property-list-view.css',
- 'type' => 'css',
- 'requires' =>
- array(
- ),
- 'disk' => '/rsrc/css/layout/phabricator-property-list-view.css',
- ),
'phabricator-remarkup-css' =>
array(
- 'uri' => '/res/7e8988dd/rsrc/css/core/remarkup.css',
+ 'uri' => '/res/4c313572/rsrc/css/core/remarkup.css',
'type' => 'css',
'requires' =>
array(
@@ -3474,7 +3527,7 @@
),
'phabricator-timeline-view-css' =>
array(
- 'uri' => '/res/49ac2513/rsrc/css/layout/phabricator-timeline-view.css',
+ 'uri' => '/res/d139291d/rsrc/css/layout/phabricator-timeline-view.css',
'type' => 'css',
'requires' =>
array(
@@ -3733,7 +3786,7 @@
),
'phui-button-css' =>
array(
- 'uri' => '/res/3718b375/rsrc/css/phui/phui-button.css',
+ 'uri' => '/res/3b21ca84/rsrc/css/phui/phui-button.css',
'type' => 'css',
'requires' =>
array(
@@ -3742,7 +3795,7 @@
),
'phui-document-view-css' =>
array(
- 'uri' => '/res/40e39942/rsrc/css/phui/phui-document.css',
+ 'uri' => '/res/cac7a825/rsrc/css/phui/phui-document.css',
'type' => 'css',
'requires' =>
array(
@@ -3778,7 +3831,7 @@
),
'phui-header-view-css' =>
array(
- 'uri' => '/res/94208bd6/rsrc/css/phui/phui-header-view.css',
+ 'uri' => '/res/d6ca0939/rsrc/css/phui/phui-header-view.css',
'type' => 'css',
'requires' =>
array(
@@ -3796,7 +3849,7 @@
),
'phui-list-view-css' =>
array(
- 'uri' => '/res/3235e888/rsrc/css/phui/phui-list.css',
+ 'uri' => '/res/c748be1f/rsrc/css/phui/phui-list.css',
'type' => 'css',
'requires' =>
array(
@@ -3805,7 +3858,7 @@
),
'phui-object-box-css' =>
array(
- 'uri' => '/res/9e56634a/rsrc/css/phui/phui-object-box.css',
+ 'uri' => '/res/8504279f/rsrc/css/phui/phui-object-box.css',
'type' => 'css',
'requires' =>
array(
@@ -3814,7 +3867,7 @@
),
'phui-object-item-list-view-css' =>
array(
- 'uri' => '/res/71f7f081/rsrc/css/phui/phui-object-item-list-view.css',
+ 'uri' => '/res/c3a0ea74/rsrc/css/phui/phui-object-item-list-view.css',
'type' => 'css',
'requires' =>
array(
@@ -3830,6 +3883,15 @@
),
'disk' => '/rsrc/css/phui/phui-pinboard-view.css',
),
+ 'phui-property-list-view-css' =>
+ array(
+ 'uri' => '/res/7c39fbe1/rsrc/css/phui/phui-property-list-view.css',
+ 'type' => 'css',
+ 'requires' =>
+ array(
+ ),
+ 'disk' => '/rsrc/css/phui/phui-property-list-view.css',
+ ),
'phui-remarkup-preview-css' =>
array(
'uri' => '/res/50fa4178/rsrc/css/phui/phui-remarkup-preview.css',
@@ -3850,7 +3912,7 @@
),
'phui-status-list-view-css' =>
array(
- 'uri' => '/res/02351f1a/rsrc/css/phui/phui-status.css',
+ 'uri' => '/res/3410386e/rsrc/css/phui/phui-status.css',
'type' => 'css',
'requires' =>
array(
@@ -3884,6 +3946,24 @@
),
'disk' => '/rsrc/css/phui/phui-workpanel-view.css',
),
+ 'policy-css' =>
+ array(
+ 'uri' => '/res/51325bff/rsrc/css/application/policy/policy.css',
+ 'type' => 'css',
+ 'requires' =>
+ array(
+ ),
+ 'disk' => '/rsrc/css/application/policy/policy.css',
+ ),
+ 'policy-edit-css' =>
+ array(
+ 'uri' => '/res/1e2a2b5e/rsrc/css/application/policy/policy-edit.css',
+ 'type' => 'css',
+ 'requires' =>
+ array(
+ ),
+ 'disk' => '/rsrc/css/application/policy/policy-edit.css',
+ ),
'ponder-comment-table-css' =>
array(
'uri' => '/res/4aa4b865/rsrc/css/application/ponder/comments.css',
@@ -4102,7 +4182,7 @@
),
'sprite-icons-css' =>
array(
- 'uri' => '/res/6e245ca0/rsrc/css/sprite-icons.css',
+ 'uri' => '/res/67a7bd90/rsrc/css/sprite-icons.css',
'type' => 'css',
'requires' =>
array(
@@ -4111,7 +4191,7 @@
),
'sprite-login-css' =>
array(
- 'uri' => '/res/8bd33e35/rsrc/css/sprite-login.css',
+ 'uri' => '/res/48dc427d/rsrc/css/sprite-login.css',
'type' => 'css',
'requires' =>
array(
@@ -4145,9 +4225,18 @@
),
'disk' => '/rsrc/css/sprite-payments.css',
),
+ 'sprite-projects-css' =>
+ array(
+ 'uri' => '/res/3ff34b69/rsrc/css/sprite-projects.css',
+ 'type' => 'css',
+ 'requires' =>
+ array(
+ ),
+ 'disk' => '/rsrc/css/sprite-projects.css',
+ ),
'sprite-status-css' =>
array(
- 'uri' => '/res/26c51270/rsrc/css/sprite-status.css',
+ 'uri' => '/res/f8d8766d/rsrc/css/sprite-status.css',
'type' => 'css',
'requires' =>
array(
@@ -4184,7 +4273,7 @@
), array(
'packages' =>
array(
- '1fa81138' =>
+ '30de5267' =>
array(
'name' => 'core.pkg.css',
'symbols' =>
@@ -4229,14 +4318,14 @@
37 => 'phui-icon-view-css',
38 => 'phabricator-application-launch-view-css',
39 => 'phabricator-action-list-view-css',
- 40 => 'phabricator-property-list-view-css',
+ 40 => 'phui-property-list-view-css',
41 => 'phabricator-tag-view-css',
42 => 'phui-list-view-css',
),
- 'uri' => '/res/pkg/1fa81138/core.pkg.css',
+ 'uri' => '/res/pkg/30de5267/core.pkg.css',
'type' => 'css',
),
- '64eeda79' =>
+ '6041c6c8' =>
array(
'name' => 'core.pkg.js',
'symbols' =>
@@ -4281,7 +4370,7 @@
37 => 'javelin-color',
38 => 'javelin-fx',
),
- 'uri' => '/res/pkg/64eeda79/core.pkg.js',
+ 'uri' => '/res/pkg/6041c6c8/core.pkg.js',
'type' => 'js',
),
'4ccfeb47' =>
@@ -4295,7 +4384,7 @@
'uri' => '/res/pkg/4ccfeb47/darkconsole.pkg.js',
'type' => 'js',
),
- 'b55e602f' =>
+ '7cd7e387' =>
array(
'name' => 'differential.pkg.css',
'symbols' =>
@@ -4314,7 +4403,7 @@
11 => 'differential-local-commits-view-css',
12 => 'inline-comment-summary-css',
),
- 'uri' => '/res/pkg/b55e602f/differential.pkg.css',
+ 'uri' => '/res/pkg/7cd7e387/differential.pkg.css',
'type' => 'css',
),
'5e9e5c4e' =>
@@ -4345,7 +4434,7 @@
'uri' => '/res/pkg/5e9e5c4e/differential.pkg.js',
'type' => 'js',
),
- 'c8ce2d88' =>
+ '270f4eb4' =>
array(
'name' => 'diffusion.pkg.css',
'symbols' =>
@@ -4353,7 +4442,7 @@
0 => 'diffusion-commit-view-css',
1 => 'diffusion-icons-css',
),
- 'uri' => '/res/pkg/c8ce2d88/diffusion.pkg.css',
+ 'uri' => '/res/pkg/270f4eb4/diffusion.pkg.css',
'type' => 'css',
),
96909266 =>
@@ -4368,7 +4457,7 @@
'uri' => '/res/pkg/96909266/diffusion.pkg.js',
'type' => 'js',
),
- '9564fa17' =>
+ '3e3be199' =>
array(
'name' => 'javelin.pkg.js',
'symbols' =>
@@ -4394,7 +4483,7 @@
18 => 'javelin-tokenizer',
19 => 'javelin-history',
),
- 'uri' => '/res/pkg/9564fa17/javelin.pkg.js',
+ 'uri' => '/res/pkg/3e3be199/javelin.pkg.js',
'type' => 'js',
),
49898640 =>
@@ -4425,40 +4514,40 @@
),
'reverse' =>
array(
- 'aphront-dialog-view-css' => '1fa81138',
- 'aphront-error-view-css' => '1fa81138',
- 'aphront-list-filter-view-css' => '1fa81138',
- 'aphront-pager-view-css' => '1fa81138',
- 'aphront-panel-view-css' => '1fa81138',
- 'aphront-table-view-css' => '1fa81138',
- 'aphront-tokenizer-control-css' => '1fa81138',
- 'aphront-tooltip-css' => '1fa81138',
- 'aphront-typeahead-control-css' => '1fa81138',
- 'differential-changeset-view-css' => 'b55e602f',
- 'differential-core-view-css' => 'b55e602f',
+ 'aphront-dialog-view-css' => '30de5267',
+ 'aphront-error-view-css' => '30de5267',
+ 'aphront-list-filter-view-css' => '30de5267',
+ 'aphront-pager-view-css' => '30de5267',
+ 'aphront-panel-view-css' => '30de5267',
+ 'aphront-table-view-css' => '30de5267',
+ 'aphront-tokenizer-control-css' => '30de5267',
+ 'aphront-tooltip-css' => '30de5267',
+ 'aphront-typeahead-control-css' => '30de5267',
+ 'differential-changeset-view-css' => '7cd7e387',
+ 'differential-core-view-css' => '7cd7e387',
'differential-inline-comment-editor' => '5e9e5c4e',
- 'differential-local-commits-view-css' => 'b55e602f',
- 'differential-results-table-css' => 'b55e602f',
- 'differential-revision-add-comment-css' => 'b55e602f',
- 'differential-revision-comment-css' => 'b55e602f',
- 'differential-revision-comment-list-css' => 'b55e602f',
- 'differential-revision-history-css' => 'b55e602f',
- 'differential-revision-list-css' => 'b55e602f',
- 'differential-table-of-contents-css' => 'b55e602f',
- 'diffusion-commit-view-css' => 'c8ce2d88',
- 'diffusion-icons-css' => 'c8ce2d88',
- 'global-drag-and-drop-css' => '1fa81138',
- 'inline-comment-summary-css' => 'b55e602f',
- 'javelin-aphlict' => '64eeda79',
- 'javelin-behavior' => '9564fa17',
- 'javelin-behavior-aphlict-dropdown' => '64eeda79',
- 'javelin-behavior-aphlict-listen' => '64eeda79',
- 'javelin-behavior-aphront-basic-tokenizer' => '64eeda79',
+ 'differential-local-commits-view-css' => '7cd7e387',
+ 'differential-results-table-css' => '7cd7e387',
+ 'differential-revision-add-comment-css' => '7cd7e387',
+ 'differential-revision-comment-css' => '7cd7e387',
+ 'differential-revision-comment-list-css' => '7cd7e387',
+ 'differential-revision-history-css' => '7cd7e387',
+ 'differential-revision-list-css' => '7cd7e387',
+ 'differential-table-of-contents-css' => '7cd7e387',
+ 'diffusion-commit-view-css' => '270f4eb4',
+ 'diffusion-icons-css' => '270f4eb4',
+ 'global-drag-and-drop-css' => '30de5267',
+ 'inline-comment-summary-css' => '7cd7e387',
+ 'javelin-aphlict' => '6041c6c8',
+ 'javelin-behavior' => '3e3be199',
+ 'javelin-behavior-aphlict-dropdown' => '6041c6c8',
+ 'javelin-behavior-aphlict-listen' => '6041c6c8',
+ 'javelin-behavior-aphront-basic-tokenizer' => '6041c6c8',
'javelin-behavior-aphront-drag-and-drop-textarea' => '5e9e5c4e',
- 'javelin-behavior-aphront-form-disable-on-submit' => '64eeda79',
+ 'javelin-behavior-aphront-form-disable-on-submit' => '6041c6c8',
'javelin-behavior-audit-preview' => '96909266',
'javelin-behavior-dark-console' => '4ccfeb47',
- 'javelin-behavior-device' => '64eeda79',
+ 'javelin-behavior-device' => '6041c6c8',
'javelin-behavior-differential-accept-with-errors' => '5e9e5c4e',
'javelin-behavior-differential-add-reviewers-and-ccs' => '5e9e5c4e',
'javelin-behavior-differential-comment-jump' => '5e9e5c4e',
@@ -4474,104 +4563,104 @@
'javelin-behavior-diffusion-commit-graph' => '96909266',
'javelin-behavior-diffusion-pull-lastmodified' => '96909266',
'javelin-behavior-error-log' => '4ccfeb47',
- 'javelin-behavior-global-drag-and-drop' => '64eeda79',
- 'javelin-behavior-history-install' => '64eeda79',
- 'javelin-behavior-konami' => '64eeda79',
- 'javelin-behavior-lightbox-attachments' => '64eeda79',
+ 'javelin-behavior-global-drag-and-drop' => '6041c6c8',
+ 'javelin-behavior-history-install' => '6041c6c8',
+ 'javelin-behavior-konami' => '6041c6c8',
+ 'javelin-behavior-lightbox-attachments' => '6041c6c8',
'javelin-behavior-load-blame' => '5e9e5c4e',
'javelin-behavior-maniphest-batch-selector' => '0a694954',
'javelin-behavior-maniphest-subpriority-editor' => '0a694954',
'javelin-behavior-maniphest-transaction-controls' => '0a694954',
'javelin-behavior-maniphest-transaction-expand' => '0a694954',
'javelin-behavior-maniphest-transaction-preview' => '0a694954',
- 'javelin-behavior-phabricator-active-nav' => '64eeda79',
- 'javelin-behavior-phabricator-autofocus' => '64eeda79',
- 'javelin-behavior-phabricator-gesture' => '64eeda79',
- 'javelin-behavior-phabricator-hovercards' => '64eeda79',
- 'javelin-behavior-phabricator-keyboard-shortcuts' => '64eeda79',
- 'javelin-behavior-phabricator-nav' => '64eeda79',
+ 'javelin-behavior-phabricator-active-nav' => '6041c6c8',
+ 'javelin-behavior-phabricator-autofocus' => '6041c6c8',
+ 'javelin-behavior-phabricator-gesture' => '6041c6c8',
+ 'javelin-behavior-phabricator-hovercards' => '6041c6c8',
+ 'javelin-behavior-phabricator-keyboard-shortcuts' => '6041c6c8',
+ 'javelin-behavior-phabricator-nav' => '6041c6c8',
'javelin-behavior-phabricator-object-selector' => '5e9e5c4e',
- 'javelin-behavior-phabricator-oncopy' => '64eeda79',
- 'javelin-behavior-phabricator-remarkup-assist' => '64eeda79',
- 'javelin-behavior-phabricator-reveal-content' => '64eeda79',
- 'javelin-behavior-phabricator-search-typeahead' => '64eeda79',
- 'javelin-behavior-phabricator-tooltips' => '64eeda79',
- 'javelin-behavior-phabricator-watch-anchor' => '64eeda79',
- 'javelin-behavior-refresh-csrf' => '64eeda79',
+ 'javelin-behavior-phabricator-oncopy' => '6041c6c8',
+ 'javelin-behavior-phabricator-remarkup-assist' => '6041c6c8',
+ 'javelin-behavior-phabricator-reveal-content' => '6041c6c8',
+ 'javelin-behavior-phabricator-search-typeahead' => '6041c6c8',
+ 'javelin-behavior-phabricator-tooltips' => '6041c6c8',
+ 'javelin-behavior-phabricator-watch-anchor' => '6041c6c8',
+ 'javelin-behavior-refresh-csrf' => '6041c6c8',
'javelin-behavior-repository-crossreference' => '5e9e5c4e',
- 'javelin-behavior-toggle-class' => '64eeda79',
- 'javelin-behavior-workflow' => '64eeda79',
- 'javelin-color' => '64eeda79',
- 'javelin-dom' => '9564fa17',
- 'javelin-event' => '9564fa17',
- 'javelin-fx' => '64eeda79',
- 'javelin-history' => '9564fa17',
- 'javelin-install' => '9564fa17',
- 'javelin-json' => '9564fa17',
- 'javelin-mask' => '9564fa17',
- 'javelin-request' => '9564fa17',
- 'javelin-resource' => '9564fa17',
- 'javelin-stratcom' => '9564fa17',
- 'javelin-tokenizer' => '9564fa17',
- 'javelin-typeahead' => '9564fa17',
- 'javelin-typeahead-normalizer' => '9564fa17',
- 'javelin-typeahead-ondemand-source' => '9564fa17',
- 'javelin-typeahead-preloaded-source' => '9564fa17',
- 'javelin-typeahead-source' => '9564fa17',
- 'javelin-uri' => '9564fa17',
- 'javelin-util' => '9564fa17',
- 'javelin-vector' => '9564fa17',
- 'javelin-workflow' => '9564fa17',
- 'lightbox-attachment-css' => '1fa81138',
+ 'javelin-behavior-toggle-class' => '6041c6c8',
+ 'javelin-behavior-workflow' => '6041c6c8',
+ 'javelin-color' => '6041c6c8',
+ 'javelin-dom' => '3e3be199',
+ 'javelin-event' => '3e3be199',
+ 'javelin-fx' => '6041c6c8',
+ 'javelin-history' => '3e3be199',
+ 'javelin-install' => '3e3be199',
+ 'javelin-json' => '3e3be199',
+ 'javelin-mask' => '3e3be199',
+ 'javelin-request' => '3e3be199',
+ 'javelin-resource' => '3e3be199',
+ 'javelin-stratcom' => '3e3be199',
+ 'javelin-tokenizer' => '3e3be199',
+ 'javelin-typeahead' => '3e3be199',
+ 'javelin-typeahead-normalizer' => '3e3be199',
+ 'javelin-typeahead-ondemand-source' => '3e3be199',
+ 'javelin-typeahead-preloaded-source' => '3e3be199',
+ 'javelin-typeahead-source' => '3e3be199',
+ 'javelin-uri' => '3e3be199',
+ 'javelin-util' => '3e3be199',
+ 'javelin-vector' => '3e3be199',
+ 'javelin-workflow' => '3e3be199',
+ 'lightbox-attachment-css' => '30de5267',
'maniphest-task-summary-css' => '49898640',
- 'phabricator-action-list-view-css' => '1fa81138',
- 'phabricator-application-launch-view-css' => '1fa81138',
- 'phabricator-busy' => '64eeda79',
- 'phabricator-content-source-view-css' => 'b55e602f',
- 'phabricator-core-css' => '1fa81138',
- 'phabricator-crumbs-view-css' => '1fa81138',
+ 'phabricator-action-list-view-css' => '30de5267',
+ 'phabricator-application-launch-view-css' => '30de5267',
+ 'phabricator-busy' => '6041c6c8',
+ 'phabricator-content-source-view-css' => '7cd7e387',
+ 'phabricator-core-css' => '30de5267',
+ 'phabricator-crumbs-view-css' => '30de5267',
'phabricator-drag-and-drop-file-upload' => '5e9e5c4e',
- 'phabricator-dropdown-menu' => '64eeda79',
- 'phabricator-file-upload' => '64eeda79',
- 'phabricator-filetree-view-css' => '1fa81138',
- 'phabricator-flag-css' => '1fa81138',
- 'phabricator-hovercard' => '64eeda79',
- 'phabricator-jump-nav' => '1fa81138',
- 'phabricator-keyboard-shortcut' => '64eeda79',
- 'phabricator-keyboard-shortcut-manager' => '64eeda79',
- 'phabricator-main-menu-view' => '1fa81138',
- 'phabricator-menu-item' => '64eeda79',
- 'phabricator-nav-view-css' => '1fa81138',
- 'phabricator-notification' => '64eeda79',
- 'phabricator-notification-css' => '1fa81138',
- 'phabricator-notification-menu-css' => '1fa81138',
- 'phabricator-object-selector-css' => 'b55e602f',
- 'phabricator-phtize' => '64eeda79',
- 'phabricator-prefab' => '64eeda79',
+ 'phabricator-dropdown-menu' => '6041c6c8',
+ 'phabricator-file-upload' => '6041c6c8',
+ 'phabricator-filetree-view-css' => '30de5267',
+ 'phabricator-flag-css' => '30de5267',
+ 'phabricator-hovercard' => '6041c6c8',
+ 'phabricator-jump-nav' => '30de5267',
+ 'phabricator-keyboard-shortcut' => '6041c6c8',
+ 'phabricator-keyboard-shortcut-manager' => '6041c6c8',
+ 'phabricator-main-menu-view' => '30de5267',
+ 'phabricator-menu-item' => '6041c6c8',
+ 'phabricator-nav-view-css' => '30de5267',
+ 'phabricator-notification' => '6041c6c8',
+ 'phabricator-notification-css' => '30de5267',
+ 'phabricator-notification-menu-css' => '30de5267',
+ 'phabricator-object-selector-css' => '7cd7e387',
+ 'phabricator-phtize' => '6041c6c8',
+ 'phabricator-prefab' => '6041c6c8',
'phabricator-project-tag-css' => '49898640',
- 'phabricator-property-list-view-css' => '1fa81138',
- 'phabricator-remarkup-css' => '1fa81138',
+ 'phabricator-remarkup-css' => '30de5267',
'phabricator-shaped-request' => '5e9e5c4e',
- 'phabricator-side-menu-view-css' => '1fa81138',
- 'phabricator-standard-page-view' => '1fa81138',
- 'phabricator-tag-view-css' => '1fa81138',
- 'phabricator-textareautils' => '64eeda79',
- 'phabricator-tooltip' => '64eeda79',
- 'phabricator-transaction-view-css' => '1fa81138',
- 'phabricator-zindex-css' => '1fa81138',
- 'phui-button-css' => '1fa81138',
- 'phui-form-css' => '1fa81138',
- 'phui-form-view-css' => '1fa81138',
- 'phui-header-view-css' => '1fa81138',
- 'phui-icon-view-css' => '1fa81138',
- 'phui-list-view-css' => '1fa81138',
- 'phui-object-item-list-view-css' => '1fa81138',
- 'phui-spacing-css' => '1fa81138',
- 'sprite-apps-large-css' => '1fa81138',
- 'sprite-gradient-css' => '1fa81138',
- 'sprite-icons-css' => '1fa81138',
- 'sprite-menu-css' => '1fa81138',
- 'sprite-status-css' => '1fa81138',
- 'syntax-highlighting-css' => '1fa81138',
+ 'phabricator-side-menu-view-css' => '30de5267',
+ 'phabricator-standard-page-view' => '30de5267',
+ 'phabricator-tag-view-css' => '30de5267',
+ 'phabricator-textareautils' => '6041c6c8',
+ 'phabricator-tooltip' => '6041c6c8',
+ 'phabricator-transaction-view-css' => '30de5267',
+ 'phabricator-zindex-css' => '30de5267',
+ 'phui-button-css' => '30de5267',
+ 'phui-form-css' => '30de5267',
+ 'phui-form-view-css' => '30de5267',
+ 'phui-header-view-css' => '30de5267',
+ 'phui-icon-view-css' => '30de5267',
+ 'phui-list-view-css' => '30de5267',
+ 'phui-object-item-list-view-css' => '30de5267',
+ 'phui-property-list-view-css' => '30de5267',
+ 'phui-spacing-css' => '30de5267',
+ 'sprite-apps-large-css' => '30de5267',
+ 'sprite-gradient-css' => '30de5267',
+ 'sprite-icons-css' => '30de5267',
+ 'sprite-menu-css' => '30de5267',
+ 'sprite-status-css' => '30de5267',
+ 'syntax-highlighting-css' => '30de5267',
),
));
Index: src/__phutil_library_map__.php
===================================================================
--- src/__phutil_library_map__.php
+++ src/__phutil_library_map__.php
@@ -74,7 +74,6 @@
'AphrontPlainTextResponse' => 'aphront/response/AphrontPlainTextResponse.php',
'AphrontProgressBarView' => 'view/widget/bars/AphrontProgressBarView.php',
'AphrontProxyResponse' => 'aphront/response/AphrontProxyResponse.php',
- 'AphrontRedirectException' => 'aphront/exception/AphrontRedirectException.php',
'AphrontRedirectResponse' => 'aphront/response/AphrontRedirectResponse.php',
'AphrontReloadResponse' => 'aphront/response/AphrontReloadResponse.php',
'AphrontRequest' => 'aphront/AphrontRequest.php',
@@ -310,6 +309,7 @@
'DifferentialBranchFieldSpecification' => 'applications/differential/field/specification/DifferentialBranchFieldSpecification.php',
'DifferentialCCWelcomeMail' => 'applications/differential/mail/DifferentialCCWelcomeMail.php',
'DifferentialCCsFieldSpecification' => 'applications/differential/field/specification/DifferentialCCsFieldSpecification.php',
+ 'DifferentialCapabilityDefaultView' => 'applications/differential/capability/DifferentialCapabilityDefaultView.php',
'DifferentialChangeType' => 'applications/differential/constants/DifferentialChangeType.php',
'DifferentialChangeset' => 'applications/differential/storage/DifferentialChangeset.php',
'DifferentialChangesetDetailView' => 'applications/differential/view/DifferentialChangesetDetailView.php',
@@ -392,6 +392,7 @@
'DifferentialPathFieldSpecification' => 'applications/differential/field/specification/DifferentialPathFieldSpecification.php',
'DifferentialPeopleMenuEventListener' => 'applications/differential/events/DifferentialPeopleMenuEventListener.php',
'DifferentialPrimaryPaneView' => 'applications/differential/view/DifferentialPrimaryPaneView.php',
+ 'DifferentialProjectReviewersFieldSpecification' => 'applications/differential/field/specification/DifferentialProjectReviewersFieldSpecification.php',
'DifferentialRawDiffRenderer' => 'applications/differential/render/DifferentialRawDiffRenderer.php',
'DifferentialReleephRequestFieldSpecification' => 'applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php',
'DifferentialRemarkupRule' => 'applications/differential/remarkup/DifferentialRemarkupRule.php',
@@ -405,6 +406,7 @@
'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php',
'DifferentialReviewerStatus' => 'applications/differential/constants/DifferentialReviewerStatus.php',
'DifferentialReviewersFieldSpecification' => 'applications/differential/field/specification/DifferentialReviewersFieldSpecification.php',
+ 'DifferentialReviewersView' => 'applications/differential/view/DifferentialReviewersView.php',
'DifferentialRevision' => 'applications/differential/storage/DifferentialRevision.php',
'DifferentialRevisionCommentListView' => 'applications/differential/view/DifferentialRevisionCommentListView.php',
'DifferentialRevisionCommentView' => 'applications/differential/view/DifferentialRevisionCommentView.php',
@@ -624,13 +626,14 @@
'HeraldAction' => 'applications/herald/storage/HeraldAction.php',
'HeraldAdapter' => 'applications/herald/adapter/HeraldAdapter.php',
'HeraldApplyTranscript' => 'applications/herald/storage/transcript/HeraldApplyTranscript.php',
+ 'HeraldCapabilityManageGlobalRules' => 'applications/herald/capability/HeraldCapabilityManageGlobalRules.php',
'HeraldCommitAdapter' => 'applications/herald/adapter/HeraldCommitAdapter.php',
'HeraldCondition' => 'applications/herald/storage/HeraldCondition.php',
'HeraldConditionTranscript' => 'applications/herald/storage/transcript/HeraldConditionTranscript.php',
'HeraldController' => 'applications/herald/controller/HeraldController.php',
'HeraldDAO' => 'applications/herald/storage/HeraldDAO.php',
- 'HeraldDeleteController' => 'applications/herald/controller/HeraldDeleteController.php',
'HeraldDifferentialRevisionAdapter' => 'applications/herald/adapter/HeraldDifferentialRevisionAdapter.php',
+ 'HeraldDisableController' => 'applications/herald/controller/HeraldDisableController.php',
'HeraldEditLogQuery' => 'applications/herald/query/HeraldEditLogQuery.php',
'HeraldEffect' => 'applications/herald/engine/HeraldEffect.php',
'HeraldEngine' => 'applications/herald/engine/HeraldEngine.php',
@@ -649,6 +652,7 @@
'HeraldRuleEdit' => 'applications/herald/storage/HeraldRuleEdit.php',
'HeraldRuleEditHistoryController' => 'applications/herald/controller/HeraldRuleEditHistoryController.php',
'HeraldRuleEditHistoryView' => 'applications/herald/view/HeraldRuleEditHistoryView.php',
+ 'HeraldRuleEditor' => 'applications/herald/editor/HeraldRuleEditor.php',
'HeraldRuleListController' => 'applications/herald/controller/HeraldRuleListController.php',
'HeraldRuleQuery' => 'applications/herald/query/HeraldRuleQuery.php',
'HeraldRuleSearchEngine' => 'applications/herald/query/HeraldRuleSearchEngine.php',
@@ -658,9 +662,11 @@
'HeraldRuleTypeConfig' => 'applications/herald/config/HeraldRuleTypeConfig.php',
'HeraldRuleViewController' => 'applications/herald/controller/HeraldRuleViewController.php',
'HeraldTestConsoleController' => 'applications/herald/controller/HeraldTestConsoleController.php',
+ 'HeraldTransactionQuery' => 'applications/herald/query/HeraldTransactionQuery.php',
'HeraldTranscript' => 'applications/herald/storage/transcript/HeraldTranscript.php',
'HeraldTranscriptController' => 'applications/herald/controller/HeraldTranscriptController.php',
'HeraldTranscriptListController' => 'applications/herald/controller/HeraldTranscriptListController.php',
+ 'HeraldTranscriptQuery' => 'applications/herald/query/HeraldTranscriptQuery.php',
'Javelin' => 'infrastructure/javelin/Javelin.php',
'JavelinReactorExample' => 'applications/uiexample/examples/JavelinReactorExample.php',
'JavelinUIExample' => 'applications/uiexample/examples/JavelinUIExample.php',
@@ -700,6 +706,8 @@
'LiskRawMigrationIterator' => 'infrastructure/storage/lisk/LiskRawMigrationIterator.php',
'ManiphestAction' => 'applications/maniphest/constants/ManiphestAction.php',
'ManiphestBatchEditController' => 'applications/maniphest/controller/ManiphestBatchEditController.php',
+ 'ManiphestCapabilityDefaultEdit' => 'applications/maniphest/capability/ManiphestCapabilityDefaultEdit.php',
+ 'ManiphestCapabilityDefaultView' => 'applications/maniphest/capability/ManiphestCapabilityDefaultView.php',
'ManiphestConfiguredCustomField' => 'applications/maniphest/field/ManiphestConfiguredCustomField.php',
'ManiphestConstants' => 'applications/maniphest/constants/ManiphestConstants.php',
'ManiphestController' => 'applications/maniphest/controller/ManiphestController.php',
@@ -780,6 +788,9 @@
'PHUIPagedFormView' => 'view/form/PHUIPagedFormView.php',
'PHUIPinboardItemView' => 'view/phui/PHUIPinboardItemView.php',
'PHUIPinboardView' => 'view/phui/PHUIPinboardView.php',
+ 'PHUIPropertyGroupView' => 'view/phui/PHUIPropertyGroupView.php',
+ 'PHUIPropertyListExample' => 'applications/uiexample/examples/PHUIPropertyListExample.php',
+ 'PHUIPropertyListView' => 'view/phui/PHUIPropertyListView.php',
'PHUIRemarkupPreviewPanel' => 'view/phui/PHUIRemarkupPreviewPanel.php',
'PHUIStatusItemView' => 'view/phui/PHUIStatusItemView.php',
'PHUIStatusListView' => 'view/phui/PHUIStatusListView.php',
@@ -792,6 +803,7 @@
'PackageDeleteMail' => 'applications/owners/mail/PackageDeleteMail.php',
'PackageMail' => 'applications/owners/mail/PackageMail.php',
'PackageModifyMail' => 'applications/owners/mail/PackageModifyMail.php',
+ 'PasteCapabilityDefaultView' => 'applications/paste/capability/PasteCapabilityDefaultView.php',
'PasteCreateMailReceiver' => 'applications/paste/mail/PasteCreateMailReceiver.php',
'PasteEmbedView' => 'applications/paste/view/PasteEmbedView.php',
'PasteMockMailReceiver' => 'applications/paste/mail/PasteMockMailReceiver.php',
@@ -951,6 +963,7 @@
'PhabricatorAuthProviderOAuthGoogle' => 'applications/auth/provider/PhabricatorAuthProviderOAuthGoogle.php',
'PhabricatorAuthProviderOAuthTwitch' => 'applications/auth/provider/PhabricatorAuthProviderOAuthTwitch.php',
'PhabricatorAuthProviderPassword' => 'applications/auth/provider/PhabricatorAuthProviderPassword.php',
+ 'PhabricatorAuthProviderPersona' => 'applications/auth/provider/PhabricatorAuthProviderPersona.php',
'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php',
'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php',
'PhabricatorAuthUnlinkController' => 'applications/auth/controller/PhabricatorAuthUnlinkController.php',
@@ -1055,6 +1068,7 @@
'PhabricatorController' => 'applications/base/controller/PhabricatorController.php',
'PhabricatorCoreConfigOptions' => 'applications/config/option/PhabricatorCoreConfigOptions.php',
'PhabricatorCountdown' => 'applications/countdown/storage/PhabricatorCountdown.php',
+ 'PhabricatorCountdownCapabilityDefaultView' => 'applications/countdown/capability/PhabricatorCountdownCapabilityDefaultView.php',
'PhabricatorCountdownController' => 'applications/countdown/controller/PhabricatorCountdownController.php',
'PhabricatorCountdownDAO' => 'applications/countdown/storage/PhabricatorCountdownDAO.php',
'PhabricatorCountdownDeleteController' => 'applications/countdown/controller/PhabricatorCountdownDeleteController.php',
@@ -1280,6 +1294,7 @@
'PhabricatorLocalTimeTestCase' => 'view/__tests__/PhabricatorLocalTimeTestCase.php',
'PhabricatorLogoutController' => 'applications/auth/controller/PhabricatorLogoutController.php',
'PhabricatorMacroAudioController' => 'applications/macro/controller/PhabricatorMacroAudioController.php',
+ 'PhabricatorMacroCapabilityManage' => 'applications/macro/capability/PhabricatorMacroCapabilityManage.php',
'PhabricatorMacroCommentController' => 'applications/macro/controller/PhabricatorMacroCommentController.php',
'PhabricatorMacroConfigOptions' => 'applications/macro/config/PhabricatorMacroConfigOptions.php',
'PhabricatorMacroController' => 'applications/macro/controller/PhabricatorMacroController.php',
@@ -1464,14 +1479,19 @@
'PhabricatorPhrequentConfigOptions' => 'applications/phrequent/config/PhabricatorPhrequentConfigOptions.php',
'PhabricatorPhrictionConfigOptions' => 'applications/phriction/config/PhabricatorPhrictionConfigOptions.php',
'PhabricatorPolicies' => 'applications/policy/constants/PhabricatorPolicies.php',
- 'PhabricatorPolicy' => 'applications/policy/filter/PhabricatorPolicy.php',
+ 'PhabricatorPolicy' => 'applications/policy/storage/PhabricatorPolicy.php',
'PhabricatorPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorPolicyAwareQuery.php',
'PhabricatorPolicyAwareTestQuery' => 'applications/policy/__tests__/PhabricatorPolicyAwareTestQuery.php',
- 'PhabricatorPolicyCapability' => 'applications/policy/constants/PhabricatorPolicyCapability.php',
+ 'PhabricatorPolicyCapability' => 'applications/policy/capability/PhabricatorPolicyCapability.php',
+ 'PhabricatorPolicyCapabilityCanEdit' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanEdit.php',
+ 'PhabricatorPolicyCapabilityCanJoin' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanJoin.php',
+ 'PhabricatorPolicyCapabilityCanView' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanView.php',
'PhabricatorPolicyConfigOptions' => 'applications/policy/config/PhabricatorPolicyConfigOptions.php',
'PhabricatorPolicyConstants' => 'applications/policy/constants/PhabricatorPolicyConstants.php',
'PhabricatorPolicyController' => 'applications/policy/controller/PhabricatorPolicyController.php',
+ 'PhabricatorPolicyDAO' => 'applications/policy/storage/PhabricatorPolicyDAO.php',
'PhabricatorPolicyDataTestCase' => 'applications/policy/__tests__/PhabricatorPolicyDataTestCase.php',
+ 'PhabricatorPolicyEditController' => 'applications/policy/controller/PhabricatorPolicyEditController.php',
'PhabricatorPolicyException' => 'applications/policy/exception/PhabricatorPolicyException.php',
'PhabricatorPolicyExplainController' => 'applications/policy/controller/PhabricatorPolicyExplainController.php',
'PhabricatorPolicyFilter' => 'applications/policy/filter/PhabricatorPolicyFilter.php',
@@ -1479,7 +1499,13 @@
'PhabricatorPolicyManagementShowWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementShowWorkflow.php',
'PhabricatorPolicyManagementUnlockWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementUnlockWorkflow.php',
'PhabricatorPolicyManagementWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementWorkflow.php',
+ 'PhabricatorPolicyPHIDTypePolicy' => 'applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php',
'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php',
+ 'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php',
+ 'PhabricatorPolicyRuleAdministrators' => 'applications/policy/rule/PhabricatorPolicyRuleAdministrators.php',
+ 'PhabricatorPolicyRuleLunarPhase' => 'applications/policy/rule/PhabricatorPolicyRuleLunarPhase.php',
+ 'PhabricatorPolicyRuleProjects' => 'applications/policy/rule/PhabricatorPolicyRuleProjects.php',
+ 'PhabricatorPolicyRuleUsers' => 'applications/policy/rule/PhabricatorPolicyRuleUsers.php',
'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php',
'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php',
'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php',
@@ -1505,13 +1531,14 @@
'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php',
'PhabricatorProjectTransactionType' => 'applications/project/constants/PhabricatorProjectTransactionType.php',
'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php',
- 'PhabricatorPropertyListExample' => 'applications/uiexample/examples/PhabricatorPropertyListExample.php',
- 'PhabricatorPropertyListView' => 'view/layout/PhabricatorPropertyListView.php',
'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php',
'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php',
'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php',
'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php',
'PhabricatorRegistrationProfile' => 'applications/people/storage/PhabricatorRegistrationProfile.php',
+ 'PhabricatorRemarkupBlockInterpreterCowsay' => 'infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterCowsay.php',
+ 'PhabricatorRemarkupBlockInterpreterFiglet' => 'infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterFiglet.php',
+ 'PhabricatorRemarkupBlockInterpreterGraphviz' => 'infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterGraphviz.php',
'PhabricatorRemarkupControl' => 'view/form/control/PhabricatorRemarkupControl.php',
'PhabricatorRemarkupRuleEmbedFile' => 'applications/files/remarkup/PhabricatorRemarkupRuleEmbedFile.php',
'PhabricatorRemarkupRuleImageMacro' => 'applications/macro/remarkup/PhabricatorRemarkupRuleImageMacro.php',
@@ -1642,6 +1669,7 @@
'PhabricatorSetupIssue' => 'applications/config/issue/PhabricatorSetupIssue.php',
'PhabricatorSetupIssueExample' => 'applications/uiexample/examples/PhabricatorSetupIssueExample.php',
'PhabricatorSetupIssueView' => 'applications/config/view/PhabricatorSetupIssueView.php',
+ 'PhabricatorSlowvoteCapabilityDefaultView' => 'applications/slowvote/capability/PhabricatorSlowvoteCapabilityDefaultView.php',
'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/PhabricatorSlowvoteChoice.php',
'PhabricatorSlowvoteComment' => 'applications/slowvote/storage/PhabricatorSlowvoteComment.php',
'PhabricatorSlowvoteCommentController' => 'applications/slowvote/controller/PhabricatorSlowvoteCommentController.php',
@@ -2001,6 +2029,7 @@
'PonderVote' => 'applications/ponder/constants/PonderVote.php',
'PonderVoteEditor' => 'applications/ponder/editor/PonderVoteEditor.php',
'PonderVoteSaveController' => 'applications/ponder/controller/PonderVoteSaveController.php',
+ 'ProjectCapabilityCreateProjects' => 'applications/project/capability/ProjectCapabilityCreateProjects.php',
'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php',
'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php',
'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php',
@@ -2172,7 +2201,6 @@
'AphrontPlainTextResponse' => 'AphrontResponse',
'AphrontProgressBarView' => 'AphrontBarView',
'AphrontProxyResponse' => 'AphrontResponse',
- 'AphrontRedirectException' => 'AphrontException',
'AphrontRedirectResponse' => 'AphrontResponse',
'AphrontReloadResponse' => 'AphrontRedirectResponse',
'AphrontRequestFailureView' => 'AphrontView',
@@ -2401,6 +2429,7 @@
'DifferentialBranchFieldSpecification' => 'DifferentialFieldSpecification',
'DifferentialCCWelcomeMail' => 'DifferentialReviewRequestMail',
'DifferentialCCsFieldSpecification' => 'DifferentialFieldSpecification',
+ 'DifferentialCapabilityDefaultView' => 'PhabricatorPolicyCapability',
'DifferentialChangeset' => 'DifferentialDAO',
'DifferentialChangesetDetailView' => 'AphrontView',
'DifferentialChangesetHTMLRenderer' => 'DifferentialChangesetRenderer',
@@ -2486,6 +2515,7 @@
'DifferentialPathFieldSpecification' => 'DifferentialFieldSpecification',
'DifferentialPeopleMenuEventListener' => 'PhutilEventListener',
'DifferentialPrimaryPaneView' => 'AphrontView',
+ 'DifferentialProjectReviewersFieldSpecification' => 'DifferentialFieldSpecification',
'DifferentialReleephRequestFieldSpecification' => 'DifferentialFieldSpecification',
'DifferentialRemarkupRule' => 'PhabricatorRemarkupRuleObject',
'DifferentialReplyHandler' => 'PhabricatorMailReplyHandler',
@@ -2496,6 +2526,7 @@
'DifferentialReviewRequestMail' => 'DifferentialMail',
'DifferentialReviewedByFieldSpecification' => 'DifferentialFieldSpecification',
'DifferentialReviewersFieldSpecification' => 'DifferentialFieldSpecification',
+ 'DifferentialReviewersView' => 'AphrontView',
'DifferentialRevision' =>
array(
0 => 'DifferentialDAO',
@@ -2715,12 +2746,13 @@
'HarbormasterScratchTable' => 'HarbormasterDAO',
'HeraldAction' => 'HeraldDAO',
'HeraldApplyTranscript' => 'HeraldDAO',
+ 'HeraldCapabilityManageGlobalRules' => 'PhabricatorPolicyCapability',
'HeraldCommitAdapter' => 'HeraldAdapter',
'HeraldCondition' => 'HeraldDAO',
'HeraldController' => 'PhabricatorController',
'HeraldDAO' => 'PhabricatorLiskDAO',
- 'HeraldDeleteController' => 'HeraldController',
'HeraldDifferentialRevisionAdapter' => 'HeraldAdapter',
+ 'HeraldDisableController' => 'HeraldController',
'HeraldEditLogQuery' => 'PhabricatorOffsetPagedQuery',
'HeraldInvalidActionException' => 'Exception',
'HeraldInvalidConditionException' => 'Exception',
@@ -2739,6 +2771,7 @@
'HeraldRuleEdit' => 'HeraldDAO',
'HeraldRuleEditHistoryController' => 'HeraldController',
'HeraldRuleEditHistoryView' => 'AphrontView',
+ 'HeraldRuleEditor' => 'PhabricatorApplicationTransactionEditor',
'HeraldRuleListController' =>
array(
0 => 'HeraldController',
@@ -2750,9 +2783,15 @@
'HeraldRuleTransactionComment' => 'PhabricatorApplicationTransactionComment',
'HeraldRuleViewController' => 'HeraldController',
'HeraldTestConsoleController' => 'HeraldController',
- 'HeraldTranscript' => 'HeraldDAO',
+ 'HeraldTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+ 'HeraldTranscript' =>
+ array(
+ 0 => 'HeraldDAO',
+ 1 => 'PhabricatorPolicyInterface',
+ ),
'HeraldTranscriptController' => 'HeraldController',
'HeraldTranscriptListController' => 'HeraldController',
+ 'HeraldTranscriptQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'JavelinReactorExample' => 'PhabricatorUIExample',
'JavelinUIExample' => 'PhabricatorUIExample',
'JavelinViewExample' => 'PhabricatorUIExample',
@@ -2802,6 +2841,8 @@
'LiskRawMigrationIterator' => 'PhutilBufferedIterator',
'ManiphestAction' => 'ManiphestConstants',
'ManiphestBatchEditController' => 'ManiphestController',
+ 'ManiphestCapabilityDefaultEdit' => 'PhabricatorPolicyCapability',
+ 'ManiphestCapabilityDefaultView' => 'PhabricatorPolicyCapability',
'ManiphestConfiguredCustomField' =>
array(
0 => 'ManiphestCustomField',
@@ -2894,6 +2935,9 @@
'PHUIPagedFormView' => 'AphrontTagView',
'PHUIPinboardItemView' => 'AphrontView',
'PHUIPinboardView' => 'AphrontView',
+ 'PHUIPropertyGroupView' => 'AphrontTagView',
+ 'PHUIPropertyListExample' => 'PhabricatorUIExample',
+ 'PHUIPropertyListView' => 'AphrontView',
'PHUIRemarkupPreviewPanel' => 'AphrontTagView',
'PHUIStatusItemView' => 'AphrontTagView',
'PHUIStatusListView' => 'AphrontTagView',
@@ -2906,6 +2950,7 @@
'PackageDeleteMail' => 'PackageMail',
'PackageMail' => 'PhabricatorMail',
'PackageModifyMail' => 'PackageMail',
+ 'PasteCapabilityDefaultView' => 'PhabricatorPolicyCapability',
'PasteCreateMailReceiver' => 'PhabricatorMailReceiver',
'PasteEmbedView' => 'AphrontView',
'PasteMockMailReceiver' => 'PhabricatorObjectMailReceiver',
@@ -3080,6 +3125,7 @@
'PhabricatorAuthProviderOAuthGoogle' => 'PhabricatorAuthProviderOAuth',
'PhabricatorAuthProviderOAuthTwitch' => 'PhabricatorAuthProviderOAuth',
'PhabricatorAuthProviderPassword' => 'PhabricatorAuthProvider',
+ 'PhabricatorAuthProviderPersona' => 'PhabricatorAuthProvider',
'PhabricatorAuthRegisterController' => 'PhabricatorAuthController',
'PhabricatorAuthStartController' => 'PhabricatorAuthController',
'PhabricatorAuthUnlinkController' => 'PhabricatorAuthController',
@@ -3202,6 +3248,7 @@
0 => 'PhabricatorCountdownDAO',
1 => 'PhabricatorPolicyInterface',
),
+ 'PhabricatorCountdownCapabilityDefaultView' => 'PhabricatorPolicyCapability',
'PhabricatorCountdownController' => 'PhabricatorController',
'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO',
'PhabricatorCountdownDeleteController' => 'PhabricatorCountdownController',
@@ -3444,6 +3491,7 @@
'PhabricatorLocalTimeTestCase' => 'PhabricatorTestCase',
'PhabricatorLogoutController' => 'PhabricatorAuthController',
'PhabricatorMacroAudioController' => 'PhabricatorMacroController',
+ 'PhabricatorMacroCapabilityManage' => 'PhabricatorPolicyCapability',
'PhabricatorMacroCommentController' => 'PhabricatorMacroController',
'PhabricatorMacroConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorMacroController' => 'PhabricatorController',
@@ -3640,18 +3688,33 @@
'PhabricatorPhrequentConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPhrictionConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPolicies' => 'PhabricatorPolicyConstants',
+ 'PhabricatorPolicy' =>
+ array(
+ 0 => 'PhabricatorPolicyDAO',
+ 1 => 'PhabricatorPolicyInterface',
+ ),
'PhabricatorPolicyAwareQuery' => 'PhabricatorOffsetPagedQuery',
'PhabricatorPolicyAwareTestQuery' => 'PhabricatorPolicyAwareQuery',
- 'PhabricatorPolicyCapability' => 'PhabricatorPolicyConstants',
+ 'PhabricatorPolicyCapability' => 'Phobject',
+ 'PhabricatorPolicyCapabilityCanEdit' => 'PhabricatorPolicyCapability',
+ 'PhabricatorPolicyCapabilityCanJoin' => 'PhabricatorPolicyCapability',
+ 'PhabricatorPolicyCapabilityCanView' => 'PhabricatorPolicyCapability',
'PhabricatorPolicyConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPolicyController' => 'PhabricatorController',
+ 'PhabricatorPolicyDAO' => 'PhabricatorLiskDAO',
'PhabricatorPolicyDataTestCase' => 'PhabricatorTestCase',
+ 'PhabricatorPolicyEditController' => 'PhabricatorPolicyController',
'PhabricatorPolicyException' => 'Exception',
'PhabricatorPolicyExplainController' => 'PhabricatorPolicyController',
'PhabricatorPolicyManagementShowWorkflow' => 'PhabricatorPolicyManagementWorkflow',
'PhabricatorPolicyManagementUnlockWorkflow' => 'PhabricatorPolicyManagementWorkflow',
'PhabricatorPolicyManagementWorkflow' => 'PhutilArgumentWorkflow',
- 'PhabricatorPolicyQuery' => 'PhabricatorQuery',
+ 'PhabricatorPolicyPHIDTypePolicy' => 'PhabricatorPHIDType',
+ 'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'PhabricatorPolicyRuleAdministrators' => 'PhabricatorPolicyRule',
+ 'PhabricatorPolicyRuleLunarPhase' => 'PhabricatorPolicyRule',
+ 'PhabricatorPolicyRuleProjects' => 'PhabricatorPolicyRule',
+ 'PhabricatorPolicyRuleUsers' => 'PhabricatorPolicyRule',
'PhabricatorPolicyTestCase' => 'PhabricatorTestCase',
'PhabricatorPolicyTestObject' => 'PhabricatorPolicyInterface',
'PhabricatorPolicyType' => 'PhabricatorPolicyConstants',
@@ -3683,12 +3746,13 @@
'PhabricatorProjectTransaction' => 'PhabricatorProjectDAO',
'PhabricatorProjectTransactionType' => 'PhabricatorProjectConstants',
'PhabricatorProjectUpdateController' => 'PhabricatorProjectController',
- 'PhabricatorPropertyListExample' => 'PhabricatorUIExample',
- 'PhabricatorPropertyListView' => 'AphrontView',
'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorRedirectController' => 'PhabricatorController',
'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController',
'PhabricatorRegistrationProfile' => 'Phobject',
+ 'PhabricatorRemarkupBlockInterpreterCowsay' => 'PhutilRemarkupBlockInterpreter',
+ 'PhabricatorRemarkupBlockInterpreterFiglet' => 'PhutilRemarkupBlockInterpreter',
+ 'PhabricatorRemarkupBlockInterpreterGraphviz' => 'PhutilRemarkupBlockInterpreter',
'PhabricatorRemarkupControl' => 'AphrontFormTextAreaControl',
'PhabricatorRemarkupRuleEmbedFile' => 'PhabricatorRemarkupRuleObject',
'PhabricatorRemarkupRuleImageMacro' => 'PhutilRemarkupRule',
@@ -3821,6 +3885,7 @@
'PhabricatorSetupCheckTimezone' => 'PhabricatorSetupCheck',
'PhabricatorSetupIssueExample' => 'PhabricatorUIExample',
'PhabricatorSetupIssueView' => 'AphrontView',
+ 'PhabricatorSlowvoteCapabilityDefaultView' => 'PhabricatorPolicyCapability',
'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvoteComment' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvoteCommentController' => 'PhabricatorSlowvoteController',
@@ -4272,6 +4337,7 @@
'PonderVote' => 'PonderConstants',
'PonderVoteEditor' => 'PhabricatorEditor',
'PonderVoteSaveController' => 'PonderController',
+ 'ProjectCapabilityCreateProjects' => 'PhabricatorPolicyCapability',
'ProjectRemarkupRule' => 'PhabricatorRemarkupRuleObject',
'QueryFormattingTestCase' => 'PhabricatorTestCase',
'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification',
Index: src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
===================================================================
--- src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
+++ src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
@@ -161,6 +161,11 @@
// Possibly we should add a header here like "you need to login to see
// the thing you are trying to look at".
$login_controller = new PhabricatorAuthStartController($request);
+
+ $auth_app_class = 'PhabricatorApplicationAuth';
+ $auth_app = PhabricatorApplication::getByClass($auth_app_class);
+ $login_controller->setCurrentApplication($auth_app);
+
return $login_controller->processRequest();
}
@@ -172,22 +177,25 @@
$list = phutil_tag('ul', array(), $list);
}
- $content = phutil_tag(
- 'div',
- array(
- 'class' => 'aphront-policy-exception',
- ),
- array(
- $ex->getMessage(),
- $list,
- ));
+ $content = array(
+ phutil_tag(
+ 'div',
+ array(
+ 'class' => 'aphront-policy-rejection',
+ ),
+ $ex->getRejection()),
+ phutil_tag(
+ 'div',
+ array(
+ 'class' => 'aphront-capability-details',
+ ),
+ pht('Users with the "%s" capability:', $ex->getCapabilityName())),
+ $list,
+ );
$dialog = new AphrontDialogView();
$dialog
- ->setTitle(
- $is_serious
- ? 'Access Denied'
- : "You Shall Not Pass")
+ ->setTitle($ex->getTitle())
->setClass('aphront-access-dialog')
->setUser($user)
->appendChild($content);
Index: src/aphront/exception/AphrontRedirectException.php
===================================================================
--- src/aphront/exception/AphrontRedirectException.php
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-
-/**
- * TODO: Remove this entirely? We have no callsites.
- */
-final class AphrontRedirectException extends AphrontException {
-
- private $uri;
-
- public function __construct($uri) {
- $this->uri = $uri;
- }
-
- public function getURI() {
- return $this->uri;
- }
-
-}
Index: src/applications/audit/controller/PhabricatorAuditListController.php
===================================================================
--- src/applications/audit/controller/PhabricatorAuditListController.php
+++ src/applications/audit/controller/PhabricatorAuditListController.php
@@ -139,9 +139,7 @@
$tok_value = null;
if ($handle) {
- $tok_value = array(
- $handle->getPHID() => $handle->getFullName(),
- );
+ $tok_value = array($handle);
}
$form->appendChild(
Index: src/applications/auth/controller/PhabricatorAuthRegisterController.php
===================================================================
--- src/applications/auth/controller/PhabricatorAuthRegisterController.php
+++ src/applications/auth/controller/PhabricatorAuthRegisterController.php
@@ -442,6 +442,12 @@
private function loadSetupAccount() {
$provider = new PhabricatorAuthProviderPassword();
+ $provider->attachProviderConfig(
+ id(new PhabricatorAuthProviderConfig())
+ ->setShouldAllowRegistration(1)
+ ->setShouldAllowLogin(1)
+ ->setIsEnabled(true));
+
$account = $provider->getDefaultExternalAccount();
$response = null;
return array($account, $provider, $response);
Index: src/applications/auth/provider/PhabricatorAuthProvider.php
===================================================================
--- src/applications/auth/provider/PhabricatorAuthProvider.php
+++ src/applications/auth/provider/PhabricatorAuthProvider.php
@@ -384,6 +384,7 @@
array(
'method' => 'optional string',
'uri' => 'string',
+ 'sigil' => 'optional string',
));
$viewer = $request->getUser();
@@ -404,11 +405,11 @@
->setSpriteIcon($this->getLoginIcon());
$button = id(new PHUIButtonView())
- ->setSize(PHUIButtonView::BIG)
- ->setColor(PHUIButtonView::GREY)
- ->setIcon($icon)
- ->setText($button_text)
- ->setSubtext($this->getProviderName());
+ ->setSize(PHUIButtonView::BIG)
+ ->setColor(PHUIButtonView::GREY)
+ ->setIcon($icon)
+ ->setText($button_text)
+ ->setSubtext($this->getProviderName());
$uri = $attributes['uri'];
$uri = new PhutilURI($uri);
@@ -432,6 +433,7 @@
array(
'method' => idx($attributes, 'method', 'GET'),
'action' => (string)$uri,
+ 'sigil' => idx($attributes, 'sigil'),
),
$content);
}
Index: src/applications/auth/provider/PhabricatorAuthProviderPersona.php
===================================================================
--- /dev/null
+++ src/applications/auth/provider/PhabricatorAuthProviderPersona.php
@@ -0,0 +1,83 @@
+<?php
+
+final class PhabricatorAuthProviderPersona
+ extends PhabricatorAuthProvider {
+
+ private $adapter;
+
+ public function getProviderName() {
+ return pht('Persona');
+ }
+
+ public function getDescriptionForCreate() {
+ return pht(
+ 'Allow users to login or register using Mozilla Persona.');
+ }
+
+ public function getAdapter() {
+ if (!$this->adapter) {
+ $adapter = new PhutilAuthAdapterPersona();
+ $this->adapter = $adapter;
+ }
+ return $this->adapter;
+ }
+
+ protected function renderLoginForm(
+ AphrontRequest $request,
+ $mode) {
+
+ Javelin::initBehavior(
+ 'persona-login',
+ array(
+ 'loginURI' => $this->getLoginURI(),
+ ));
+
+ return $this->renderStandardLoginButton(
+ $request,
+ $mode,
+ array(
+ 'uri' => $this->getLoginURI(),
+ 'sigil' => 'persona-login-form',
+ ));
+ }
+
+ public function isLoginFormAButton() {
+ return true;
+ }
+
+ public function processLoginRequest(
+ PhabricatorAuthLoginController $controller) {
+
+ $request = $controller->getRequest();
+ $adapter = $this->getAdapter();
+
+ $account = null;
+ $response = null;
+
+ if (!$request->isAjax()) {
+ throw new Exception("Expected this request to come via Ajax.");
+ }
+
+ $assertion = $request->getStr('assertion');
+ if (!$assertion) {
+ throw new Exception("Expected identity assertion.");
+ }
+
+ $adapter->setAssertion($assertion);
+ $adapter->setAudience(PhabricatorEnv::getURI('/'));
+
+ try {
+ $account_id = $adapter->getAccountID();
+ } catch (Exception $ex) {
+ // TODO: Handle this in a more user-friendly way.
+ throw $ex;
+ }
+
+ return array($this->loadOrCreateAccount($account_id), $response);
+ }
+
+ protected function getLoginIcon() {
+ return 'Persona';
+ }
+
+}
Index: src/applications/base/PhabricatorApplication.php
===================================================================
--- src/applications/base/PhabricatorApplication.php
+++ src/applications/base/PhabricatorApplication.php
@@ -349,11 +349,7 @@
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
- if (PhabricatorEnv::getEnvConfig('policy.allow-public')) {
- return PhabricatorPolicies::POLICY_PUBLIC;
- } else {
- return PhabricatorPolicies::POLICY_USER;
- }
+ return PhabricatorPolicies::getMostOpenPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return PhabricatorPolicies::POLICY_ADMIN;
default:
@@ -400,21 +396,26 @@
private function getCustomCapabilitySpecification($capability) {
$custom = $this->getCustomCapabilities();
- if (empty($custom[$capability])) {
+ if (!isset($custom[$capability])) {
throw new Exception("Unknown capability '{$capability}'!");
}
return $custom[$capability];
}
public function getCapabilityLabel($capability) {
- $map = array(
- PhabricatorPolicyCapability::CAN_VIEW => pht('Can Use Application'),
- PhabricatorPolicyCapability::CAN_EDIT => pht('Can Configure Application'),
- );
+ switch ($capability) {
+ case PhabricatorPolicyCapability::CAN_VIEW:
+ return pht('Can Use Application');
+ case PhabricatorPolicyCapability::CAN_EDIT:
+ return pht('Can Configure Application');
+ }
- $map += ipull($this->getCustomCapabilities(), 'label');
+ $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
+ if ($capobj) {
+ return $capobj->getCapabilityName();
+ }
- return idx($map, $capability);
+ return null;
}
public function isCapabilityEditable($capability) {
Index: src/applications/base/controller/PhabricatorController.php
===================================================================
--- src/applications/base/controller/PhabricatorController.php
+++ src/applications/base/controller/PhabricatorController.php
@@ -319,7 +319,7 @@
return implode_selected_handle_links($style_map[$style],
$this->getLoadedHandles(),
- $phids);
+ array_filter($phids));
}
protected function buildApplicationMenu() {
@@ -364,9 +364,46 @@
$capability);
}
- protected function explainApplicationCapability($capability, $message) {
- // TODO: Render a link to get more information.
- return $message;
+ protected function explainApplicationCapability(
+ $capability,
+ $positive_message,
+ $negative_message) {
+
+ $can_act = $this->hasApplicationCapability($capability);
+ if ($can_act) {
+ $message = $positive_message;
+ $icon_name = 'enable-grey';
+ } else {
+ $message = $negative_message;
+ $icon_name = 'lock';
+ }
+
+ $icon = id(new PHUIIconView())
+ ->setSpriteSheet(PHUIIconView::SPRITE_ICONS)
+ ->setSpriteIcon($icon_name);
+
+ require_celerity_resource('policy-css');
+
+ $phid = $this->getCurrentApplication()->getPHID();
+ $explain_uri = "/policy/explain/{$phid}/{$capability}/";
+
+ $message = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'policy-capability-explanation',
+ ),
+ array(
+ $icon,
+ javelin_tag(
+ 'a',
+ array(
+ 'href' => $explain_uri,
+ 'sigil' => 'workflow',
+ ),
+ $message),
+ ));
+
+ return array($can_act, $message);
}
}
Index: src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php
===================================================================
--- src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php
+++ src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php
@@ -139,6 +139,48 @@
$message));
}
+ $links = array();
+
+ $first_uri = $pager->getFirstPageURI();
+ if ($first_uri) {
+ $links[] = phutil_tag(
+ 'a',
+ array(
+ 'href' => $first_uri,
+ ),
+ "\xC2\xAB ". pht("Newest"));
+ }
+
+ $prev_uri = $pager->getPrevPageURI();
+ if ($prev_uri) {
+ $links[] = phutil_tag(
+ 'a',
+ array(
+ 'href' => $prev_uri,
+ ),
+ "\xE2\x80\xB9 " . pht("Newer"));
+ }
+
+ $next_uri = $pager->getNextPageURI();
+ if ($next_uri) {
+ $links[] = phutil_tag(
+ 'a',
+ array(
+ 'href' => $next_uri,
+ ),
+ pht("Older") . " \xE2\x80\xBA");
+ }
+
+ $pager_top = phutil_tag(
+ 'div',
+ array('class' => 'phabricator-chat-log-pager-top'),
+ $links);
+
+ $pager_bottom = phutil_tag(
+ 'div',
+ array('class' => 'phabricator-chat-log-pager-bottom'),
+ $links);
+
$crumbs = $this
->buildApplicationCrumbs()
->addCrumb(
@@ -176,19 +218,44 @@
),
$table);
+ $jump_link = phutil_tag(
+ 'a',
+ array(
+ 'href' => '#latest'
+ ),
+ pht("Jump to Bottom") . " \xE2\x96\xBE");
+
+ $jump = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'phabricator-chat-log-jump'
+ ),
+ $jump_link);
+
+ $jump_target = phutil_tag(
+ 'div',
+ array(
+ 'id' => 'latest'
+ ));
+
$content = phutil_tag(
'div',
array(
'class' => 'phabricator-chat-log-wrap'
),
- $log);
+ array(
+ $jump,
+ $pager_top,
+ $log,
+ $jump_target,
+ $pager_bottom,
+ ));
return $this->buildApplicationPage(
array(
$crumbs,
$filter,
$content,
- $pager,
),
array(
'title' => pht('Channel Log'),
Index: src/applications/conduit/method/ConduitAPIMethod.php
===================================================================
--- src/applications/conduit/method/ConduitAPIMethod.php
+++ src/applications/conduit/method/ConduitAPIMethod.php
@@ -25,7 +25,7 @@
/**
* This is mostly for compatibility with
- * @{class:AphrontCursorPagedPolicyAwareQuery}.
+ * @{class:PhabricatorCursorPagedPolicyAwareQuery}.
*/
public function getID() {
return $this->getAPIMethodName();
@@ -166,6 +166,10 @@
/* -( PhabricatorPolicyInterface )----------------------------------------- */
+ public function getPHID() {
+ return null;
+ }
+
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
Index: src/applications/config/controller/PhabricatorConfigAllController.php
===================================================================
--- src/applications/config/controller/PhabricatorConfigAllController.php
+++ src/applications/config/controller/PhabricatorConfigAllController.php
@@ -66,7 +66,7 @@
$versions = $this->loadVersions();
- $version_property_list = id(new PhabricatorPropertyListView());
+ $version_property_list = id(new PHUIPropertyListView());
foreach ($versions as $version) {
list($name, $hash) = $version;
$version_property_list->addProperty($name, $hash);
@@ -74,7 +74,7 @@
$object_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Current Settings'))
- ->setPropertyList($version_property_list);
+ ->addPropertyList($version_property_list);
$phabricator_root = dirname(phutil_get_library_root('phabricator'));
$version_path = $phabricator_root.'/conf/local/VERSION';
Index: src/applications/config/storage/PhabricatorConfigEntry.php
===================================================================
--- src/applications/config/storage/PhabricatorConfigEntry.php
+++ src/applications/config/storage/PhabricatorConfigEntry.php
@@ -3,8 +3,6 @@
final class PhabricatorConfigEntry extends PhabricatorConfigEntryDAO
implements PhabricatorPolicyInterface {
- protected $id;
- protected $phid;
protected $namespace;
protected $configKey;
protected $value;
Index: src/applications/conpherence/controller/ConpherenceNewController.php
===================================================================
--- src/applications/conpherence/controller/ConpherenceNewController.php
+++ src/applications/conpherence/controller/ConpherenceNewController.php
@@ -11,15 +11,12 @@
$title = pht('New Message');
$participants = array();
+ $participant_prefill = null;
$message = '';
$e_participants = null;
$e_message = null;
// this comes from ajax requests from all over. should be a single phid.
- $participant_prefill = $request->getStr('participant');
- if ($participant_prefill) {
- $participants[] = $participant_prefill;
- }
if ($request->isFormPost()) {
$participants = $request->getArr('participants');
@@ -47,15 +44,20 @@
return id(new AphrontRedirectResponse())
->setURI($uri);
}
+ } else {
+ $participant_prefill = $request->getStr('participant');
+ if ($participant_prefill) {
+ $participants[] = $participant_prefill;
+ }
}
+
$participant_handles = array();
if ($participants) {
- $handles = id(new PhabricatorHandleQuery())
+ $participant_handles = id(new PhabricatorHandleQuery())
->setViewer($user)
->withPHIDs($participants)
->execute();
- $participant_handles = mpull($handles, 'getFullName', 'getPHID');
}
$submit_uri = $this->getApplicationURI('new/');
@@ -64,7 +66,7 @@
// TODO - we can get a better cancel_uri once we get better at crazy
// ajax jonx T2086
if ($participant_prefill) {
- $handle = $handles[$participant_prefill];
+ $handle = $participant_handles[$participant_prefill];
$cancel_uri = $handle->getURI();
}
Index: src/applications/conpherence/mail/ConpherenceReplyHandler.php
===================================================================
--- src/applications/conpherence/mail/ConpherenceReplyHandler.php
+++ src/applications/conpherence/mail/ConpherenceReplyHandler.php
@@ -69,7 +69,6 @@
->setParentMessageID($mail->getMessageID());
$body = $mail->getCleanTextBody();
- $body = trim($body);
$file_phids = $mail->getAttachments();
$body = $this->enhanceBodyWithAttachments(
$body,
Index: src/applications/conpherence/storage/ConpherenceThread.php
===================================================================
--- src/applications/conpherence/storage/ConpherenceThread.php
+++ src/applications/conpherence/storage/ConpherenceThread.php
@@ -6,8 +6,6 @@
final class ConpherenceThread extends ConpherenceDAO
implements PhabricatorPolicyInterface {
- protected $id;
- protected $phid;
protected $title;
protected $messageCount;
protected $recentParticipantPHIDs = array();
Index: src/applications/countdown/application/PhabricatorApplicationCountdown.php
===================================================================
--- src/applications/countdown/application/PhabricatorApplicationCountdown.php
+++ src/applications/countdown/application/PhabricatorApplicationCountdown.php
@@ -1,8 +1,5 @@
<?php
-/**
- * @group countdown
- */
final class PhabricatorApplicationCountdown extends PhabricatorApplication {
public function getBaseURI() {
@@ -50,4 +47,12 @@
);
}
+ public function getCustomCapabilities() {
+ return array(
+ PhabricatorCountdownCapabilityDefaultView::CAPABILITY => array(
+ 'caption' => pht('Default view policy for new countdowns.'),
+ ),
+ );
+ }
+
}
Index: src/applications/countdown/capability/PhabricatorCountdownCapabilityDefaultView.php
===================================================================
--- /dev/null
+++ src/applications/countdown/capability/PhabricatorCountdownCapabilityDefaultView.php
@@ -0,0 +1,20 @@
+<?php
+
+final class PhabricatorCountdownCapabilityDefaultView
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'countdown.default.view';
+
+ public function getCapabilityKey() {
+ return self::CAPABILITY;
+ }
+
+ public function getCapabilityName() {
+ return pht('Default View Policy');
+ }
+
+ public function shouldAllowPublicPolicySetting() {
+ return true;
+ }
+
+}
Index: src/applications/countdown/controller/PhabricatorCountdownDeleteController.php
===================================================================
--- src/applications/countdown/controller/PhabricatorCountdownDeleteController.php
+++ src/applications/countdown/controller/PhabricatorCountdownDeleteController.php
@@ -31,11 +31,6 @@
return new Aphront404Response();
}
- if (($countdown->getAuthorPHID() !== $user->getPHID())
- && $user->getIsAdmin() === false) {
- return new Aphront403Response();
- }
-
if ($request->isFormPost()) {
$countdown->delete();
return id(new AphrontRedirectResponse())
Index: src/applications/countdown/controller/PhabricatorCountdownEditController.php
===================================================================
--- src/applications/countdown/controller/PhabricatorCountdownEditController.php
+++ src/applications/countdown/controller/PhabricatorCountdownEditController.php
@@ -27,24 +27,23 @@
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
-
- // If no countdown is found
if (!$countdown) {
return new Aphront404Response();
}
} else {
$page_title = pht('Create Countdown');
- $countdown = new PhabricatorCountdown();
- $countdown->setEpoch(time());
+ $countdown = PhabricatorCountdown::initializeNewCountdown($user);
}
$error_view = null;
- $e_text = null;
+ $e_text = true;
+ $e_epoch = null;
if ($request->isFormPost()) {
$errors = array();
$title = $request->getStr('title');
$epoch = $request->getStr('epoch');
+ $view_policy = $request->getStr('viewPolicy');
$e_text = null;
if (!strlen($title)) {
@@ -68,7 +67,7 @@
if (!count($errors)) {
$countdown->setTitle($title);
$countdown->setEpoch($timestamp);
- $countdown->setAuthorPHID($user->getPHID());
+ $countdown->setViewPolicy($view_policy);
$countdown->save();
return id(new AphrontRedirectResponse())
->setURI('/countdown/'.$countdown->getID().'/');
@@ -106,6 +105,10 @@
$submit_label = pht('Create Countdown');
}
+ $policies = id(new PhabricatorPolicyQuery())
+ ->setViewer($user)
+ ->setObject($countdown)
+ ->execute();
$form = id(new AphrontFormView())
->setUser($user)
@@ -114,16 +117,25 @@
id(new AphrontFormTextControl())
->setLabel(pht('Title'))
->setValue($countdown->getTitle())
- ->setName('title'))
+ ->setName('title')
+ ->setError($e_text))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('End Date'))
->setValue($display_epoch)
->setName('epoch')
+ ->setError($e_epoch)
->setCaption(pht('Examples: '.
'2011-12-25 or 3 hours or '.
'June 8 2011, 5 PM.')))
->appendChild(
+ id(new AphrontFormPolicyControl())
+ ->setUser($user)
+ ->setName('viewPolicy')
+ ->setPolicyObject($countdown)
+ ->setPolicies($policies)
+ ->setCapability(PhabricatorPolicyCapability::CAN_VIEW))
+ ->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($submit_label));
Index: src/applications/countdown/controller/PhabricatorCountdownViewController.php
===================================================================
--- src/applications/countdown/controller/PhabricatorCountdownViewController.php
+++ src/applications/countdown/controller/PhabricatorCountdownViewController.php
@@ -22,7 +22,6 @@
->setViewer($user)
->withIDs(array($this->id))
->executeOne();
-
if (!$countdown) {
return new Aphront404Response();
}
@@ -42,15 +41,16 @@
->setName("C{$id}"));
$header = id(new PHUIHeaderView())
- ->setHeader($title);
+ ->setHeader($title)
+ ->setUser($user)
+ ->setPolicyObject($countdown);
$actions = $this->buildActionListView($countdown);
- $properties = $this->buildPropertyListView($countdown);
+ $properties = $this->buildPropertyListView($countdown, $actions);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
$content = array(
$crumbs,
@@ -99,14 +99,18 @@
return $view;
}
- private function buildPropertyListView(PhabricatorCountdown $countdown) {
+ private function buildPropertyListView(
+ PhabricatorCountdown $countdown,
+ PhabricatorActionListView $actions) {
+
$request = $this->getRequest();
$viewer = $request->getUser();
$this->loadHandles(array($countdown->getAuthorPHID()));
- $view = id(new PhabricatorPropertyListView())
- ->setUser($viewer);
+ $view = id(new PHUIPropertyListView())
+ ->setUser($viewer)
+ ->setActionList($actions);
$view->addProperty(
pht('Author'),
Index: src/applications/countdown/query/PhabricatorCountdownSearchEngine.php
===================================================================
--- src/applications/countdown/query/PhabricatorCountdownSearchEngine.php
+++ src/applications/countdown/query/PhabricatorCountdownSearchEngine.php
@@ -33,11 +33,10 @@
AphrontFormView $form,
PhabricatorSavedQuery $saved_query) {
$phids = $saved_query->getParameter('authorPHIDs', array());
- $handles = id(new PhabricatorHandleQuery())
+ $author_handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireViewer())
->withPHIDs($phids)
->execute();
- $author_tokens = mpull($handles, 'getFullName', 'getPHID');
$upcoming = $saved_query->getParameter('upcoming');
@@ -47,7 +46,7 @@
->setDatasource('/typeahead/common/users/')
->setName('authors')
->setLabel(pht('Authors'))
- ->setValue($author_tokens))
+ ->setValue($author_handles))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
Index: src/applications/countdown/storage/PhabricatorCountdown.php
===================================================================
--- src/applications/countdown/storage/PhabricatorCountdown.php
+++ src/applications/countdown/storage/PhabricatorCountdown.php
@@ -1,18 +1,28 @@
<?php
-/**
- * @group countdown
- */
final class PhabricatorCountdown
extends PhabricatorCountdownDAO
implements PhabricatorPolicyInterface {
- protected $id;
- protected $phid;
protected $title;
protected $authorPHID;
protected $epoch;
- // protected $viewPolicy; //commented out till we have it on countdown table
+ protected $viewPolicy;
+
+ public static function initializeNewCountdown(PhabricatorUser $actor) {
+ $app = id(new PhabricatorApplicationQuery())
+ ->setViewer($actor)
+ ->withClasses(array('PhabricatorApplicationCountdown'))
+ ->executeOne();
+
+ $view_policy = $app->getPolicy(
+ PhabricatorCountdownCapabilityDefaultView::CAPABILITY);
+
+ return id(new PhabricatorCountdown())
+ ->setAuthorPHID($actor->getPHID())
+ ->setViewPolicy($view_policy)
+ ->setEpoch(PhabricatorTime::getNow());
+ }
public function getConfiguration() {
return array(
@@ -25,10 +35,6 @@
PhabricatorCountdownPHIDTypeCountdown::TYPECONST);
}
- public function getViewPolicy() {
- return PhabricatorPolicies::POLICY_USER;
- }
-
/* -( PhabricatorPolicyInterface )----------------------------------------- */
Index: src/applications/daemon/controller/PhabricatorDaemonLogViewController.php
===================================================================
--- src/applications/daemon/controller/PhabricatorDaemonLogViewController.php
+++ src/applications/daemon/controller/PhabricatorDaemonLogViewController.php
@@ -72,11 +72,14 @@
$event_panel->setNoBackground();
$event_panel->appendChild($event_view);
+ $object_box = id(new PHUIObjectBoxView())
+ ->setHeader($header)
+ ->addPropertyList($properties);
+
return $this->buildApplicationPage(
array(
$crumbs,
- $header,
- $properties,
+ $object_box,
$event_panel,
),
array(
@@ -88,7 +91,7 @@
$request = $this->getRequest();
$viewer = $request->getUser();
- $view = id(new PhabricatorPropertyListView())
+ $view = id(new PHUIPropertyListView())
->setUser($viewer);
$id = $daemon->getID();
Index: src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
===================================================================
--- src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
+++ src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
@@ -38,18 +38,20 @@
$task->getID(),
$task->getTaskClass()));
- $actions = $this->buildActionListView($task);
- $properties = $this->buildPropertyListView($task);
+ $actions = $this->buildActionListView($task);
+ $properties = $this->buildPropertyListView($task, $actions);
$retry_head = id(new PHUIHeaderView())
->setHeader(pht('Retries'));
$retry_info = $this->buildRetryListView($task);
+ $object_box = id(new PHUIObjectBoxView())
+ ->setHeader($header)
+ ->addPropertyList($properties);
+
$content = array(
- $header,
- $actions,
- $properties,
+ $object_box,
$retry_head,
$retry_info,
);
@@ -114,8 +116,12 @@
return $view;
}
- private function buildPropertyListView(PhabricatorWorkerTask $task) {
- $view = new PhabricatorPropertyListView();
+ private function buildPropertyListView(
+ PhabricatorWorkerTask $task,
+ PhabricatorActionListView $actions) {
+
+ $view = new PHUIPropertyListView();
+ $view->setActionList($actions);
if ($task->isArchived()) {
switch ($task->getResult()) {
@@ -197,7 +203,7 @@
}
private function buildRetryListView(PhabricatorWorkerTask $task) {
- $view = new PhabricatorPropertyListView();
+ $view = new PHUIPropertyListView();
$data = id(new PhabricatorWorkerTaskData())->load($task->getDataID());
$task->setData($data->getData());
Index: src/applications/daemon/storage/PhabricatorDaemonLog.php
===================================================================
--- src/applications/daemon/storage/PhabricatorDaemonLog.php
+++ src/applications/daemon/storage/PhabricatorDaemonLog.php
@@ -26,6 +26,9 @@
/* -( PhabricatorPolicyInterface )----------------------------------------- */
+ public function getPHID() {
+ return null;
+ }
public function getCapabilities() {
return array(
Index: src/applications/differential/application/PhabricatorApplicationDifferential.php
===================================================================
--- src/applications/differential/application/PhabricatorApplicationDifferential.php
+++ src/applications/differential/application/PhabricatorApplicationDifferential.php
@@ -118,5 +118,14 @@
return $status;
}
+ protected function getCustomCapabilities() {
+ return array(
+ DifferentialCapabilityDefaultView::CAPABILITY => array(
+ 'caption' => pht(
+ 'Default view policy for newly created revisions.')
+ ),
+ );
+ }
+
}
Index: src/applications/differential/capability/DifferentialCapabilityDefaultView.php
===================================================================
--- /dev/null
+++ src/applications/differential/capability/DifferentialCapabilityDefaultView.php
@@ -0,0 +1,20 @@
+<?php
+
+final class DifferentialCapabilityDefaultView
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'differential.default.view';
+
+ public function getCapabilityKey() {
+ return self::CAPABILITY;
+ }
+
+ public function getCapabilityName() {
+ return pht('Default View Policy');
+ }
+
+ public function shouldAllowPublicPolicySetting() {
+ return true;
+ }
+
+}
Index: src/applications/differential/conduit/ConduitAPI_differential_creatediff_Method.php
===================================================================
--- src/applications/differential/conduit/ConduitAPI_differential_creatediff_Method.php
+++ src/applications/differential/conduit/ConduitAPI_differential_creatediff_Method.php
@@ -59,11 +59,15 @@
$parent_id = $request->getValue('parentRevisionID');
if ($parent_id) {
+ // NOTE: If the viewer can't see the parent revision, just don't set
+ // a parent revision ID. This isn't used for anything meaningful.
+ // TODO: Can we delete this entirely?
$parent_rev = id(new DifferentialRevisionQuery())
->setViewer($request->getUser())
->withIDs(array($parent_id))
- ->executeOne();
+ ->execute();
if ($parent_rev) {
+ $parent_rev = head($parent_rev);
if ($parent_rev->getStatus() !=
ArcanistDifferentialRevisionStatus::CLOSED) {
$diff->setParentRevisionID($parent_id);
Index: src/applications/differential/conduit/ConduitAPI_differential_getcommitmessage_Method.php
===================================================================
--- src/applications/differential/conduit/ConduitAPI_differential_getcommitmessage_Method.php
+++ src/applications/differential/conduit/ConduitAPI_differential_getcommitmessage_Method.php
@@ -30,11 +30,12 @@
protected function execute(ConduitAPIRequest $request) {
$id = $request->getValue('revision_id');
+ $viewer = $request->getUser();
if ($id) {
$revision = id(new DifferentialRevisionQuery())
->withIDs(array($id))
- ->setViewer($request->getUser())
+ ->setViewer($viewer)
->needRelationships(true)
->needReviewerStatus(true)
->executeOne();
@@ -43,8 +44,7 @@
throw new ConduitException('ERR_NOT_FOUND');
}
} else {
- $revision = new DifferentialRevision();
- $revision->attachRelationships(array());
+ $revision = DifferentialRevision::initializeNewRevision($viewer);
}
@@ -57,7 +57,7 @@
$pro_tips = array();
foreach ($aux_fields as $key => $aux_field) {
- $aux_field->setUser($request->getUser());
+ $aux_field->setUser($viewer);
$aux_field->setRevision($revision);
$pro_tips[] = $aux_field->getCommitMessageTips();
if (!$aux_field->shouldAppearOnCommitMessage()) {
Index: src/applications/differential/constants/DifferentialReviewerStatus.php
===================================================================
--- src/applications/differential/constants/DifferentialReviewerStatus.php
+++ src/applications/differential/constants/DifferentialReviewerStatus.php
@@ -2,7 +2,10 @@
final class DifferentialReviewerStatus {
+ const STATUS_BLOCKING = 'blocking';
const STATUS_ADDED = 'added';
+ const STATUS_ACCEPTED = 'accepted';
const STATUS_REJECTED = 'rejected';
+ const STATUS_COMMENTED = 'commented';
}
Index: src/applications/differential/controller/DifferentialDiffViewController.php
===================================================================
--- src/applications/differential/controller/DifferentialDiffViewController.php
+++ src/applications/differential/controller/DifferentialDiffViewController.php
@@ -117,7 +117,7 @@
$property_head = id(new PHUIHeaderView())
->setHeader(pht('Properties'));
- $property_view = new PhabricatorPropertyListView();
+ $property_view = new PHUIPropertyListView();
foreach ($dict as $key => $value) {
$property_view->addProperty($key, $value);
}
Index: src/applications/differential/controller/DifferentialRevisionEditController.php
===================================================================
--- src/applications/differential/controller/DifferentialRevisionEditController.php
+++ src/applications/differential/controller/DifferentialRevisionEditController.php
@@ -32,8 +32,7 @@
return new Aphront404Response();
}
} else {
- $revision = new DifferentialRevision();
- $revision->attachRelationships(array());
+ $revision = DifferentialRevision::initializeNewRevision($viewer);
}
$aux_fields = $this->loadAuxiliaryFields($revision);
Index: src/applications/differential/controller/DifferentialRevisionViewController.php
===================================================================
--- src/applications/differential/controller/DifferentialRevisionViewController.php
+++ src/applications/differential/controller/DifferentialRevisionViewController.php
@@ -30,6 +30,7 @@
->setViewer($request->getUser())
->needRelationships(true)
->needReviewerStatus(true)
+ ->needReviewerAuthority(true)
->executeOne();
if (!$revision) {
@@ -587,9 +588,24 @@
$viewer_phid = $viewer->getPHID();
$viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID());
$viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
- $viewer_did_accept = ($viewer_phid === $revision->loadReviewedBy());
$status = $revision->getStatus();
+ $viewer_has_accepted = false;
+ $viewer_has_rejected = false;
+ $status_accepted = DifferentialReviewerStatus::STATUS_ACCEPTED;
+ $status_rejected = DifferentialReviewerStatus::STATUS_REJECTED;
+ foreach ($revision->getReviewerStatus() as $reviewer) {
+ if ($reviewer->getReviewerPHID() == $viewer_phid) {
+ if ($reviewer->getStatus() == $status_accepted) {
+ $viewer_has_accepted = true;
+ }
+ if ($reviewer->getStatus() == $status_rejected) {
+ $viewer_has_rejected = true;
+ }
+ break;
+ }
+ }
+
$allow_self_accept = PhabricatorEnv::getEnvConfig(
'differential.allow-self-accept');
$always_allow_close = PhabricatorEnv::getEnvConfig(
@@ -630,12 +646,13 @@
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
$actions[DifferentialAction::ACTION_ACCEPT] = true;
+ $actions[DifferentialAction::ACTION_REJECT] = !$viewer_has_rejected;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
+ $actions[DifferentialAction::ACTION_ACCEPT] = !$viewer_has_accepted;
$actions[DifferentialAction::ACTION_REJECT] = true;
- $actions[DifferentialAction::ACTION_RESIGN] =
- $viewer_is_reviewer && !$viewer_did_accept;
+ $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::CLOSED:
case ArcanistDifferentialRevisionStatus::ABANDONED:
Index: src/applications/differential/editor/DifferentialCommentEditor.php
===================================================================
--- src/applications/differential/editor/DifferentialCommentEditor.php
+++ src/applications/differential/editor/DifferentialCommentEditor.php
@@ -93,7 +93,18 @@
}
public function save() {
- $actor = $this->requireActor();
+ $actor = $this->requireActor();
+
+ // Reload the revision to pick up reviewer status, until we can lift this
+ // out of here.
+ $this->revision = id(new DifferentialRevisionQuery())
+ ->setViewer($actor)
+ ->withIDs(array($this->revision->getID()))
+ ->needRelationships(true)
+ ->needReviewerStatus(true)
+ ->needReviewerAuthority(true)
+ ->executeOne();
+
$revision = $this->revision;
$action = $this->action;
$actor_phid = $actor->getPHID();
@@ -105,8 +116,8 @@
$allow_reopen = PhabricatorEnv::getEnvConfig(
'differential.allow-reopen');
$revision_status = $revision->getStatus();
+ $update_accepted_status = false;
- $revision->loadRelationships();
$reviewer_phids = $revision->getReviewers();
if ($reviewer_phids) {
$reviewer_phids = array_fuse($reviewer_phids);
@@ -128,6 +139,27 @@
"You are submitting an empty comment with no action: ".
"you must act on the revision or post a comment.");
}
+
+ // If the actor is a reviewer, and their status is "added" (that is,
+ // they haven't accepted or requested changes to the revision),
+ // upgrade their status to "commented". If they have a stronger status
+ // already, don't overwrite it.
+ if (isset($reviewer_phids[$actor_phid])) {
+ $status_added = DifferentialReviewerStatus::STATUS_ADDED;
+ $reviewer_status = $revision->getReviewerStatus();
+ foreach ($reviewer_status as $reviewer) {
+ if ($reviewer->getReviewerPHID() == $actor_phid) {
+ if ($reviewer->getStatus() == $status_added) {
+ DifferentialRevisionEditor::updateReviewerStatus(
+ $revision,
+ $actor,
+ $actor_phid,
+ DifferentialReviewerStatus::STATUS_COMMENTED);
+ }
+ }
+ }
+ }
+
break;
case DifferentialAction::ACTION_RESIGN:
@@ -152,6 +184,16 @@
array(),
array($actor_phid));
+ // If you are a blocking reviewer, your presence as a reviewer may be
+ // the only thing keeping a revision from transitioning to "accepted".
+ // Recalculate state after removing the resigning reviewer.
+ switch ($revision_status) {
+ case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
+ case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
+ $update_accepted_status = true;
+ break;
+ }
+
break;
case DifferentialAction::ACTION_ABANDON:
@@ -178,38 +220,49 @@
if ($actor_is_author && !$allow_self_accept) {
throw new Exception('You can not accept your own revision.');
}
- if (($revision_status !=
- ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) &&
- ($revision_status !=
- ArcanistDifferentialRevisionStatus::NEEDS_REVISION)) {
-
- switch ($revision_status) {
- case ArcanistDifferentialRevisionStatus::ACCEPTED:
- throw new DifferentialActionHasNoEffectException(
- "You can not accept this revision because someone else ".
- "already accepted it.");
- case ArcanistDifferentialRevisionStatus::ABANDONED:
- throw new DifferentialActionHasNoEffectException(
- "You can not accept this revision because it has been ".
- "abandoned.");
- case ArcanistDifferentialRevisionStatus::CLOSED:
- throw new DifferentialActionHasNoEffectException(
- "You can not accept this revision because it has already ".
- "been closed.");
- default:
- throw new Exception(
- "Unexpected revision state '{$revision_status}'!");
+
+ switch ($revision_status) {
+ case ArcanistDifferentialRevisionStatus::ABANDONED:
+ throw new DifferentialActionHasNoEffectException(
+ "You can not accept this revision because it has been ".
+ "abandoned.");
+ case ArcanistDifferentialRevisionStatus::CLOSED:
+ throw new DifferentialActionHasNoEffectException(
+ "You can not accept this revision because it has already ".
+ "been closed.");
+ case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
+ case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
+ case ArcanistDifferentialRevisionStatus::ACCEPTED:
+ // We expect "Accept" from these states.
+ break;
+ default:
+ throw new Exception(
+ "Unexpected revision state '{$revision_status}'!");
+ }
+
+ $was_reviewer_already = false;
+ foreach ($revision->getReviewerStatus() as $reviewer) {
+ if ($reviewer->hasAuthority($actor)) {
+ DifferentialRevisionEditor::updateReviewerStatus(
+ $revision,
+ $actor,
+ $reviewer->getReviewerPHID(),
+ DifferentialReviewerStatus::STATUS_ACCEPTED);
+ if ($reviewer->getReviewerPHID() == $actor_phid) {
+ $was_reviewer_already = true;
+ }
}
}
- $revision
- ->setStatus(ArcanistDifferentialRevisionStatus::ACCEPTED);
+ if (!$was_reviewer_already) {
+ DifferentialRevisionEditor::updateReviewerStatus(
+ $revision,
+ $actor,
+ $actor_phid,
+ DifferentialReviewerStatus::STATUS_ACCEPTED);
+ }
- DifferentialRevisionEditor::updateReviewerStatus(
- $revision,
- $this->getActor(),
- $actor_phid,
- DifferentialReviewerStatus::STATUS_ADDED);
+ $update_accepted_status = true;
break;
case DifferentialAction::ACTION_REQUEST:
@@ -258,9 +311,7 @@
case ArcanistDifferentialRevisionStatus::ACCEPTED:
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
- // NOTE: We allow you to reject an already-rejected revision
- // because it doesn't create any ambiguity and avoids a rather
- // needless dialog.
+ // We expect rejects from these states.
break;
case ArcanistDifferentialRevisionStatus::ABANDONED:
throw new DifferentialActionHasNoEffectException(
@@ -277,7 +328,7 @@
DifferentialRevisionEditor::updateReviewerStatus(
$revision,
- $this->getActor(),
+ $actor,
$actor_phid,
DifferentialReviewerStatus::STATUS_REJECTED);
@@ -295,6 +346,7 @@
case ArcanistDifferentialRevisionStatus::ACCEPTED:
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
+ // We expect accepts from these states.
break;
case ArcanistDifferentialRevisionStatus::ABANDONED:
throw new DifferentialActionHasNoEffectException(
@@ -326,6 +378,8 @@
$revision
->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
+
+ $update_accepted_status = true;
break;
case DifferentialAction::ACTION_CLOSE:
@@ -485,6 +539,12 @@
// top of the action list.
$revision->save();
+ if ($update_accepted_status) {
+ $revision = DifferentialRevisionEditor::updateAcceptedStatus(
+ $actor,
+ $revision);
+ }
+
if ($action != DifferentialAction::ACTION_RESIGN) {
DifferentialRevisionEditor::addCC(
$revision,
@@ -574,7 +634,7 @@
$phids = array($actor_phid);
$handles = id(new PhabricatorHandleQuery())
- ->setViewer($this->getActor())
+ ->setViewer($actor)
->withPHIDs($phids)
->execute();
$actor_handle = $handles[$actor_phid];
@@ -590,7 +650,7 @@
$comment,
$changesets,
$inline_comments))
- ->setActor($this->getActor())
+ ->setActor($actor)
->setExcludeMailRecipientPHIDs($this->getExcludeMailRecipientPHIDs())
->setToPHIDs(
array_merge(
Index: src/applications/differential/editor/DifferentialRevisionEditor.php
===================================================================
--- src/applications/differential/editor/DifferentialRevisionEditor.php
+++ src/applications/differential/editor/DifferentialRevisionEditor.php
@@ -40,10 +40,8 @@
DifferentialDiff $diff,
PhabricatorUser $actor) {
- $revision = new DifferentialRevision();
+ $revision = DifferentialRevision::initializeNewRevision($actor);
$revision->setPHID($revision->generatePHID());
- $revision->setAuthorPHID($actor->getPHID());
- $revision->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
$editor = new DifferentialRevisionEditor($revision);
$editor->setActor($actor);
@@ -168,9 +166,6 @@
$revision = $this->getRevision();
$is_new = $this->isNewRevision();
- if ($is_new) {
- $this->initializeNewRevision($revision);
- }
$revision->loadRelationships();
@@ -272,15 +267,18 @@
$xscript_header);
$sub = array(
- 'rev' => array(),
+ 'rev' => $adapter->getReviewersAddedByHerald(),
'ccs' => $adapter->getCCsAddedByHerald(),
);
$rem_ccs = $adapter->getCCsRemovedByHerald();
+ $blocking_reviewers = array_keys(
+ $adapter->getBlockingReviewersAddedByHerald());
} else {
$sub = array(
'rev' => array(),
'ccs' => array(),
);
+ $blocking_reviewers = array();
}
// Remove any CCs which are prevented by Herald rules.
@@ -306,12 +304,15 @@
$stable[$key] = array_diff_key($old[$key], $add[$key] + $rem[$key]);
}
+ // Prevent Herald rules from adding a revision's owner as a reviewer.
+ unset($add['rev'][$revision->getAuthorPHID()]);
+
self::updateReviewers(
$revision,
$this->getActor(),
array_keys($add['rev']),
array_keys($rem['rev']),
- $this->getActorPHID());
+ $blocking_reviewers);
// We want to attribute new CCs to a "reasonPHID", representing the reason
// they were added. This is either a user (if some user explicitly CCs
@@ -375,6 +376,8 @@
$changesets = null;
$comment = null;
+ $old_status = $revision->getStatus();
+
if ($diff) {
$changesets = $diff->loadChangesets();
// TODO: This should probably be in DifferentialFeedbackEditor?
@@ -423,6 +426,17 @@
$revision->save();
+ // If the actor just deleted all the blocking/rejected reviewers, we may
+ // be able to put the revision into "accepted".
+ switch ($revision->getStatus()) {
+ case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
+ case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
+ $revision = self::updateAcceptedStatus(
+ $this->getActor(),
+ $revision);
+ break;
+ }
+
$this->didWriteRevision();
$event_data = array(
@@ -599,7 +613,8 @@
DifferentialRevision $revision,
PhabricatorUser $actor,
array $add_phids,
- array $remove_phids) {
+ array $remove_phids,
+ array $blocking_phids = array()) {
$reviewers = $revision->getReviewers();
@@ -615,22 +630,32 @@
$editor = id(new PhabricatorEdgeEditor())
->setActor($actor);
- $options = array(
- 'data' => array(
- 'status' => DifferentialReviewerStatus::STATUS_ADDED
- )
- );
-
$reviewer_phids_map = array_fill_keys($reviewers, true);
+ $blocking_phids = array_fuse($blocking_phids);
foreach ($add_phids as $phid) {
// Adding an already existing edge again would have cause memory loss
// That is, the previous state for that reviewer would be lost
if (isset($reviewer_phids_map[$phid])) {
+ // TODO: If we're writing a blocking edge, we should overwrite an
+ // existing weaker edge (like "added" or "commented"), just not a
+ // stronger existing edge.
continue;
}
+ if (isset($blocking_phids[$phid])) {
+ $status = DifferentialReviewerStatus::STATUS_BLOCKING;
+ } else {
+ $status = DifferentialReviewerStatus::STATUS_ADDED;
+ }
+
+ $options = array(
+ 'data' => array(
+ 'status' => $status,
+ )
+ );
+
$editor->addEdge(
$revision->getPHID(),
PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER,
@@ -1071,26 +1096,53 @@
}
}
- private function initializeNewRevision(DifferentialRevision $revision) {
- // These fields aren't nullable; set them to sensible defaults if they
- // haven't been configured. We're just doing this so we can generate an
- // ID for the revision if we don't have one already.
- $revision->setLineCount(0);
- if ($revision->getStatus() === null) {
- $revision->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
- }
- if ($revision->getTitle() === null) {
- $revision->setTitle('Untitled Revision');
- }
- if ($revision->getAuthorPHID() === null) {
- $revision->setAuthorPHID($this->getActorPHID());
- }
- if ($revision->getSummary() === null) {
- $revision->setSummary('');
+ /**
+ * Try to move a revision to "accepted". We look for:
+ *
+ * - at least one accepting reviewer who is a user; and
+ * - no rejects; and
+ * - no blocking reviewers.
+ */
+ public static function updateAcceptedStatus(
+ PhabricatorUser $viewer,
+ DifferentialRevision $revision) {
+
+ $revision = id(new DifferentialRevisionQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($revision->getID()))
+ ->needRelationships(true)
+ ->needReviewerStatus(true)
+ ->needReviewerAuthority(true)
+ ->executeOne();
+
+ $has_user_accept = false;
+ foreach ($revision->getReviewerStatus() as $reviewer) {
+ $status = $reviewer->getStatus();
+ if ($status == DifferentialReviewerStatus::STATUS_BLOCKING) {
+ // We have a blocking reviewer, so just leave the revision in its
+ // existing state.
+ return $revision;
+ }
+
+ if ($status == DifferentialReviewerStatus::STATUS_REJECTED) {
+ // We have a rejecting reviewer, so leave the revisoin as is.
+ return $revision;
+ }
+
+ if ($reviewer->isUser()) {
+ if ($status == DifferentialReviewerStatus::STATUS_ACCEPTED) {
+ $has_user_accept = true;
+ }
+ }
}
- if ($revision->getTestPlan() === null) {
- $revision->setTestPlan('');
+
+ if ($has_user_accept) {
+ $revision
+ ->setStatus(ArcanistDifferentialRevisionStatus::ACCEPTED)
+ ->save();
}
+
+ return $revision;
}
}
Index: src/applications/differential/field/selector/DifferentialDefaultFieldSelector.php
===================================================================
--- src/applications/differential/field/selector/DifferentialDefaultFieldSelector.php
+++ src/applications/differential/field/selector/DifferentialDefaultFieldSelector.php
@@ -11,6 +11,7 @@
new DifferentialRevisionStatusFieldSpecification(),
new DifferentialAuthorFieldSpecification(),
new DifferentialReviewersFieldSpecification(),
+ new DifferentialProjectReviewersFieldSpecification(),
new DifferentialReviewedByFieldSpecification(),
new DifferentialCCsFieldSpecification(),
new DifferentialRepositoryFieldSpecification(),
Index: src/applications/differential/field/specification/DifferentialCCsFieldSpecification.php
===================================================================
--- src/applications/differential/field/specification/DifferentialCCsFieldSpecification.php
+++ src/applications/differential/field/specification/DifferentialCCsFieldSpecification.php
@@ -50,7 +50,7 @@
public function renderEditControl() {
$cc_map = array();
foreach ($this->ccs as $phid) {
- $cc_map[$phid] = $this->getHandle($phid)->getFullName();
+ $cc_map[] = $this->getHandle($phid);
}
return id(new AphrontFormTokenizerControl())
->setLabel('CC')
Index: src/applications/differential/field/specification/DifferentialFieldSpecification.php
===================================================================
--- src/applications/differential/field/specification/DifferentialFieldSpecification.php
+++ src/applications/differential/field/specification/DifferentialFieldSpecification.php
@@ -779,6 +779,14 @@
return $this->parseCommitMessageObjectList($value, $mailables = false);
}
+ protected function parseCommitMessageUserOrProjectList($value) {
+ return $this->parseCommitMessageObjectList(
+ $value,
+ $mailables = false,
+ $allow_partial = false,
+ $projects = true);
+ }
+
/**
* Parse a list of mailable objects into a canonical PHID list.
*
@@ -813,36 +821,66 @@
$object_map = array();
- $users = id(new PhabricatorUser())->loadAllWhere(
- '(username IN (%Ls))',
- $value);
-
- $user_map = mpull($users, 'getPHID', 'getUsername');
- foreach ($user_map as $username => $phid) {
- // Usernames may have uppercase letters in them. Put both names in the
- // map so we can try the original case first, so that username *always*
- // works in weird edge cases where some other mailable object collides.
- $object_map[$username] = $phid;
- $object_map[strtolower($username)] = $phid;
+ $project_names = array();
+ $other_names = array();
+ foreach ($value as $item) {
+ if (preg_match('/^#/', $item)) {
+ $project_names[$item] = ltrim(phutil_utf8_strtolower($item), '#').'/';
+ } else {
+ $other_names[] = $item;
+ }
}
- if ($include_mailables) {
- $mailables = id(new PhabricatorMetaMTAMailingList())->loadAllWhere(
- '(email IN (%Ls)) OR (name IN (%Ls))',
- $value,
- $value);
- $object_map += mpull($mailables, 'getPHID', 'getName');
- $object_map += mpull($mailables, 'getPHID', 'getEmail');
+ if ($project_names) {
+ // TODO: (T603) This should probably be policy-aware, although maybe not,
+ // since we generally don't want to destroy data and it doesn't leak
+ // anything?
+ $projects = id(new PhabricatorProjectQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withPhrictionSlugs($project_names)
+ ->execute();
+
+ $reverse_map = array_flip($project_names);
+ foreach ($projects as $project) {
+ $reverse_key = $project->getPhrictionSlug();
+ if (isset($reverse_map[$reverse_key])) {
+ $object_map[$reverse_map[$reverse_key]] = $project->getPHID();
+ }
+ }
+ }
+
+ if ($other_names) {
+ $users = id(new PhabricatorUser())->loadAllWhere(
+ '(username IN (%Ls))',
+ $other_names);
+
+ $user_map = mpull($users, 'getPHID', 'getUsername');
+ foreach ($user_map as $username => $phid) {
+ // Usernames may have uppercase letters in them. Put both names in the
+ // map so we can try the original case first, so that username *always*
+ // works in weird edge cases where some other mailable object collides.
+ $object_map[$username] = $phid;
+ $object_map[strtolower($username)] = $phid;
+ }
+
+ if ($include_mailables) {
+ $mailables = id(new PhabricatorMetaMTAMailingList())->loadAllWhere(
+ '(email IN (%Ls)) OR (name IN (%Ls))',
+ $other_names,
+ $other_names);
+ $object_map += mpull($mailables, 'getPHID', 'getName');
+ $object_map += mpull($mailables, 'getPHID', 'getEmail');
+ }
}
$invalid = array();
$results = array();
foreach ($value as $name) {
if (empty($object_map[$name])) {
- if (empty($object_map[strtolower($name)])) {
+ if (empty($object_map[phutil_utf8_strtolower($name)])) {
$invalid[] = $name;
} else {
- $results[] = $object_map[strtolower($name)];
+ $results[] = $object_map[phutil_utf8_strtolower($name)];
}
} else {
$results[] = $object_map[$name];
Index: src/applications/differential/field/specification/DifferentialProjectReviewersFieldSpecification.php
===================================================================
--- /dev/null
+++ src/applications/differential/field/specification/DifferentialProjectReviewersFieldSpecification.php
@@ -0,0 +1,43 @@
+<?php
+
+final class DifferentialProjectReviewersFieldSpecification
+ extends DifferentialFieldSpecification {
+
+ public function shouldAppearOnRevisionView() {
+ return true;
+ }
+
+ public function getRequiredHandlePHIDsForRevisionView() {
+ return $this->getRevision()->getReviewers();
+ }
+
+ public function renderLabelForRevisionView() {
+ return pht('Project Reviewers');
+ }
+
+ public function renderValueForRevisionView() {
+ $reviewers = array();
+ foreach ($this->getRevision()->getReviewerStatus() as $reviewer) {
+ if (!$reviewer->isUser()) {
+ $reviewers[] = $reviewer;
+ }
+ }
+
+ if (!$reviewers) {
+ return null;
+ }
+
+ $view = id(new DifferentialReviewersView())
+ ->setUser($this->getUser())
+ ->setReviewers($reviewers)
+ ->setHandles($this->getLoadedHandles());
+
+ $diff = $this->getRevision()->loadActiveDiff();
+ if ($diff) {
+ $view->setActiveDiff($diff);
+ }
+
+ return $view;
+ }
+
+}
Index: src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php
===================================================================
--- src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php
+++ src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php
@@ -15,11 +15,33 @@
}
public function renderLabelForRevisionView() {
- return 'Reviewers:';
+ return pht('Reviewers');
}
public function renderValueForRevisionView() {
- return $this->renderUserList($this->getReviewerPHIDs());
+ $reviewers = array();
+ foreach ($this->getRevision()->getReviewerStatus() as $reviewer) {
+ if ($reviewer->isUser()) {
+ $reviewers[] = $reviewer;
+ }
+ }
+
+ if (!$reviewers) {
+ // Renders "None".
+ return $this->renderUserList(array());
+ }
+
+ $view = id(new DifferentialReviewersView())
+ ->setUser($this->getUser())
+ ->setReviewers($reviewers)
+ ->setHandles($this->getLoadedHandles());
+
+ $diff = $this->getRevision()->loadActiveDiff();
+ if ($diff) {
+ $view->setActiveDiff($diff);
+ }
+
+ return $view;
}
private function getReviewerPHIDs() {
@@ -67,13 +89,13 @@
public function renderEditControl() {
$reviewer_map = array();
foreach ($this->reviewers as $phid) {
- $reviewer_map[$phid] = $this->getHandle($phid)->getFullName();
+ $reviewer_map[] = $this->getHandle($phid);
}
return id(new AphrontFormTokenizerControl())
->setLabel(pht('Reviewers'))
->setName('reviewers')
->setUser($this->getUser())
- ->setDatasource('/typeahead/common/users/')
+ ->setDatasource('/typeahead/common/usersorprojects/')
->setValue($reviewer_map)
->setError($this->error);
}
@@ -108,9 +130,11 @@
return null;
}
+ $project_type = PhabricatorProjectPHIDTypeProject::TYPECONST;
+
$names = array();
foreach ($this->reviewers as $phid) {
- $names[] = $this->getHandle($phid)->getName();
+ $names[] = $this->getHandle($phid)->getObjectName();
}
return implode(', ', $names);
@@ -124,7 +148,7 @@
}
public function parseValueFromCommitMessage($value) {
- return $this->parseCommitMessageUserList($value);
+ return $this->parseCommitMessageUserOrProjectList($value);
}
public function shouldAppearOnRevisionList() {
@@ -171,7 +195,8 @@
$handles = array_select_keys(
$handles,
array($this->getRevision()->getPrimaryReviewer())) + $handles;
- $names = mpull($handles, 'getName');
+
+ $names = mpull($handles, 'getObjectName');
return 'Reviewers: '.implode(', ', $names);
}
Index: src/applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php
===================================================================
--- src/applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php
+++ src/applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php
@@ -6,7 +6,7 @@
public function generate() {
$author = $this->loadPhabrictorUser();
$authorPHID = $author->getPHID();
- $revision = new DifferentialRevision();
+ $revision = DifferentialRevision::initializeNewRevision($author);
$revision->setTitle($this->generateTitle());
$revision->setSummary($this->generateDescription());
$revision->setTestPlan($this->generateDescription());
Index: src/applications/differential/query/DifferentialRevisionQuery.php
===================================================================
--- src/applications/differential/query/DifferentialRevisionQuery.php
+++ src/applications/differential/query/DifferentialRevisionQuery.php
@@ -59,6 +59,7 @@
private $needCommitPHIDs = false;
private $needHashes = false;
private $needReviewerStatus = false;
+ private $needReviewerAuthority;
private $buildingGlobalOrder;
@@ -347,6 +348,21 @@
}
+ /**
+ * Request information about the viewer's authority to act on behalf of each
+ * reviewer. In particular, they have authority to act on behalf of projects
+ * they are a member of.
+ *
+ * @param bool True to load and attach authority.
+ * @return this
+ * @task config
+ */
+ public function needReviewerAuthority($need_reviewer_authority) {
+ $this->needReviewerAuthority = $need_reviewer_authority;
+ return $this;
+ }
+
+
/* -( Query Execution )---------------------------------------------------- */
@@ -450,7 +466,7 @@
$this->loadHashes($conn_r, $revisions);
}
- if ($this->needReviewerStatus) {
+ if ($this->needReviewerStatus || $this->needReviewerAuthority) {
$this->loadReviewers($conn_r, $revisions);
}
@@ -495,15 +511,26 @@
if ($this->responsibles) {
$basic_authors = $this->authors;
$basic_reviewers = $this->reviewers;
+
+ $authority_projects = id(new PhabricatorProjectQuery())
+ ->setViewer($this->getViewer())
+ ->withMemberPHIDs($this->responsibles)
+ ->execute();
+ $authority_phids = mpull($authority_projects, 'getPHID');
+
try {
// Build the query where the responsible users are authors.
$this->authors = array_merge($basic_authors, $this->responsibles);
$this->reviewers = $basic_reviewers;
$selects[] = $this->buildSelectStatement($conn_r);
- // Build the query where the responsible users are reviewers.
+ // Build the query where the responsible users are reviewers, or
+ // projects they are members of are reviewers.
$this->authors = $basic_authors;
- $this->reviewers = array_merge($basic_reviewers, $this->responsibles);
+ $this->reviewers = array_merge(
+ $basic_reviewers,
+ $this->responsibles,
+ $authority_phids);
$selects[] = $this->buildSelectStatement($conn_r);
// Put everything back like it was.
@@ -599,25 +626,32 @@
if ($this->reviewers) {
$joins[] = qsprintf(
$conn_r,
- 'JOIN %T reviewer_rel ON reviewer_rel.revisionID = r.id '.
- 'AND reviewer_rel.relation = %s '.
- 'AND reviewer_rel.objectPHID in (%Ls)',
- DifferentialRevision::RELATIONSHIP_TABLE,
- DifferentialRevision::RELATION_REVIEWER,
+ 'JOIN %T e_reviewers ON e_reviewers.src = r.phid '.
+ 'AND e_reviewers.type = %s '.
+ 'AND e_reviewers.dst in (%Ls)',
+ PhabricatorEdgeConfig::TABLE_NAME_EDGE,
+ PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER,
$this->reviewers);
}
if ($this->subscribers) {
+ // TODO: These can be expressed as a JOIN again (and the corresponding
+ // WHERE clause removed) once subscribers move to edges.
$joins[] = qsprintf(
$conn_r,
- 'JOIN %T sub_rel ON sub_rel.revisionID = r.id '.
- 'AND sub_rel.relation IN (%Ls) '.
- 'AND sub_rel.objectPHID in (%Ls)',
+ 'LEFT JOIN %T sub_rel_cc ON sub_rel_cc.revisionID = r.id '.
+ 'AND sub_rel_cc.relation = %s '.
+ 'AND sub_rel_cc.objectPHID in (%Ls)',
DifferentialRevision::RELATIONSHIP_TABLE,
- array(
- DifferentialRevision::RELATION_SUBSCRIBED,
- DifferentialRevision::RELATION_REVIEWER,
- ),
+ DifferentialRevision::RELATION_SUBSCRIBED,
+ $this->subscribers);
+ $joins[] = qsprintf(
+ $conn_r,
+ 'LEFT JOIN %T sub_rel_reviewer ON sub_rel_reviewer.src = r.phid '.
+ 'AND sub_rel_reviewer.type = %s '.
+ 'AND sub_rel_reviewer.dst in (%Ls)',
+ PhabricatorEdgeConfig::TABLE_NAME_EDGE,
+ PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER,
$this->subscribers);
}
@@ -710,6 +744,13 @@
$this->arcanistProjectPHIDs);
}
+ if ($this->subscribers) {
+ $where[] = qsprintf(
+ $conn_r,
+ '(sub_rel_cc.objectPHID IS NOT NULL)
+ OR (sub_rel_reviewer.dst IS NOT NULL)');
+ }
+
switch ($this->status) {
case self::STATUS_ANY:
break;
@@ -889,16 +930,32 @@
assert_instances_of($revisions, 'DifferentialRevision');
$relationships = queryfx_all(
$conn_r,
- 'SELECT * FROM %T WHERE revisionID in (%Ld) ORDER BY sequence',
+ 'SELECT * FROM %T WHERE revisionID in (%Ld)
+ AND relation != %s ORDER BY sequence',
DifferentialRevision::RELATIONSHIP_TABLE,
- mpull($revisions, 'getID'));
+ mpull($revisions, 'getID'),
+ DifferentialRevision::RELATION_REVIEWER);
$relationships = igroup($relationships, 'revisionID');
+
+ $type_reviewer = PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER;
+ $edges = id(new PhabricatorEdgeQuery())
+ ->withSourcePHIDs(mpull($revisions, 'getPHID'))
+ ->withEdgeTypes(array($type_reviewer))
+ ->setOrder(PhabricatorEdgeQuery::ORDER_OLDEST_FIRST)
+ ->execute();
+
foreach ($revisions as $revision) {
- $revision->attachRelationships(
- idx(
- $relationships,
- $revision->getID(),
- array()));
+ $data = idx($relationships, $revision->getID(), array());
+ $revision_edges = $edges[$revision->getPHID()][$type_reviewer];
+ foreach ($revision_edges as $dst_phid => $edge_data) {
+ $data[] = array(
+ 'relation' => DifferentialRevision::RELATION_REVIEWER,
+ 'objectPHID' => $dst_phid,
+ 'reasonPHID' => null,
+ );
+ }
+
+ $revision->attachRelationships($data);
}
}
@@ -996,18 +1053,45 @@
->withSourcePHIDs(mpull($revisions, 'getPHID'))
->withEdgeTypes(array($edge_type))
->needEdgeData(true)
+ ->setOrder(PhabricatorEdgeQuery::ORDER_OLDEST_FIRST)
->execute();
+ $viewer = $this->getViewer();
+ $viewer_phid = $viewer->getPHID();
+
+ // Figure out which of these reviewers the viewer has authority to act as.
+ if ($this->needReviewerAuthority && $viewer_phid) {
+ $allow_key = 'differential.allow-self-accept';
+ $allow_self = PhabricatorEnv::getEnvConfig($allow_key);
+ $authority = $this->loadReviewerAuthority(
+ $revisions,
+ $edges,
+ $allow_self);
+ }
+
foreach ($revisions as $revision) {
$revision_edges = $edges[$revision->getPHID()][$edge_type];
-
$reviewers = array();
- foreach ($revision_edges as $user_phid => $edge) {
- $data = $edge['data'];
- $reviewers[] = new DifferentialReviewer(
- $user_phid,
- idx($data, 'status'),
- idx($data, 'diff'));
+ foreach ($revision_edges as $reviewer_phid => $edge) {
+ $reviewer = new DifferentialReviewer($reviewer_phid, $edge['data']);
+
+ if ($this->needReviewerAuthority) {
+ if (!$viewer_phid) {
+ // Logged-out users never have authority.
+ $has_authority = false;
+ } else if ((!$allow_self) &&
+ ($revision->getAuthorPHID() == $viewer_phid)) {
+ // The author can never have authority unless we allow self-accept.
+ $has_authority = false;
+ } else {
+ // Otherwise, look up whether th viewer has authority.
+ $has_authority = isset($authority[$reviewer_phid]);
+ }
+
+ $reviewer->attachAuthority($viewer, $has_authority);
+ }
+
+ $reviewers[$reviewer_phid] = $reviewer;
}
$revision->attachReviewerStatus($reviewers);
@@ -1050,5 +1134,55 @@
return array($blocking, $active, $waiting);
}
+ private function loadReviewerAuthority(
+ array $revisions,
+ array $edges,
+ $allow_self) {
+
+ $revision_map = mpull($revisions, null, 'getPHID');
+ $viewer_phid = $this->getViewer()->getPHID();
+
+ // Find all the project reviewers which the user may have authority over.
+ $project_phids = array();
+ $project_type = PhabricatorProjectPHIDTypeProject::TYPECONST;
+ $edge_type = PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER;
+ foreach ($edges as $src => $types) {
+ if (!$allow_self) {
+ if ($revision_map[$src]->getAuthorPHID() == $viewer_phid) {
+ // If self-review isn't permitted, the user will never have
+ // authority over projects on revisions they authored because you
+ // can't accept your own revisions, so we don't need to load any
+ // data about these reviewers.
+ continue;
+ }
+ }
+ $edge_data = idx($types, $edge_type, array());
+ foreach ($edge_data as $dst => $data) {
+ if (phid_get_type($dst) == $project_type) {
+ $project_phids[] = $dst;
+ }
+ }
+ }
+
+ // Now, figure out which of these projects the viewer is actually a
+ // member of.
+ $project_authority = array();
+ if ($project_phids) {
+ $project_authority = id(new PhabricatorProjectQuery())
+ ->setViewer($this->getViewer())
+ ->withPHIDs($project_phids)
+ ->withMemberPHIDs(array($viewer_phid))
+ ->execute();
+ $project_authority = mpull($project_authority, 'getPHID');
+ }
+
+ // Finally, the viewer has authority over themselves.
+ return array(
+ $viewer_phid => true,
+ ) + array_fuse($project_authority);
+ }
+
+
+
}
Index: src/applications/differential/query/DifferentialRevisionSearchEngine.php
===================================================================
--- src/applications/differential/query/DifferentialRevisionSearchEngine.php
+++ src/applications/differential/query/DifferentialRevisionSearchEngine.php
@@ -23,7 +23,12 @@
$saved->setParameter(
'reviewerPHIDs',
- $this->readUsersFromRequest($request, 'reviewers'));
+ $this->readUsersFromRequest(
+ $request,
+ 'reviewers',
+ array(
+ PhabricatorProjectPHIDTypeProject::TYPECONST,
+ )));
$saved->setParameter(
'subscriberPHIDs',
@@ -140,7 +145,7 @@
id(new AphrontFormTokenizerControl())
->setLabel(pht('Reviewers'))
->setName('reviewers')
- ->setDatasource('/typeahead/common/accounts/')
+ ->setDatasource('/typeahead/common/accountsorprojects/')
->setValue(array_select_keys($handles, $reviewer_phids)))
->appendChild(
id(new AphrontFormTokenizerControl())
Index: src/applications/differential/storage/DifferentialReviewer.php
===================================================================
--- src/applications/differential/storage/DifferentialReviewer.php
+++ src/applications/differential/storage/DifferentialReviewer.php
@@ -2,13 +2,15 @@
final class DifferentialReviewer {
- protected $reviewerPHID;
- protected $status;
- protected $diffID;
+ private $reviewerPHID;
+ private $status;
+ private $diffID;
+ private $authority = array();
- public function __construct($reviewer_phid, $status, $diff_id = null) {
+ public function __construct($reviewer_phid, array $edge_data) {
$this->reviewerPHID = $reviewer_phid;
- $this->setStatus($status, $diff_id);
+ $this->status = idx($edge_data, 'status');
+ $this->diffID = idx($edge_data, 'diff');
}
public function getReviewerPHID() {
@@ -23,17 +25,25 @@
return $this->diffID;
}
- public function setStatus($status, $diff_id = null) {
- if ($status == DifferentialReviewerStatus::STATUS_REJECTED
- && $diff_id === null) {
+ public function isUser() {
+ $user_type = PhabricatorPeoplePHIDTypeUser::TYPECONST;
+ return (phid_get_type($this->getReviewerPHID()) == $user_type);
+ }
- throw new Exception('STATUS_REJECTED must have a diff_id set');
- }
+ public function attachAuthority(PhabricatorUser $user, $has_authority) {
+ $this->authority[$user->getPHID()] = $has_authority;
+ return $this;
+ }
- $this->status = $status;
- $this->diffID = $diff_id;
+ public function hasAuthority(PhabricatorUser $viewer) {
+ // It would be nice to use assertAttachedKey() here, but we don't extend
+ // PhabricatorLiskDAO, and faking that seems sketchy.
- return $this;
+ $viewer_phid = $viewer->getPHID();
+ if (!array_key_exists($viewer_phid, $this->authority)) {
+ throw new Exception("You must attachAuthority() first!");
+ }
+ return $this->authority[$viewer_phid];
}
}
Index: src/applications/differential/storage/DifferentialRevision.php
===================================================================
--- src/applications/differential/storage/DifferentialRevision.php
+++ src/applications/differential/storage/DifferentialRevision.php
@@ -6,20 +6,19 @@
PhabricatorPolicyInterface,
PhrequentTrackableInterface {
- protected $title;
+ protected $title = '';
protected $originalTitle;
protected $status;
- protected $summary;
- protected $testPlan;
+ protected $summary = '';
+ protected $testPlan = '';
- protected $phid;
protected $authorPHID;
protected $lastReviewerPHID;
protected $dateCommitted;
- protected $lineCount;
+ protected $lineCount = 0;
protected $attached = array();
protected $mailKey;
@@ -44,6 +43,22 @@
const RELATION_REVIEWER = 'revw';
const RELATION_SUBSCRIBED = 'subd';
+ public static function initializeNewRevision(PhabricatorUser $actor) {
+ $app = id(new PhabricatorApplicationQuery())
+ ->setViewer($actor)
+ ->withClasses(array('PhabricatorApplicationDifferential'))
+ ->executeOne();
+
+ $view_policy = $app->getPolicy(
+ DifferentialCapabilityDefaultView::CAPABILITY);
+
+ return id(new DifferentialRevision())
+ ->setViewPolicy($view_policy)
+ ->setAuthorPHID($actor->getPHID())
+ ->attachRelationships(array())
+ ->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
+ }
+
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
@@ -223,11 +238,30 @@
return;
}
+ // Read "subscribed" and "unsubscribed" data out of the old relationship
+ // table.
$data = queryfx_all(
$this->establishConnection('r'),
- 'SELECT * FROM %T WHERE revisionID = %d ORDER BY sequence',
+ 'SELECT * FROM %T WHERE revisionID = %d
+ AND relation != %s ORDER BY sequence',
self::RELATIONSHIP_TABLE,
- $this->getID());
+ $this->getID(),
+ self::RELATION_REVIEWER);
+
+ // Read "reviewer" data out of the new table.
+ $reviewer_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
+ $this->getPHID(),
+ PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER);
+ $reviewer_phids = array_reverse($reviewer_phids);
+
+ foreach ($reviewer_phids as $phid) {
+ $data[] = array(
+ 'relation' => self::RELATION_REVIEWER,
+ 'objectPHID' => $phid,
+ 'reasonPHID' => null,
+ );
+ }
+
return $this->attachRelationships($data);
}
@@ -338,11 +372,9 @@
case PhabricatorPolicyCapability::CAN_VIEW:
$description[] = pht(
"A revision's reviewers can always view it.");
- if ($this->getRepositoryPHID()) {
- $description[] = pht(
- 'This revision belongs to a repository. Other users must be able '.
- 'to view the repository in order to view this revision.');
- }
+ $description[] = pht(
+ 'If a revision belongs to a repository, other users must be able '.
+ 'to view the repository in order to view the revision.');
break;
}
Index: src/applications/differential/view/DifferentialAddCommentView.php
===================================================================
--- src/applications/differential/view/DifferentialAddCommentView.php
+++ src/applications/differential/view/DifferentialAddCommentView.php
@@ -118,12 +118,12 @@
'add_reviewers' => 1,
'resign' => 1,
),
- 'src' => '/typeahead/common/users/',
+ 'src' => '/typeahead/common/usersorprojects/',
'value' => $this->reviewers,
'row' => 'add-reviewers',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'labels' => $add_reviewers_labels,
- 'placeholder' => pht('Type a user name...'),
+ 'placeholder' => pht('Type a user or project name...'),
),
'add-ccs-tokenizer' => array(
'actions' => array('add_ccs' => 1),
Index: src/applications/differential/view/DifferentialChangesetDetailView.php
===================================================================
--- src/applications/differential/view/DifferentialChangesetDetailView.php
+++ src/applications/differential/view/DifferentialChangesetDetailView.php
@@ -45,6 +45,54 @@
return $this->vsChangesetID;
}
+ public function getFileIcon($filename) {
+ $path_info = pathinfo($filename);
+ $extension = $path_info['extension'];
+ switch ($extension) {
+ case 'psd':
+ case 'ai':
+ $icon = 'preview';
+ break;
+ case 'conf':
+ $icon = 'wrench';
+ break;
+ case 'wav':
+ case 'mp3':
+ case 'aiff':
+ $icon = 'music';
+ break;
+ case 'm4v':
+ case 'mov':
+ $icon = 'film';
+ break;
+ case 'sql';
+ case 'db':
+ case 'csv':
+ $icon = 'data';
+ break;
+ case 'ics':
+ $icon = 'calendar';
+ break;
+ case 'zip':
+ case 'tar':
+ case 'bz':
+ case 'tgz':
+ case 'gz':
+ $icon = 'zip';
+ break;
+ case 'png':
+ case 'jpg':
+ case 'bmp':
+ case 'gif':
+ $icon = 'image';
+ break;
+ default:
+ $icon = 'file';
+ break;
+ }
+ return $icon;
+ }
+
public function render() {
require_celerity_resource('differential-changeset-view-css');
require_celerity_resource('syntax-highlighting-css');
@@ -78,6 +126,10 @@
}
$display_filename = $changeset->getDisplayFilename();
+ $display_icon = $this->getFileIcon($display_filename);
+ $icon = id(new PHUIIconView())
+ ->setSpriteSheet(PHUIIconView::SPRITE_ICONS)
+ ->setSpriteIcon($display_icon);
return javelin_tag(
'div',
@@ -98,7 +150,12 @@
->setNavigationMarker(true)
->render(),
$buttons,
- phutil_tag('h1', array(), $display_filename),
+ phutil_tag('h1',
+ array(
+ 'class' => 'differential-file-icon-header'),
+ array(
+ $icon,
+ $display_filename)),
phutil_tag('div', array('style' => 'clear: both'), ''),
$this->renderChildren(),
));
Index: src/applications/differential/view/DifferentialReviewersView.php
===================================================================
--- /dev/null
+++ src/applications/differential/view/DifferentialReviewersView.php
@@ -0,0 +1,102 @@
+<?php
+
+final class DifferentialReviewersView extends AphrontView {
+
+ private $reviewers;
+ private $handles;
+ private $diff;
+
+ public function setReviewers(array $reviewers) {
+ assert_instances_of($reviewers, 'DifferentialReviewer');
+ $this->reviewers = $reviewers;
+ return $this;
+ }
+
+ public function setHandles(array $handles) {
+ assert_instances_of($handles, 'PhabricatorObjectHandle');
+ $this->handles = $handles;
+ return $this;
+ }
+
+ public function setActiveDiff(DifferentialDiff $diff) {
+ $this->diff = $diff;
+ return $this;
+ }
+
+ public function render() {
+ $viewer = $this->getUser();
+
+ $view = new PHUIStatusListView();
+ foreach ($this->reviewers as $reviewer) {
+ $phid = $reviewer->getReviewerPHID();
+ $handle = $this->handles[$phid];
+
+ // If we're missing either the diff or action information for the
+ // reviewer, render information as current.
+ $is_current = (!$this->diff) ||
+ (!$reviewer->getDiffID()) ||
+ ($this->diff->getID() == $reviewer->getDiffID());
+
+ $item = new PHUIStatusItemView();
+
+ $item->setHighlighted($reviewer->hasAuthority($viewer));
+
+ switch ($reviewer->getStatus()) {
+ case DifferentialReviewerStatus::STATUS_ADDED:
+ $item->setIcon('open-dark', pht('Review Requested'));
+ break;
+
+ case DifferentialReviewerStatus::STATUS_ACCEPTED:
+ if ($is_current) {
+ $item->setIcon(
+ 'accept-green',
+ pht('Accepted'));
+ } else {
+ $item->setIcon(
+ 'accept-dark',
+ pht('Accepted Prior Diff'));
+ }
+ break;
+
+ case DifferentialReviewerStatus::STATUS_REJECTED:
+ if ($is_current) {
+ $item->setIcon(
+ 'reject-red',
+ pht('Requested Changes'));
+ } else {
+ $item->setIcon(
+ 'reject-dark',
+ pht('Requested Changes to Prior Diff'));
+ }
+ break;
+
+ case DifferentialReviewerStatus::STATUS_COMMENTED:
+ if ($is_current) {
+ $item->setIcon(
+ 'info-blue',
+ pht('Commented'));
+ } else {
+ $item->setIcon(
+ 'info-dark',
+ pht('Commented Previously'));
+ }
+ break;
+
+ case DifferentialReviewerStatus::STATUS_BLOCKING:
+ $item->setIcon('minus-red', pht('Blocking Review'));
+ break;
+
+ default:
+ $item->setIcon('question-dark', pht('%s?', $reviewer->getStatus()));
+ break;
+
+ }
+
+ $item->setTarget($handle->renderLink());
+ $view->addItem($item);
+ }
+
+ return $view;
+ }
+
+}
Index: src/applications/differential/view/DifferentialRevisionDetailView.php
===================================================================
--- src/applications/differential/view/DifferentialRevisionDetailView.php
+++ src/applications/differential/view/DifferentialRevisionDetailView.php
@@ -68,7 +68,7 @@
$actions->addAction($obj);
}
- $properties = id(new PhabricatorPropertyListView())
+ $properties = id(new PHUIPropertyListView())
->setUser($user)
->setObject($revision);
@@ -110,11 +110,11 @@
}
}
$properties->setHasKeyboardShortcuts(true);
+ $properties->setActionList($actions);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
return $object_box;
}
Index: src/applications/diffusion/controller/DiffusionBrowseController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionBrowseController.php
+++ src/applications/diffusion/controller/DiffusionBrowseController.php
@@ -120,11 +120,15 @@
return $view;
}
- protected function buildPropertyView(DiffusionRequest $drequest) {
+ protected function buildPropertyView(
+ DiffusionRequest $drequest,
+ PhabricatorActionListView $actions) {
+
$viewer = $this->getRequest()->getUser();
- $view = id(new PhabricatorPropertyListView())
- ->setUser($viewer);
+ $view = id(new PHUIPropertyListView())
+ ->setUser($viewer)
+ ->setActionList($actions);
$stable_commit = $drequest->getStableCommitName();
$callsign = $drequest->getRepository()->getCallsign();
Index: src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php
+++ src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php
@@ -21,11 +21,12 @@
$reason = $results->getReasonForEmptyResultSet();
$content = array();
+ $actions = $this->buildActionView($drequest);
+ $properties = $this->buildPropertyView($drequest, $actions);
$object_box = id(new PHUIObjectBoxView())
->setHeader($this->buildHeaderView($drequest))
- ->setActionList($this->buildActionView($drequest))
- ->setPropertyList($this->buildPropertyView($drequest));
+ ->addPropertyList($properties);
$content[] = $object_box;
$content[] = $this->renderSearchForm($collapsed = true);
Index: src/applications/diffusion/controller/DiffusionBrowseFileController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionBrowseFileController.php
+++ src/applications/diffusion/controller/DiffusionBrowseFileController.php
@@ -107,10 +107,10 @@
$show_color,
$binary_uri);
+ $properties = $this->buildPropertyView($drequest, $action_list);
$object_box = id(new PHUIObjectBoxView())
->setHeader($this->buildHeaderView($drequest))
- ->setActionList($action_list)
- ->setPropertyList($this->buildPropertyView($drequest));
+ ->addPropertyList($properties);
$content = array();
$content[] = $object_box;
@@ -297,11 +297,14 @@
$corpus = phutil_tag(
'div',
array(
- 'style' => 'padding: 0 2em;',
'id' => $id,
),
$corpus_table);
+ $corpus = id(new PHUIObjectBoxView())
+ ->setHeaderText('File Contents')
+ ->appendChild($corpus);
+
Javelin::initBehavior('load-blame', array('id' => $id));
}
@@ -822,21 +825,22 @@
}
private function buildImageCorpus($file_uri) {
- $properties = new PhabricatorPropertyListView();
+ $properties = new PHUIPropertyListView();
- $properties->addProperty(
- pht('Image'),
+ $properties->addImageContent(
phutil_tag(
'img',
array(
'src' => $file_uri,
)));
- return $properties;
+ return id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Image'))
+ ->addPropertyList($properties);
}
private function buildBinaryCorpus($file_uri, $data) {
- $properties = new PhabricatorPropertyListView();
+ $properties = new PHUIPropertyListView();
$size = strlen($data);
$properties->addTextContent(
Index: src/applications/diffusion/controller/DiffusionBrowseSearchController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionBrowseSearchController.php
+++ src/applications/diffusion/controller/DiffusionBrowseSearchController.php
@@ -5,10 +5,12 @@
public function processRequest() {
$drequest = $this->diffusionRequest;
+ $actions = $this->buildActionView($drequest);
+ $properties = $this->buildPropertyView($drequest, $actions);
+
$object_box = id(new PHUIObjectBoxView())
->setHeader($this->buildHeaderView($drequest))
- ->setActionList($this->buildActionView($drequest))
- ->setPropertyList($this->buildPropertyView($drequest));
+ ->addPropertyList($properties);
$content = array();
Index: src/applications/diffusion/controller/DiffusionChangeController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionChangeController.php
+++ src/applications/diffusion/controller/DiffusionChangeController.php
@@ -81,12 +81,11 @@
->setUser($viewer)
->setPolicyObject($drequest->getRepository());
$actions = $this->buildActionView($drequest);
- $properties = $this->buildPropertyView($drequest);
+ $properties = $this->buildPropertyView($drequest, $actions);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
return $this->buildApplicationPage(
array(
@@ -130,11 +129,15 @@
return $view;
}
- protected function buildPropertyView(DiffusionRequest $drequest) {
+ protected function buildPropertyView(
+ DiffusionRequest $drequest,
+ PhabricatorActionListView $actions) {
+
$viewer = $this->getRequest()->getUser();
- $view = id(new PhabricatorPropertyListView())
- ->setUser($viewer);
+ $view = id(new PHUIPropertyListView())
+ ->setUser($viewer)
+ ->setActionList($actions);
$stable_commit = $drequest->getStableCommitName();
$callsign = $drequest->getRepository()->getCallsign();
Index: src/applications/diffusion/controller/DiffusionCommitController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionCommitController.php
+++ src/applications/diffusion/controller/DiffusionCommitController.php
@@ -113,7 +113,7 @@
$commit_data,
$parents,
$audit_requests);
- $property_list = id(new PhabricatorPropertyListView())
+ $property_list = id(new PHUIPropertyListView())
->setHasKeyboardShortcuts(true)
->setUser($user)
->setObject($commit);
@@ -129,7 +129,11 @@
$message = $engine->markupText($message);
$property_list->invokeWillRenderEvent();
- $property_list->addTextContent(
+ $property_list->setActionList($headsup_actions);
+
+ $detail_list = new PHUIPropertyListView();
+ $detail_list->addSectionHeader(pht('Description'));
+ $detail_list->addTextContent(
phutil_tag(
'div',
array(
@@ -140,8 +144,8 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($headsup_view)
- ->setActionList($headsup_actions)
- ->setPropertyList($property_list);
+ ->addPropertyList($property_list)
+ ->addPropertyList($detail_list);
$content[] = $object_box;
}
@@ -473,7 +477,25 @@
}
if ($audit_requests) {
- $props['Auditors'] = $this->renderAuditStatusView($audit_requests);
+ $user_requests = array();
+ $other_requests = array();
+ foreach ($audit_requests as $audit_request) {
+ if ($audit_request->isUser()) {
+ $user_requests[] = $audit_request;
+ } else {
+ $other_requests[] = $audit_request;
+ }
+ }
+
+ if ($user_requests) {
+ $props['Auditors'] = $this->renderAuditStatusView(
+ $user_requests);
+ }
+
+ if ($other_requests) {
+ $props['Project/Package Auditors'] = $this->renderAuditStatusView(
+ $other_requests);
+ }
}
$props['Committed'] = phabricator_datetime($commit->getEpoch(), $user);
@@ -1004,14 +1026,19 @@
$item->setIcon('warning-dark', pht('Audit Requested'));
break;
case PhabricatorAuditStatusConstants::RESIGNED:
- $item->setIcon('open-dark', pht('Accepted'));
+ $item->setIcon('open-dark', pht('Resigned'));
break;
case PhabricatorAuditStatusConstants::CLOSED:
- $item->setIcon('accept-blue', pht('Accepted'));
+ $item->setIcon('accept-blue', pht('Closed'));
break;
case PhabricatorAuditStatusConstants::CC:
$item->setIcon('info-dark', pht('Subscribed'));
break;
+ default:
+ $item->setIcon(
+ 'question-dark',
+ pht('%s?', $request->getAuditStatus()));
+ break;
}
$note = array();
Index: src/applications/diffusion/controller/DiffusionCommitEditController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionCommitEditController.php
+++ src/applications/diffusion/controller/DiffusionCommitEditController.php
@@ -27,7 +27,7 @@
$commit_phid,
$edge_type);
$handles = $this->loadViewerHandles($current_proj_phids);
- $proj_t_values = mpull($handles, 'getFullName', 'getPHID');
+ $proj_t_values = $handles;
if ($request->isFormPost()) {
$proj_phids = $request->getArr('projects');
Index: src/applications/diffusion/controller/DiffusionController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionController.php
+++ src/applications/diffusion/controller/DiffusionController.php
@@ -166,6 +166,12 @@
$path = $drequest->getPath();
$path_parts = array_filter(explode('/', trim($path, '/')));
+ $divider = phutil_tag(
+ 'span',
+ array(
+ 'class' => 'phui-header-divider'),
+ '/');
+
$links = array();
if ($path_parts) {
$links[] = phutil_tag(
@@ -177,11 +183,11 @@
'path' => '',
)),
),
- 'r'.$drequest->getRepository()->getCallsign().'/');
+ 'r'.$drequest->getRepository()->getCallsign());
+ $links[] = $divider;
$accum = '';
$last_key = last_key($path_parts);
foreach ($path_parts as $key => $part) {
- $links[] = ' ';
$accum .= '/'.$part;
if ($key === $last_key) {
$links[] = $part;
@@ -195,11 +201,13 @@
'path' => $accum.'/',
)),
),
- $part.'/');
+ $part);
+ $links[] = $divider;
}
}
} else {
- $links[] = 'r'.$drequest->getRepository()->getCallsign().'/';
+ $links[] = 'r'.$drequest->getRepository()->getCallsign();
+ $links[] = $divider;
}
return $links;
Index: src/applications/diffusion/controller/DiffusionHistoryController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionHistoryController.php
+++ src/applications/diffusion/controller/DiffusionHistoryController.php
@@ -70,12 +70,11 @@
->setHeader($this->renderPathLinks($drequest, $mode = 'history'));
$actions = $this->buildActionView($drequest);
- $properties = $this->buildPropertyView($drequest);
+ $properties = $this->buildPropertyView($drequest, $actions);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
$crumbs = $this->buildCrumbs(
array(
@@ -143,11 +142,15 @@
return $view;
}
- protected function buildPropertyView(DiffusionRequest $drequest) {
+ protected function buildPropertyView(
+ DiffusionRequest $drequest,
+ PhabricatorActionListView $actions) {
+
$viewer = $this->getRequest()->getUser();
- $view = id(new PhabricatorPropertyListView())
- ->setUser($viewer);
+ $view = id(new PHUIPropertyListView())
+ ->setUser($viewer)
+ ->setActionList($actions);
$stable_commit = $drequest->getStableCommitName();
$callsign = $drequest->getRepository()->getCallsign();
Index: src/applications/diffusion/controller/DiffusionLintController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionLintController.php
+++ src/applications/diffusion/controller/DiffusionLintController.php
@@ -21,18 +21,14 @@
$owners = array();
if (!$drequest) {
if (!$request->getArr('owner')) {
- if ($user->isLoggedIn()) {
- $owners[$user->getPHID()] = $user->getFullName();
- }
+ $owners = array($user->getPHID());
} else {
- $phids = $request->getArr('owner');
- $phid = reset($phids);
- $handles = $this->loadViewerHandles(array($phid));
- $owners[$phid] = $handles[$phid]->getFullName();
+ $owners = array(head($request->getArr('owner')));
}
+ $owner_handles = $this->loadViewerHandles($owners);
}
- $codes = $this->loadLintCodes(array_keys($owners));
+ $codes = $this->loadLintCodes($owners);
if ($codes && !$drequest) {
// TODO: Build some real Query classes for this stuff.
@@ -125,7 +121,7 @@
->setLimit(1)
->setName('owner')
->setLabel(pht('Owner'))
- ->setValue($owners))
+ ->setValue($owner_handles))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Filter'));
@@ -164,12 +160,12 @@
$properties = $this->buildPropertyView(
$drequest,
$branch,
- $total);
+ $total,
+ $actions);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
} else {
$object_box = null;
}
@@ -320,12 +316,14 @@
protected function buildPropertyView(
DiffusionRequest $drequest,
PhabricatorRepositoryBranch $branch,
- $total) {
+ $total,
+ PhabricatorActionListView $actions) {
$viewer = $this->getRequest()->getUser();
- $view = id(new PhabricatorPropertyListView())
- ->setUser($viewer);
+ $view = id(new PHUIPropertyListView())
+ ->setUser($viewer)
+ ->setActions($actions);
$callsign = $drequest->getRepository()->getCallsign();
$lint_commit = $branch->getLintCommit();
Index: src/applications/diffusion/controller/DiffusionRepositoryController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionRepositoryController.php
+++ src/applications/diffusion/controller/DiffusionRepositoryController.php
@@ -147,7 +147,7 @@
$actions = $this->buildActionList($repository);
- $view = id(new PhabricatorPropertyListView())
+ $view = id(new PHUIPropertyListView())
->setUser($user);
$view->addProperty(pht('Callsign'), $repository->getCallsign());
@@ -174,10 +174,11 @@
$view->addTextContent($description);
}
+ $view->setActionList($actions);
+
return id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($view);
+ ->addPropertyList($view);
}
Index: src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php
+++ src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php
@@ -65,18 +65,16 @@
}
}
- $content = array();
-
$crumbs = $this->buildCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit Basics')));
- $content[] = $crumbs;
$title = pht('Edit %s', $repository->getName());
+ $error_view = null;
if ($errors) {
- $content[] = id(new AphrontErrorView())
+ $error_view = id(new AphrontErrorView())
->setTitle(pht('Form Errors'))
->setErrors($errors);
}
@@ -101,10 +99,15 @@
->appendChild(id(new PHUIFormDividerControl()))
->appendRemarkupInstructions($this->getReadmeInstructions());
- $content[] = $form;
+ $object_box = id(new PHUIObjectBoxView())
+ ->setHeaderText($title)
+ ->setForm($form)
+ ->setFormError($error_view);
return $this->buildApplicationPage(
- $content,
+ array(
+ $crumbs,
+ $object_box),
array(
'title' => $title,
'device' => true,
Index: src/applications/diffusion/controller/DiffusionRepositoryEditController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionRepositoryEditController.php
+++ src/applications/diffusion/controller/DiffusionRepositoryEditController.php
@@ -8,13 +8,10 @@
$drequest = $this->diffusionRequest;
$repository = $drequest->getRepository();
- $content = array();
-
$crumbs = $this->buildCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit')));
- $content[] = $crumbs;
$title = pht('Edit %s', $repository->getName());
@@ -28,25 +25,17 @@
->setBackgroundColor(PhabricatorTagView::COLOR_BLACK));
}
- $content[] = $header;
-
- $content[] = $this->buildBasicActions($repository);
- $content[] = $this->buildBasicProperties($repository);
-
- $content[] = id(new PHUIHeaderView())
- ->setHeader(pht('Policies'));
-
- $content[] = $this->buildPolicyActions($repository);
- $content[] = $this->buildPolicyProperties($repository);
+ $basic_actions = $this->buildBasicActions($repository);
+ $basic_properties =
+ $this->buildBasicProperties($repository, $basic_actions);
- $content[] = id(new PHUIHeaderView())
- ->setHeader(pht('Text Encoding'));
+ $policy_actions = $this->buildPolicyActions($repository);
+ $policy_properties =
+ $this->buildPolicyProperties($repository, $policy_actions);
- $content[] = $this->buildEncodingActions($repository);
- $content[] = $this->buildEncodingProperties($repository);
-
- $content[] = id(new PHUIHeaderView())
- ->setHeader(pht('Edit History'));
+ $encoding_actions = $this->buildEncodingActions($repository);
+ $encoding_properties =
+ $this->buildEncodingProperties($repository, $encoding_actions);
$xactions = id(new PhabricatorRepositoryTransactionQuery())
->setViewer($user)
@@ -70,11 +59,18 @@
->setTransactions($xactions)
->setMarkupEngine($engine);
- $content[] = $xaction_view;
-
+ $obj_box = id(new PHUIObjectBoxView())
+ ->setHeader($header)
+ ->addPropertyList($basic_properties)
+ ->addPropertyList($policy_properties)
+ ->addPropertyList($encoding_properties);
return $this->buildApplicationPage(
- $content,
+ array(
+ $crumbs,
+ $obj_box,
+ $xaction_view,
+ ),
array(
'title' => $title,
'device' => true,
@@ -122,11 +118,15 @@
return $view;
}
- private function buildBasicProperties(PhabricatorRepository $repository) {
+ private function buildBasicProperties(
+ PhabricatorRepository $repository,
+ PhabricatorActionListView $actions) {
+
$user = $this->getRequest()->getUser();
- $view = id(new PhabricatorPropertyListView())
- ->setUser($user);
+ $view = id(new PHUIPropertyListView())
+ ->setUser($user)
+ ->setActionList($actions);
$view->addProperty(pht('Name'), $repository->getName());
$view->addProperty(pht('ID'), $repository->getID());
@@ -177,11 +177,16 @@
return $view;
}
- private function buildEncodingProperties(PhabricatorRepository $repository) {
+ private function buildEncodingProperties(
+ PhabricatorRepository $repository,
+ PhabricatorActionListView $actions) {
+
$user = $this->getRequest()->getUser();
- $view = id(new PhabricatorPropertyListView())
- ->setUser($user);
+ $view = id(new PHUIPropertyListView())
+ ->setUser($user)
+ ->setActionList($actions)
+ ->addSectionHeader(pht('Text Encoding'));
$encoding = $repository->getDetail('encoding');
if (!$encoding) {
@@ -217,11 +222,16 @@
return $view;
}
- private function buildPolicyProperties(PhabricatorRepository $repository) {
+ private function buildPolicyProperties(
+ PhabricatorRepository $repository,
+ PhabricatorActionListView $actions) {
+
$viewer = $this->getRequest()->getUser();
- $view = id(new PhabricatorPropertyListView())
- ->setUser($viewer);
+ $view = id(new PHUIPropertyListView())
+ ->setUser($viewer)
+ ->setActionList($actions)
+ ->addSectionHeader(pht('Policies'));
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$viewer,
Index: src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php
+++ src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php
@@ -56,18 +56,16 @@
}
}
- $content = array();
-
$crumbs = $this->buildCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit Encoding')));
- $content[] = $crumbs;
$title = pht('Edit %s', $repository->getName());
+ $error_view = null;
if ($errors) {
- $content[] = id(new AphrontErrorView())
+ $error_view = id(new AphrontErrorView())
->setTitle(pht('Form Errors'))
->setErrors($errors);
}
@@ -86,10 +84,16 @@
->setValue(pht('Save Encoding'))
->addCancelButton($edit_uri));
- $content[] = $form;
+ $object_box = id(new PHUIObjectBoxView())
+ ->setHeaderText($title)
+ ->setForm($form)
+ ->setFormError($error_view);
return $this->buildApplicationPage(
- $content,
+ array(
+ $crumbs,
+ $object_box,
+ ),
array(
'title' => $title,
'device' => true,
Index: src/applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php
===================================================================
--- src/applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php
+++ src/applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php
@@ -83,7 +83,12 @@
$request_phids = PhabricatorOwnersOwner::loadAffiliatedUserPHIDs(
array($object->getID()));
} else if ($object instanceof PhabricatorProject) {
- $request_phids = $object->loadMemberPHIDs();
+ $project = id(new PhabricatorProjectQuery())
+ ->setViewer($this->getViewer())
+ ->withIDs(array($object->getID()))
+ ->needMembers(true)
+ ->executeOne();
+ $request_phids = $project->getMemberPHIDs();
} else {
// Dunno what this is.
$request_phids = array();
Index: src/applications/directory/controller/PhabricatorDirectoryMainController.php
===================================================================
--- src/applications/directory/controller/PhabricatorDirectoryMainController.php
+++ src/applications/directory/controller/PhabricatorDirectoryMainController.php
@@ -41,15 +41,23 @@
$tasks_panel = null;
}
+ $audit = 'PhabricatorApplicationAudit';
+ if (PhabricatorApplication::isClassInstalled($audit)) {
+ $audit_panel = $this->buildAuditPanel();
+ $commit_panel = $this->buildCommitPanel();
+ } else {
+ $audit_panel = null;
+ $commit_panel = null;
+ }
+
if (PhabricatorEnv::getEnvConfig('welcome.html') !== null) {
$welcome_panel = $this->buildWelcomePanel();
} else {
$welcome_panel = null;
}
+
$jump_panel = $this->buildJumpPanel();
$revision_panel = $this->buildRevisionPanel();
- $audit_panel = $this->buildAuditPanel();
- $commit_panel = $this->buildCommitPanel();
$content = array(
$jump_panel,
Index: src/applications/diviner/controller/DivinerAtomController.php
===================================================================
--- src/applications/diviner/controller/DivinerAtomController.php
+++ src/applications/diviner/controller/DivinerAtomController.php
@@ -80,7 +80,7 @@
->setBackgroundColor(PhabricatorTagView::COLOR_BLUE)
->setName(DivinerAtom::getAtomTypeNameString($atom->getType())));
- $properties = id(new PhabricatorPropertyListView());
+ $properties = id(new PHUIPropertyListView());
$group = $atom->getProperty('group');
if ($group) {
@@ -255,7 +255,7 @@
}
private function buildExtendsAndImplements(
- PhabricatorPropertyListView $view,
+ PHUIPropertyListView $view,
DivinerLiveSymbol $symbol) {
$lineage = $this->getExtendsLineage($symbol);
@@ -335,7 +335,7 @@
}
private function buildDefined(
- PhabricatorPropertyListView $view,
+ PHUIPropertyListView $view,
DivinerLiveSymbol $symbol) {
$atom = $symbol->getAtom();
Index: src/applications/diviner/controller/DivinerBookController.php
===================================================================
--- src/applications/diviner/controller/DivinerBookController.php
+++ src/applications/diviner/controller/DivinerBookController.php
@@ -85,7 +85,7 @@
private function buildPropertyList(DivinerLiveBook $book) {
$user = $this->getRequest()->getUser();
- $view = id(new PhabricatorPropertyListView())
+ $view = id(new PHUIPropertyListView())
->setUser($user);
$policies = PhabricatorPolicyQuery::renderPolicyDescriptions(
Index: src/applications/diviner/storage/DivinerLiveBook.php
===================================================================
--- src/applications/diviner/storage/DivinerLiveBook.php
+++ src/applications/diviner/storage/DivinerLiveBook.php
@@ -3,7 +3,6 @@
final class DivinerLiveBook extends DivinerDAO
implements PhabricatorPolicyInterface {
- protected $phid;
protected $name;
protected $viewPolicy;
protected $configurationData = array();
Index: src/applications/diviner/storage/DivinerLiveSymbol.php
===================================================================
--- src/applications/diviner/storage/DivinerLiveSymbol.php
+++ src/applications/diviner/storage/DivinerLiveSymbol.php
@@ -3,7 +3,6 @@
final class DivinerLiveSymbol extends DivinerDAO
implements PhabricatorPolicyInterface, PhabricatorMarkupInterface {
- protected $phid;
protected $bookPHID;
protected $context;
protected $type;
Index: src/applications/doorkeeper/worker/DoorkeeperFeedWorkerAsana.php
===================================================================
--- src/applications/doorkeeper/worker/DoorkeeperFeedWorkerAsana.php
+++ src/applications/doorkeeper/worker/DoorkeeperFeedWorkerAsana.php
@@ -209,8 +209,12 @@
// Add the silent followers first so that a user who is both a reviewer and
// a CC gets silently added and then implicitly skipped by then noisy add.
// They will get a subtask notification.
- $this->addFollowers($oauth_token, $task_id, $silent_followers, true);
- $this->addFollowers($oauth_token, $task_id, $noisy_followers);
+
+ // We only do this if the task still exists.
+ if (empty($extra_data['gone'])) {
+ $this->addFollowers($oauth_token, $task_id, $silent_followers, true);
+ $this->addFollowers($oauth_token, $task_id, $noisy_followers);
+ }
$dst_phid = $parent_ref->getExternalObject()->getPHID();
Index: src/applications/drydock/controller/DrydockLeaseViewController.php
===================================================================
--- src/applications/drydock/controller/DrydockLeaseViewController.php
+++ src/applications/drydock/controller/DrydockLeaseViewController.php
@@ -25,7 +25,7 @@
->setHeader($title);
$actions = $this->buildActionListView($lease);
- $properties = $this->buildPropertyListView($lease);
+ $properties = $this->buildPropertyListView($lease, $actions);
$pager = new AphrontPagerView();
$pager->setURI(new PhutilURI($lease_uri), 'offset');
@@ -47,8 +47,7 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
return $this->buildApplicationPage(
array(
@@ -84,8 +83,12 @@
return $view;
}
- private function buildPropertyListView(DrydockLease $lease) {
- $view = new PhabricatorPropertyListView();
+ private function buildPropertyListView(
+ DrydockLease $lease,
+ PhabricatorActionListView $actions) {
+
+ $view = new PHUIPropertyListView();
+ $view->setActionList($actions);
switch ($lease->getStatus()) {
case DrydockLeaseStatus::STATUS_ACTIVE:
Index: src/applications/drydock/controller/DrydockResourceViewController.php
===================================================================
--- src/applications/drydock/controller/DrydockResourceViewController.php
+++ src/applications/drydock/controller/DrydockResourceViewController.php
@@ -23,7 +23,7 @@
->setHeader($title);
$actions = $this->buildActionListView($resource);
- $properties = $this->buildPropertyListView($resource);
+ $properties = $this->buildPropertyListView($resource, $actions);
$resource_uri = 'resource/'.$resource->getID().'/';
$resource_uri = $this->getApplicationURI($resource_uri);
@@ -58,8 +58,7 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
return $this->buildApplicationPage(
array(
@@ -97,8 +96,12 @@
return $view;
}
- private function buildPropertyListView(DrydockResource $resource) {
- $view = new PhabricatorPropertyListView();
+ private function buildPropertyListView(
+ DrydockResource $resource,
+ PhabricatorActionListView $actions) {
+
+ $view = new PHUIPropertyListView();
+ $view->setActionList($actions);
$status = $resource->getStatus();
$status = DrydockResourceStatus::getNameForStatus($status);
Index: src/applications/feed/query/PhabricatorFeedSearchEngine.php
===================================================================
--- src/applications/feed/query/PhabricatorFeedSearchEngine.php
+++ src/applications/feed/query/PhabricatorFeedSearchEngine.php
@@ -66,9 +66,8 @@
->setViewer($this->requireViewer())
->withPHIDs($phids)
->execute();
- $tokens = mpull($handles, 'getFullName', 'getPHID');
- $user_tokens = array_select_keys($tokens, $user_phids);
- $proj_tokens = array_select_keys($tokens, $proj_phids);
+ $user_handles = array_select_keys($handles, $user_phids);
+ $proj_handles = array_select_keys($handles, $proj_phids);
$viewer_projects = $saved_query->getParameter('viewerProjects');
@@ -78,13 +77,13 @@
->setDatasource('/typeahead/common/users/')
->setName('users')
->setLabel(pht('Include Users'))
- ->setValue($user_tokens))
+ ->setValue($user_handles))
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/projects/')
->setName('projectPHIDs')
->setLabel(pht('Include Projects'))
- ->setValue($proj_tokens))
+ ->setValue($proj_handles))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
Index: src/applications/feed/story/PhabricatorFeedStory.php
===================================================================
--- src/applications/feed/story/PhabricatorFeedStory.php
+++ src/applications/feed/story/PhabricatorFeedStory.php
@@ -283,6 +283,9 @@
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
+ public function getPHID() {
+ return null;
+ }
/**
* @task policy
Index: src/applications/files/controller/PhabricatorFileInfoController.php
===================================================================
--- src/applications/files/controller/PhabricatorFileInfoController.php
+++ src/applications/files/controller/PhabricatorFileInfoController.php
@@ -44,7 +44,7 @@
}
$actions = $this->buildActionView($file);
- $properties = $this->buildPropertyView($file);
+ $properties_array = $this->buildPropertyView($file, $actions);
$timeline = $this->buildTransactionView($file, $xactions);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
@@ -54,9 +54,11 @@
->setHref($this->getApplicationURI("/info/{$phid}/")));
$object_box = id(new PHUIObjectBoxView())
- ->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->setHeader($header);
+
+ foreach ($properties_array as $property_item) {
+ $object_box->addPropertyList($property_item);
+ }
return $this->buildApplicationPage(
array(
@@ -162,71 +164,80 @@
return $view;
}
- private function buildPropertyView(PhabricatorFile $file) {
+ private function buildPropertyView(
+ PhabricatorFile $file,
+ PhabricatorActionListView $actions) {
$request = $this->getRequest();
$user = $request->getUser();
- $view = id(new PhabricatorPropertyListView());
+ $listview = array();
+ $properties = id(new PHUIPropertyListView());
+ $properties->setActionList($actions);
if ($file->getAuthorPHID()) {
- $view->addProperty(
+ $properties->addProperty(
pht('Author'),
$this->getHandle($file->getAuthorPHID())->renderLink());
}
- $view->addProperty(
+ $properties->addProperty(
pht('Created'),
phabricator_datetime($file->getDateCreated(), $user));
- $view->addProperty(
+ $properties->addProperty(
pht('Size'),
phabricator_format_bytes($file->getByteSize()));
- $view->addSectionHeader(pht('Technical Details'));
+ $properties->addSectionHeader(pht('Technical Details'));
- $view->addProperty(
+ $properties->addProperty(
pht('Mime Type'),
$file->getMimeType());
- $view->addProperty(
+ $properties->addProperty(
pht('Engine'),
$file->getStorageEngine());
- $view->addProperty(
+ $properties->addProperty(
pht('Format'),
$file->getStorageFormat());
- $view->addProperty(
+ $properties->addProperty(
pht('Handle'),
$file->getStorageHandle());
+ $listview[] = $properties;
+
$metadata = $file->getMetadata();
if (!empty($metadata)) {
- $view->addSectionHeader(pht('Metadata'));
+ $mdata = id(new PHUIPropertyListView())
+ ->addSectionHeader(pht('Metadata'));
foreach ($metadata as $key => $value) {
- $view->addProperty(
+ $mdata->addProperty(
PhabricatorFile::getMetadataName($key),
$value);
}
+ $listview[] = $mdata;
}
$phids = $file->getObjectPHIDs();
if ($phids) {
- $view->addSectionHeader(pht('Attached'));
- $view->addProperty(
+ $attached = new PHUIPropertyListView();
+ $attached->addSectionHeader(pht('Attached'));
+ $attached->addProperty(
pht('Attached To'),
$this->renderHandlesForPHIDs($phids));
+ $listview[] = $attached;
}
-
if ($file->isViewableImage()) {
$image = phutil_tag(
'img',
array(
'src' => $file->getViewURI(),
- 'class' => 'phabricator-property-list-image',
+ 'class' => 'phui-property-list-image',
));
$linked_image = phutil_tag(
@@ -236,13 +247,15 @@
),
$image);
- $view->addImageContent($linked_image);
+ $media = id(new PHUIPropertyListView())
+ ->addImageContent($linked_image);
+ $listview[] = $media;
} else if ($file->isAudio()) {
$audio = phutil_tag(
'audio',
array(
'controls' => 'controls',
- 'class' => 'phabricator-property-list-audio',
+ 'class' => 'phui-property-list-audio',
),
phutil_tag(
'source',
@@ -250,10 +263,12 @@
'src' => $file->getViewURI(),
'type' => $file->getMimeType(),
)));
- $view->addImageContent($audio);
+ $media = id(new PHUIPropertyListView())
+ ->addImageContent($audio);
+ $listview[] = $media;
}
- return $view;
+ return $listview;
}
}
Index: src/applications/files/controller/PhabricatorFileTransformController.php
===================================================================
--- src/applications/files/controller/PhabricatorFileTransformController.php
+++ src/applications/files/controller/PhabricatorFileTransformController.php
@@ -7,21 +7,24 @@
private $phid;
private $key;
+ public function shouldRequireLogin() {
+ return false;
+ }
+
public function willProcessRequest(array $data) {
$this->transform = $data['transform'];
$this->phid = $data['phid'];
$this->key = $data['key'];
}
- public function shouldRequireLogin() {
- return false;
- }
-
public function processRequest() {
$viewer = $this->getRequest()->getUser();
+ // NOTE: This is a public/CDN endpoint, and permission to see files is
+ // controlled by knowing the secret key, not by authentication.
+
$file = id(new PhabricatorFileQuery())
- ->setViewer($viewer)
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($this->phid))
->executeOne();
if (!$file) {
@@ -130,7 +133,7 @@
PhabricatorTransformedFile $xform) {
$file = id(new PhabricatorFileQuery())
- ->setViewer($this->getRequest()->getUser())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($xform->getTransformedPHID()))
->executeOne();
if (!$file) {
Index: src/applications/files/controller/PhabricatorFileUploadDialogController.php
===================================================================
--- src/applications/files/controller/PhabricatorFileUploadDialogController.php
+++ src/applications/files/controller/PhabricatorFileUploadDialogController.php
@@ -9,7 +9,7 @@
$dialog = id(new AphrontDialogView())
->setUser($user)
- ->setTitle(pht('Upload Image'))
+ ->setTitle(pht('Upload File'))
->appendChild(pht(
'To add files, drag and drop them into the comment text area.'))
->addCancelButton('/', pht('Close'));
Index: src/applications/files/mail/FileReplyHandler.php
===================================================================
--- src/applications/files/mail/FileReplyHandler.php
+++ src/applications/files/mail/FileReplyHandler.php
@@ -32,8 +32,8 @@
$actor = $this->getActor();
$file = $this->getMailReceiver();
- $body = $mail->getCleanTextBody();
- $body = trim($body);
+ $body_data = $mail->parseBody();
+ $body = $body_data['body'];
$body = $this->enhanceBodyWithAttachments($body, $mail->getAttachments());
$content_source = PhabricatorContentSource::newForSource(
@@ -42,19 +42,8 @@
'id' => $mail->getID(),
));
- $lines = explode("\n", trim($body));
- $first_line = head($lines);
-
$xactions = array();
- $command = null;
- $matches = null;
- if (preg_match('/^!(\w+)/', $first_line, $matches)) {
- $lines = array_slice($lines, 1);
- $body = implode("\n", $lines);
- $body = trim($body);
-
- $command = $matches[1];
- }
+ $command = $body_data['body'];
switch ($command) {
case 'unsubscribe':
Index: src/applications/files/query/PhabricatorFileQuery.php
===================================================================
--- src/applications/files/query/PhabricatorFileQuery.php
+++ src/applications/files/query/PhabricatorFileQuery.php
@@ -128,12 +128,14 @@
$object_phids[$phid] = true;
}
}
+ $object_phids = array_keys($object_phids);
// Now, load the objects.
$objects = array();
if ($object_phids) {
$objects = id(new PhabricatorObjectQuery())
+ ->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($object_phids)
->execute();
Index: src/applications/files/query/PhabricatorFileSearchEngine.php
===================================================================
--- src/applications/files/query/PhabricatorFileSearchEngine.php
+++ src/applications/files/query/PhabricatorFileSearchEngine.php
@@ -46,11 +46,10 @@
PhabricatorSavedQuery $saved_query) {
$phids = $saved_query->getParameter('authorPHIDs', array());
- $handles = id(new PhabricatorHandleQuery())
+ $author_handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireViewer())
->withPHIDs($phids)
->execute();
- $author_tokens = mpull($handles, 'getFullName', 'getPHID');
$explicit = $saved_query->getParameter('explicit');
@@ -60,7 +59,7 @@
->setDatasource('/typeahead/common/users/')
->setName('authors')
->setLabel(pht('Authors'))
- ->setValue($author_tokens))
+ ->setValue($author_handles))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
Index: src/applications/files/storage/PhabricatorFile.php
===================================================================
--- src/applications/files/storage/PhabricatorFile.php
+++ src/applications/files/storage/PhabricatorFile.php
@@ -14,7 +14,6 @@
const METADATA_IMAGE_WIDTH = 'width';
const METADATA_IMAGE_HEIGHT = 'height';
- protected $phid;
protected $name;
protected $mimeType;
protected $byteSize;
@@ -765,8 +764,10 @@
);
}
+ // NOTE: Anyone is allowed to access builtin files.
+
$files = id(new PhabricatorFileQuery())
- ->setViewer($user)
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
->withTransforms($specs)
->execute();
@@ -860,6 +861,25 @@
return idx($this->metadata, self::METADATA_IMAGE_WIDTH);
}
+ /**
+ * Write the policy edge between this file and some object.
+ *
+ * @param PhabricatorUser Acting user.
+ * @param phid Object PHID to attach to.
+ * @return this
+ */
+ public function attachToObject(PhabricatorUser $actor, $phid) {
+ $edge_type = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_FILE;
+
+ id(new PhabricatorEdgeEditor())
+ ->setActor($actor)
+ ->setSuppressEvents(true)
+ ->addEdge($phid, $edge_type, $this->getPHID())
+ ->save();
+
+ return $this;
+ }
+
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
Index: src/applications/herald/adapter/HeraldAdapter.php
===================================================================
--- src/applications/herald/adapter/HeraldAdapter.php
+++ src/applications/herald/adapter/HeraldAdapter.php
@@ -23,6 +23,7 @@
const FIELD_AFFECTED_PACKAGE_OWNER = 'affected-package-owner';
const FIELD_CONTENT_SOURCE = 'contentsource';
const FIELD_ALWAYS = 'always';
+ const FIELD_AUTHOR_PROJECTS = 'authorprojects';
const CONDITION_CONTAINS = 'contains';
const CONDITION_NOT_CONTAINS = '!contains';
@@ -51,6 +52,8 @@
const ACTION_FLAG = 'flag';
const ACTION_ASSIGN_TASK = 'assigntask';
const ACTION_ADD_PROJECTS = 'addprojects';
+ const ACTION_ADD_REVIEWERS = 'addreviewers';
+ const ACTION_ADD_BLOCKING_REVIEWERS = 'addblockingreviewers';
const VALUE_TEXT = 'text';
const VALUE_NONE = 'none';
@@ -63,6 +66,7 @@
const VALUE_PROJECT = 'project';
const VALUE_FLAG_COLOR = 'flagcolor';
const VALUE_CONTENT_SOURCE = 'contentsource';
+ const VALUE_USER_OR_PROJECT = 'userorproject';
private $contentSource;
@@ -93,10 +97,6 @@
abstract public function applyHeraldEffects(array $effects);
- public function isEnabled() {
- return true;
- }
-
public function isAvailableToUser(PhabricatorUser $viewer) {
$applications = id(new PhabricatorApplicationQuery())
->setViewer($viewer)
@@ -118,6 +118,7 @@
abstract public function getAdapterContentName();
abstract public function getAdapterApplicationClass();
+ abstract public function getObject();
/* -( Fields )------------------------------------------------------------- */
@@ -150,6 +151,7 @@
pht("Any affected package's owner"),
self::FIELD_CONTENT_SOURCE => pht('Content Source'),
self::FIELD_ALWAYS => pht('Always'),
+ self::FIELD_AUTHOR_PROJECTS => pht("Author's projects"),
);
}
@@ -167,7 +169,7 @@
self::CONDITION_IS_NOT_ANY => pht('is not any of'),
self::CONDITION_INCLUDE_ALL => pht('include all of'),
self::CONDITION_INCLUDE_ANY => pht('include any of'),
- self::CONDITION_INCLUDE_NONE => pht('include none of'),
+ self::CONDITION_INCLUDE_NONE => pht('do not include'),
self::CONDITION_IS_ME => pht('is myself'),
self::CONDITION_IS_NOT_ME => pht('is not myself'),
self::CONDITION_REGEXP => pht('matches regexp'),
@@ -202,6 +204,7 @@
case self::FIELD_TAGS:
case self::FIELD_REVIEWERS:
case self::FIELD_CC:
+ case self::FIELD_AUTHOR_PROJECTS:
return array(
self::CONDITION_INCLUDE_ALL,
self::CONDITION_INCLUDE_ANY,
@@ -296,7 +299,7 @@
}
if (!is_array($condition_value)) {
throw new HeraldInvalidConditionException(
- "Expected conditionv value to be an array.");
+ "Expected condition value to be an array.");
}
$have = array_select_keys(array_fuse($field_value), $condition_value);
@@ -485,6 +488,8 @@
self::ACTION_FLAG => pht('Mark with flag'),
self::ACTION_ASSIGN_TASK => pht('Assign task to'),
self::ACTION_ADD_PROJECTS => pht('Add projects'),
+ self::ACTION_ADD_REVIEWERS => pht('Add reviewers'),
+ self::ACTION_ADD_BLOCKING_REVIEWERS => pht('Add blocking reviewers'),
);
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
return array(
@@ -494,8 +499,11 @@
self::ACTION_EMAIL => pht('Send me an email'),
self::ACTION_AUDIT => pht('Trigger an Audit by me'),
self::ACTION_FLAG => pht('Mark with flag'),
- self::ACTION_ASSIGN_TASK => pht('Assign task to me.'),
+ self::ACTION_ASSIGN_TASK => pht('Assign task to me'),
self::ACTION_ADD_PROJECTS => pht('Add projects'),
+ self::ACTION_ADD_REVIEWERS => pht('Add me as a reviewer'),
+ self::ACTION_ADD_BLOCKING_REVIEWERS =>
+ pht('Add me as a blocking reviewer'),
);
default:
throw new Exception("Unknown rule type '{$rule_type}'!");
@@ -521,6 +529,8 @@
case self::ACTION_REMOVE_CC:
case self::ACTION_AUDIT:
case self::ACTION_ASSIGN_TASK:
+ case self::ACTION_ADD_REVIEWERS:
+ case self::ACTION_ADD_BLOCKING_REVIEWERS:
// For personal rules, force these actions to target the rule owner.
$target = array($author_phid);
break;
@@ -586,6 +596,8 @@
return self::VALUE_TAG;
case self::FIELD_AFFECTED_PACKAGE:
return self::VALUE_OWNERS_PACKAGE;
+ case self::FIELD_AUTHOR_PROJECTS:
+ return self::VALUE_PROJECT;
default:
return self::VALUE_USER;
}
@@ -615,6 +627,8 @@
case self::ACTION_NOTHING:
case self::ACTION_AUDIT:
case self::ACTION_ASSIGN_TASK:
+ case self::ACTION_ADD_REVIEWERS:
+ case self::ACTION_ADD_BLOCKING_REVIEWERS:
return self::VALUE_NONE;
case self::ACTION_FLAG:
return self::VALUE_FLAG_COLOR;
@@ -638,6 +652,9 @@
return self::VALUE_FLAG_COLOR;
case self::ACTION_ASSIGN_TASK:
return self::VALUE_USER;
+ case self::ACTION_ADD_REVIEWERS:
+ case self::ACTION_ADD_BLOCKING_REVIEWERS:
+ return self::VALUE_USER_OR_PROJECT;
default:
throw new Exception("Unknown or invalid action '{$action}'.");
}
@@ -755,7 +772,10 @@
}
$out[] = null;
- if ($rule->getRepetitionPolicy() == HeraldRepetitionPolicyConfig::EVERY) {
+ $integer_code_for_every = HeraldRepetitionPolicyConfig::toInt(
+ HeraldRepetitionPolicyConfig::EVERY);
+
+ if ($rule->getRepetitionPolicy() == $integer_code_for_every) {
$out[] = pht('Take these actions every time this rule matches:');
} else {
$out[] = pht('Take these actions the first time this rule matches:');
Index: src/applications/herald/adapter/HeraldCommitAdapter.php
===================================================================
--- src/applications/herald/adapter/HeraldCommitAdapter.php
+++ src/applications/herald/adapter/HeraldCommitAdapter.php
@@ -31,6 +31,10 @@
return 'PhabricatorApplicationDiffusion';
}
+ public function getObject() {
+ return $this->commit;
+ }
+
public function getAdapterContentType() {
return 'commit';
}
@@ -146,6 +150,8 @@
$object = new HeraldCommitAdapter();
+ $commit->attachRepository($repository);
+
$object->repository = $repository;
$object->commit = $commit;
$object->commitData = $commit_data;
@@ -221,10 +227,20 @@
$data = $this->commitData;
$revision_id = $data->getCommitDetail('differential.revisionID');
if ($revision_id) {
- // TODO: (T603) Herald policy stuff.
- $revision = id(new DifferentialRevision())->load($revision_id);
+ // NOTE: The Herald rule owner might not actually have access to
+ // the revision, and can control which revision a commit is
+ // associated with by putting text in the commit message. However,
+ // the rules they can write against revisions don't actually expose
+ // anything interesting, so it seems reasonable to load unconditionally
+ // here.
+
+ $revision = id(new DifferentialRevisionQuery())
+ ->withIDs(array($revision_id))
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->needRelationships(true)
+ ->needReviewerStatus(true)
+ ->executeOne();
if ($revision) {
- $revision->loadRelationships();
$this->affectedRevision = $revision;
}
}
Index: src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php
===================================================================
--- src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php
+++ src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php
@@ -1,8 +1,5 @@
<?php
-/**
- * @group herald
- */
final class HeraldDifferentialRevisionAdapter extends HeraldAdapter {
protected $revision;
@@ -15,6 +12,8 @@
protected $newCCs = array();
protected $remCCs = array();
protected $emailPHIDs = array();
+ protected $addReviewerPHIDs = array();
+ protected $blockingReviewerPHIDs = array();
protected $repository;
protected $affectedPackages;
@@ -24,6 +23,10 @@
return 'PhabricatorApplicationDifferential';
}
+ public function getObject() {
+ return $this->revision;
+ }
+
public function getAdapterContentType() {
return 'differential';
}
@@ -38,6 +41,7 @@
self::FIELD_TITLE,
self::FIELD_BODY,
self::FIELD_AUTHOR,
+ self::FIELD_AUTHOR_PROJECTS,
self::FIELD_REVIEWERS,
self::FIELD_CC,
self::FIELD_REPOSITORY,
@@ -62,10 +66,16 @@
public static function newLegacyAdapter(
DifferentialRevision $revision,
DifferentialDiff $diff) {
-
$object = new HeraldDifferentialRevisionAdapter();
- $revision->loadRelationships();
+ // Reload the revision to pick up relationship information.
+ $revision = id(new DifferentialRevisionQuery())
+ ->withIDs(array($revision->getID()))
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->needRelationships(true)
+ ->needReviewerStatus(true)
+ ->executeOne();
+
$object->revision = $revision;
$object->diff = $diff;
@@ -99,6 +109,14 @@
return $this->emailPHIDs;
}
+ public function getReviewersAddedByHerald() {
+ return $this->addReviewerPHIDs;
+ }
+
+ public function getBlockingReviewersAddedByHerald() {
+ return $this->blockingReviewerPHIDs;
+ }
+
public function getPHID() {
return $this->revision->getPHID();
}
@@ -272,6 +290,18 @@
case self::FIELD_AUTHOR:
return $this->revision->getAuthorPHID();
break;
+ case self::FIELD_AUTHOR_PROJECTS:
+ $author_phid = $this->revision->getAuthorPHID();
+ if (!$author_phid) {
+ return array();
+ }
+
+ $projects = id(new PhabricatorProjectQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withMemberPHIDs(array($author_phid))
+ ->execute();
+
+ return mpull($projects, 'getPHID');
case self::FIELD_DIFF_FILE:
return $this->loadAffectedPaths();
case self::FIELD_CC:
@@ -317,6 +347,8 @@
self::ACTION_ADD_CC,
self::ACTION_REMOVE_CC,
self::ACTION_EMAIL,
+ self::ACTION_ADD_REVIEWERS,
+ self::ACTION_ADD_BLOCKING_REVIEWERS,
self::ACTION_NOTHING,
);
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
@@ -325,6 +357,8 @@
self::ACTION_REMOVE_CC,
self::ACTION_EMAIL,
self::ACTION_FLAG,
+ self::ACTION_ADD_REVIEWERS,
+ self::ACTION_ADD_BLOCKING_REVIEWERS,
self::ACTION_NOTHING,
);
}
@@ -414,6 +448,26 @@
true,
pht('Removed addresses from CC list.'));
break;
+ case self::ACTION_ADD_REVIEWERS:
+ foreach ($effect->getTarget() as $phid) {
+ $this->addReviewerPHIDs[$phid] = true;
+ }
+ $result[] = new HeraldApplyTranscript(
+ $effect,
+ true,
+ pht('Added reviewers.'));
+ break;
+ case self::ACTION_ADD_BLOCKING_REVIEWERS:
+ // This adds reviewers normally, it just also marks them blocking.
+ foreach ($effect->getTarget() as $phid) {
+ $this->addReviewerPHIDs[$phid] = true;
+ $this->blockingReviewerPHIDs[$phid] = true;
+ }
+ $result[] = new HeraldApplyTranscript(
+ $effect,
+ true,
+ pht('Added blocking reviewers.'));
+ break;
default:
throw new Exception("No rules to handle action '{$action}'.");
}
Index: src/applications/herald/adapter/HeraldManiphestTaskAdapter.php
===================================================================
--- src/applications/herald/adapter/HeraldManiphestTaskAdapter.php
+++ src/applications/herald/adapter/HeraldManiphestTaskAdapter.php
@@ -22,6 +22,10 @@
return $this->task;
}
+ public function getObject() {
+ return $this->task;
+ }
+
private function setCcPHIDs(array $cc_phids) {
$this->ccPHIDs = $cc_phids;
return $this;
@@ -118,11 +122,9 @@
pht('Great success at doing nothing.'));
break;
case self::ACTION_ADD_CC:
- $add_cc = array();
foreach ($effect->getTarget() as $phid) {
- $add_cc[$phid] = true;
+ $this->ccPHIDs[] = $phid;
}
- $this->setCcPHIDs(array_keys($add_cc));
$result[] = new HeraldApplyTranscript(
$effect,
true,
@@ -143,11 +145,9 @@
pht('Assigned task.'));
break;
case self::ACTION_ADD_PROJECTS:
- $add_projects = array();
foreach ($effect->getTarget() as $phid) {
- $add_projects[$phid] = true;
+ $this->projectPHIDs[] = $phid;
}
- $this->setProjectPHIDs(array_keys($add_projects));
$result[] = new HeraldApplyTranscript(
$effect,
true,
Index: src/applications/herald/adapter/HeraldPholioMockAdapter.php
===================================================================
--- src/applications/herald/adapter/HeraldPholioMockAdapter.php
+++ src/applications/herald/adapter/HeraldPholioMockAdapter.php
@@ -12,6 +12,10 @@
return 'PhabricatorApplicationPholio';
}
+ public function getObject() {
+ return $this->mock;
+ }
+
public function setMock(PholioMock $mock) {
$this->mock = $mock;
return $this;
@@ -97,11 +101,9 @@
pht('Great success at doing nothing.'));
break;
case self::ACTION_ADD_CC:
- $add_cc = array();
foreach ($effect->getTarget() as $phid) {
- $add_cc[$phid] = true;
+ $this->ccPHIDs[] = $phid;
}
- $this->setCcPHIDs(array_keys($add_cc));
$result[] = new HeraldApplyTranscript(
$effect,
true,
Index: src/applications/herald/application/PhabricatorApplicationHerald.php
===================================================================
--- src/applications/herald/application/PhabricatorApplicationHerald.php
+++ src/applications/herald/application/PhabricatorApplicationHerald.php
@@ -2,9 +2,6 @@
final class PhabricatorApplicationHerald extends PhabricatorApplication {
- const CAN_CREATE_RULE = 'herald.create';
- const CAN_CREATE_GLOBAL_RULE = 'herald.global';
-
public function getBaseURI() {
return '/herald/';
}
@@ -41,8 +38,9 @@
=> 'HeraldNewController',
'rule/(?P<id>[1-9]\d*)/' => 'HeraldRuleViewController',
'edit/(?:(?P<id>[1-9]\d*)/)?' => 'HeraldRuleController',
+ 'disable/(?P<id>[1-9]\d*)/(?P<action>\w+)/' =>
+ 'HeraldDisableController',
'history/(?:(?P<id>[1-9]\d*)/)?' => 'HeraldRuleEditHistoryController',
- 'delete/(?P<id>[1-9]\d*)/' => 'HeraldDeleteController',
'test/' => 'HeraldTestConsoleController',
'transcript/' => 'HeraldTranscriptListController',
'transcript/(?P<id>[1-9]\d*)/(?:(?P<filter>\w+)/)?'
@@ -53,11 +51,7 @@
protected function getCustomCapabilities() {
return array(
- self::CAN_CREATE_RULE => array(
- 'label' => pht('Can Create Rules'),
- ),
- self::CAN_CREATE_GLOBAL_RULE => array(
- 'label' => pht('Can Create Global Rules'),
+ HeraldCapabilityManageGlobalRules::CAPABILITY => array(
'caption' => pht('Global rules can bypass access controls.'),
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
Index: src/applications/herald/capability/HeraldCapabilityManageGlobalRules.php
===================================================================
--- /dev/null
+++ src/applications/herald/capability/HeraldCapabilityManageGlobalRules.php
@@ -0,0 +1,20 @@
+<?php
+
+final class HeraldCapabilityManageGlobalRules
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'herald.global';
+
+ public function getCapabilityKey() {
+ return self::CAPABILITY;
+ }
+
+ public function getCapabilityName() {
+ return pht('Can Manage Global Rules');
+ }
+
+ public function describeCapabilityRejection() {
+ return pht('You do not have permission to manage global Herald rules.');
+ }
+
+}
Index: src/applications/herald/controller/HeraldController.php
===================================================================
--- src/applications/herald/controller/HeraldController.php
+++ src/applications/herald/controller/HeraldController.php
@@ -23,15 +23,11 @@
public function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
- $can_create = $this->hasApplicationCapability(
- PhabricatorApplicationHerald::CAN_CREATE_RULE);
-
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('Create Herald Rule'))
->setHref($this->getApplicationURI('new/'))
- ->setIcon('create')
- ->setDisabled(!$can_create));
+ ->setIcon('create'));
return $crumbs;
}
Index: src/applications/herald/controller/HeraldDeleteController.php
===================================================================
--- src/applications/herald/controller/HeraldDeleteController.php
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-
-final class HeraldDeleteController extends HeraldController {
-
- private $id;
-
- public function getFilter() {
- // note this controller is only used from a dialog-context at the moment
- // and there is actually no "delete" filter
- return 'delete';
- }
-
- public function willProcessRequest(array $data) {
- $this->id = $data['id'];
- }
-
- public function processRequest() {
-
- $rule = id(new HeraldRule())->load($this->id);
- if (!$rule) {
- return new Aphront404Response();
- }
-
- $request = $this->getRequest();
- $user = $request->getUser();
-
- // Anyone can delete a global rule, but only the rule owner can delete a
- // personal one.
- if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
- if ($user->getPHID() != $rule->getAuthorPHID()) {
- return new Aphront400Response();
- }
- }
-
- if ($request->isFormPost()) {
- $rule->openTransaction();
- $rule->logEdit($user->getPHID(), 'delete');
- $rule->delete();
- $rule->saveTransaction();
- return id(new AphrontReloadResponse())->setURI('/herald/');
- }
-
- $dialog = new AphrontDialogView();
- $dialog->setUser($request->getUser());
- $dialog->setTitle(pht('Really delete this rule?'));
- $dialog->appendChild(pht(
- "Are you sure you want to delete the rule: %s?",
- $rule->getName()));
- $dialog->addSubmitButton(pht('Delete'));
- $dialog->addCancelButton('/herald/');
- $dialog->setSubmitURI($request->getPath());
-
- return id(new AphrontDialogResponse())->setDialog($dialog);
-
- }
-
-}
Index: src/applications/herald/controller/HeraldDisableController.php
===================================================================
--- /dev/null
+++ src/applications/herald/controller/HeraldDisableController.php
@@ -0,0 +1,74 @@
+<?php
+
+final class HeraldDisableController extends HeraldController {
+
+ private $id;
+ private $action;
+
+ public function willProcessRequest(array $data) {
+ $this->id = $data['id'];
+ $this->action = $data['action'];
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+ $id = $this->id;
+
+ $rule = id(new HeraldRuleQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($id))
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ))
+ ->executeOne();
+ if (!$rule) {
+ return new Aphront404Response();
+ }
+
+ if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) {
+ $this->requireApplicationCapability(
+ HeraldCapabilityManageGlobalRules::CAPABILITY);
+ }
+
+ $view_uri = $this->getApplicationURI("rule/{$id}/");
+
+ $is_disable = ($this->action === 'disable');
+
+ if ($request->isFormPost()) {
+ $xaction = id(new HeraldRuleTransaction())
+ ->setTransactionType(HeraldRuleTransaction::TYPE_DISABLE)
+ ->setNewValue($is_disable);
+
+ id(new HeraldRuleEditor())
+ ->setActor($viewer)
+ ->setContinueOnNoEffect(true)
+ ->setContentSourceFromRequest($request)
+ ->applyTransactions($rule, array($xaction));
+
+ return id(new AphrontRedirectResponse())->setURI($view_uri);
+ }
+
+ if ($is_disable) {
+ $title = pht('Really disable this rule?');
+ $body = pht('This rule will no longer activate.');
+ $button = pht('Disable Rule');
+ } else {
+ $title = pht('Really enable this rule?');
+ $body = pht('This rule will become active again.');
+ $button = pht('Enable Rule');
+ }
+
+ $dialog = id(new AphrontDialogView())
+ ->setUser($viewer)
+ ->setTitle($title)
+ ->appendChild($body)
+ ->addSubmitButton($button)
+ ->addCancelButton($view_uri);
+
+ return id(new AphrontDialogResponse())->setDialog($dialog);
+ }
+
+}
Index: src/applications/herald/controller/HeraldNewController.php
===================================================================
--- src/applications/herald/controller/HeraldNewController.php
+++ src/applications/herald/controller/HeraldNewController.php
@@ -14,12 +14,6 @@
$request = $this->getRequest();
$user = $request->getUser();
- $this->requireApplicationCapability(
- PhabricatorApplicationHerald::CAN_CREATE_RULE);
-
- $can_global = $this->hasApplicationCapability(
- PhabricatorApplicationHerald::CAN_CREATE_GLOBAL_RULE);
-
$content_type_map = HeraldAdapter::getEnabledAdapterMap($user);
if (empty($content_type_map[$this->contentType])) {
$this->contentType = head_key($content_type_map);
@@ -37,29 +31,24 @@
HeraldRuleTypeConfig::RULE_TYPE_PERSONAL,
)) + $rule_type_map;
- if (!$can_global) {
- $global_link = $this->explainApplicationCapability(
- PhabricatorApplicationHerald::CAN_CREATE_GLOBAL_RULE,
- pht('You do not have permission to create or manage global rules.'));
- } else {
- $global_link = null;
- }
+ list($can_global, $global_link) = $this->explainApplicationCapability(
+ HeraldCapabilityManageGlobalRules::CAPABILITY,
+ pht('You have permission to create and manage global rules.'),
+ pht('You do not have permission to create or manage global rules.'));
$captions = array(
HeraldRuleTypeConfig::RULE_TYPE_PERSONAL =>
pht(
'Personal rules notify you about events. You own them, but they can '.
- 'only affect you.'),
+ 'only affect you. Personal rules only trigger for objects you have '.
+ 'permission to see.'),
HeraldRuleTypeConfig::RULE_TYPE_GLOBAL =>
- phutil_implode_html(
- phutil_tag('br'),
- array_filter(
- array(
- pht(
- 'Global rules notify anyone about events. Global rules can '.
- 'bypass access control policies.'),
- $global_link,
- ))),
+ array(
+ pht(
+ 'Global rules notify anyone about events. Global rules can '.
+ 'bypass access control policies and act on any object.'),
+ $global_link,
+ ),
);
$radio = id(new AphrontFormRadioButtonControl())
Index: src/applications/herald/controller/HeraldRuleController.php
===================================================================
--- src/applications/herald/controller/HeraldRuleController.php
+++ src/applications/herald/controller/HeraldRuleController.php
@@ -47,14 +47,11 @@
$rule->setRuleType($rule_type);
$cancel_uri = $this->getApplicationURI();
-
- $this->requireApplicationCapability(
- PhabricatorApplicationHerald::CAN_CREATE_RULE);
}
if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) {
$this->requireApplicationCapability(
- PhabricatorApplicationHerald::CAN_CREATE_GLOBAL_RULE);
+ HeraldCapabilityManageGlobalRules::CAPABILITY);
}
$adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType());
@@ -509,10 +506,11 @@
return array(
'source' => array(
'email' => '/typeahead/common/mailable/',
- 'user' => '/typeahead/common/users/',
+ 'user' => '/typeahead/common/accounts/',
'repository' => '/typeahead/common/repositories/',
'package' => '/typeahead/common/packages/',
'project' => '/typeahead/common/projects/',
+ 'userorproject' => '/typeahead/common/accountsorprojects/',
),
'markup' => $template,
);
Index: src/applications/herald/controller/HeraldRuleListController.php
===================================================================
--- src/applications/herald/controller/HeraldRuleListController.php
+++ src/applications/herald/controller/HeraldRuleListController.php
@@ -54,18 +54,17 @@
$item->addIcon('world', pht('Global Rule'));
}
+ if ($rule->getIsDisabled()) {
+ $item->setDisabled(true);
+ $item->addIcon('disable-grey', pht('Disabled'));
+ }
+
$item->addAction(
id(new PHUIListItemView())
->setHref($this->getApplicationURI("history/{$id}/"))
->setIcon('transcript')
->setName(pht('Edit Log')));
- $item->addAction(
- id(new PHUIListItemView())
- ->setHref('/herald/delete/'.$rule->getID().'/')
- ->setIcon('delete')
- ->setWorkflow(true));
-
$content_type_name = idx($content_type_map, $rule->getContentType());
$item->addAttribute(pht('Affects: %s', $content_type_name));
Index: src/applications/herald/controller/HeraldRuleViewController.php
===================================================================
--- src/applications/herald/controller/HeraldRuleViewController.php
+++ src/applications/herald/controller/HeraldRuleViewController.php
@@ -22,10 +22,24 @@
}
$header = id(new PHUIHeaderView())
- ->setHeader($rule->getName());
+ ->setUser($viewer)
+ ->setHeader($rule->getName())
+ ->setPolicyObject($rule);
+
+ if ($rule->getIsDisabled()) {
+ $header->setStatus(
+ 'oh-open',
+ 'red',
+ pht('Disabled'));
+ } else {
+ $header->setStatus(
+ 'oh-open',
+ null,
+ pht('Active'));
+ }
$actions = $this->buildActionView($rule);
- $properties = $this->buildPropertyView($rule);
+ $properties = $this->buildPropertyView($rule, $actions);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
@@ -34,13 +48,15 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
+
+ $timeline = $this->buildTimeline($rule);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
+ $timeline,
),
array(
'title' => $rule->getName(),
@@ -70,17 +86,40 @@
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
+ if ($rule->getIsDisabled()) {
+ $disable_uri = "disable/{$id}/enable/";
+ $disable_icon = 'enable';
+ $disable_name = pht('Enable Rule');
+ } else {
+ $disable_uri = "disable/{$id}/disable/";
+ $disable_icon = 'disable';
+ $disable_name = pht('Disable Rule');
+ }
+
+ $view->addAction(
+ id(new PhabricatorActionView())
+ ->setName(pht('Disable Rule'))
+ ->setHref($this->getApplicationURI($disable_uri))
+ ->setIcon($disable_icon)
+ ->setName($disable_name)
+ ->setDisabled(!$can_edit)
+ ->setWorkflow(true));
+
return $view;
}
- private function buildPropertyView(HeraldRule $rule) {
+ private function buildPropertyView(
+ HeraldRule $rule,
+ PhabricatorActionListView $actions) {
+
$viewer = $this->getRequest()->getUser();
$this->loadHandles(HeraldAdapter::getHandlePHIDs($rule));
- $view = id(new PhabricatorPropertyListView())
+ $view = id(new PHUIPropertyListView())
->setUser($viewer)
- ->setObject($rule);
+ ->setObject($rule)
+ ->setActionList($actions);
$view->addProperty(
pht('Rule Type'),
@@ -115,4 +154,31 @@
return $view;
}
+ private function buildTimeline(HeraldRule $rule) {
+ $viewer = $this->getRequest()->getUser();
+
+ $xactions = id(new HeraldTransactionQuery())
+ ->setViewer($viewer)
+ ->withObjectPHIDs(array($rule->getPHID()))
+ ->needComments(true)
+ ->execute();
+
+ $engine = id(new PhabricatorMarkupEngine())
+ ->setViewer($viewer);
+ foreach ($xactions as $xaction) {
+ if ($xaction->getComment()) {
+ $engine->addObject(
+ $xaction->getComment(),
+ PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
+ }
+ }
+ $engine->process();
+
+ return id(new PhabricatorApplicationTransactionView())
+ ->setUser($viewer)
+ ->setObjectPHID($rule->getPHID())
+ ->setTransactions($xactions)
+ ->setMarkupEngine($engine);
+ }
+
}
Index: src/applications/herald/controller/HeraldTestConsoleController.php
===================================================================
--- src/applications/herald/controller/HeraldTestConsoleController.php
+++ src/applications/herald/controller/HeraldTestConsoleController.php
@@ -59,6 +59,7 @@
$rules = id(new HeraldRuleQuery())
->setViewer($user)
->withContentTypes(array($adapter->getAdapterContentType()))
+ ->withDisabled(false)
->needConditionsAndActions(true)
->needAppliedToPHIDs(array($object->getPHID()))
->needValidateAuthors(true)
Index: src/applications/herald/controller/HeraldTranscriptController.php
===================================================================
--- src/applications/herald/controller/HeraldTranscriptController.php
+++ src/applications/herald/controller/HeraldTranscriptController.php
@@ -25,10 +25,15 @@
}
public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
- $xscript = id(new HeraldTranscript())->load($this->id);
+ $xscript = id(new HeraldTranscriptQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->executeOne();
if (!$xscript) {
- throw new Exception('Uknown transcript!');
+ return new Aphront404Response();
}
require_celerity_resource('herald-test-css');
@@ -46,9 +51,19 @@
pht('Details of this transcript have been garbage collected.')));
$nav->appendChild($notice);
} else {
+ $map = HeraldAdapter::getEnabledAdapterMap($viewer);
+ $object_type = $object_xscript->getType();
+ if (empty($map[$object_type])) {
+ // TODO: We should filter these out in the Query, but we have to load
+ // the objectTranscript right now, which is potentially enormous. We
+ // should denormalize the object type, or move the data into a separate
+ // table, and then filter this earlier (and thus raise a better error).
+ // For now, just block access so we don't violate policies.
+ throw new Exception(
+ pht("This transcript has an invalid or inaccessible adapter."));
+ }
- $this->adapter = HeraldAdapter::getAdapterForContentType(
- $object_xscript->getType());
+ $this->adapter = HeraldAdapter::getAdapterForContentType($object_type);
$filter = $this->getFilterPHIDs();
$this->filterTranscript($xscript, $filter);
Index: src/applications/herald/controller/HeraldTranscriptListController.php
===================================================================
--- src/applications/herald/controller/HeraldTranscriptListController.php
+++ src/applications/herald/controller/HeraldTranscriptListController.php
@@ -7,61 +7,33 @@
$request = $this->getRequest();
$user = $request->getUser();
- // Get one page of data together with the pager.
- // Pull these objects manually since the serialized fields are gigantic.
- $transcript = new HeraldTranscript();
+ $pager = new AphrontCursorPagerView();
+ $pager->readFromRequest($request);
- $conn_r = $transcript->establishConnection('r');
- $phid = $request->getStr('phid');
- $where_clause = '';
- if ($phid) {
- $where_clause = qsprintf(
- $conn_r,
- 'WHERE objectPHID = %s',
- $phid);
- }
-
- $pager = new AphrontPagerView();
- $pager->setOffset($request->getInt('offset'));
- $pager->setURI($request->getRequestURI(), 'offset');
-
- $limit_clause = qsprintf(
- $conn_r,
- 'LIMIT %d, %d',
- $pager->getOffset(),
- $pager->getPageSize() + 1);
-
- $data = queryfx_all(
- $conn_r,
- 'SELECT id, objectPHID, time, duration, dryRun FROM %T
- %Q
- ORDER BY id DESC
- %Q',
- $transcript->getTableName(),
- $where_clause,
- $limit_clause);
-
- $data = $pager->sliceResults($data);
+ $transcripts = id(new HeraldTranscriptQuery())
+ ->setViewer($user)
+ ->needPartialRecords(true)
+ ->executeWithCursorPager($pager);
// Render the table.
$handles = array();
- if ($data) {
- $phids = ipull($data, 'objectPHID', 'objectPHID');
+ if ($transcripts) {
+ $phids = mpull($transcripts, 'getObjectPHID', 'getObjectPHID');
$handles = $this->loadViewerHandles($phids);
}
$rows = array();
- foreach ($data as $xscript) {
+ foreach ($transcripts as $xscript) {
$rows[] = array(
- phabricator_date($xscript['time'], $user),
- phabricator_time($xscript['time'], $user),
- $handles[$xscript['objectPHID']]->renderLink(),
- $xscript['dryRun'] ? 'Yes' : '',
- number_format((int)(1000 * $xscript['duration'])).' ms',
+ phabricator_date($xscript->getTime(), $user),
+ phabricator_time($xscript->getTime(), $user),
+ $handles[$xscript->getObjectPHID()]->renderLink(),
+ $xscript->getDryRun() ? pht('Yes') : '',
+ number_format((int)(1000 * $xscript->getDuration())).' ms',
phutil_tag(
'a',
array(
- 'href' => '/herald/transcript/'.$xscript['id'].'/',
+ 'href' => '/herald/transcript/'.$xscript->getID().'/',
'class' => 'button small grey',
),
pht('View Transcript')),
Index: src/applications/herald/editor/HeraldRuleEditor.php
===================================================================
--- /dev/null
+++ src/applications/herald/editor/HeraldRuleEditor.php
@@ -0,0 +1,54 @@
+<?php
+
+final class HeraldRuleEditor
+ extends PhabricatorApplicationTransactionEditor {
+
+ public function getTransactionTypes() {
+ $types = parent::getTransactionTypes();
+
+ $types[] = PhabricatorTransactions::TYPE_COMMENT;
+ $types[] = HeraldRuleTransaction::TYPE_DISABLE;
+
+ return $types;
+ }
+
+ protected function getCustomTransactionOldValue(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ switch ($xaction->getTransactionType()) {
+ case HeraldRuleTransaction::TYPE_DISABLE:
+ return (int)$object->getIsDisabled();
+ }
+
+ }
+
+ protected function getCustomTransactionNewValue(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ switch ($xaction->getTransactionType()) {
+ case HeraldRuleTransaction::TYPE_DISABLE:
+ return (int)$xaction->getNewValue();
+ }
+
+ }
+
+ protected function applyCustomInternalTransaction(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ switch ($xaction->getTransactionType()) {
+ case HeraldRuleTransaction::TYPE_DISABLE:
+ return $object->setIsDisabled($xaction->getNewValue());
+ }
+
+ }
+
+ protected function applyCustomExternalTransaction(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+ return;
+ }
+
+}
Index: src/applications/herald/engine/HeraldEngine.php
===================================================================
--- src/applications/herald/engine/HeraldEngine.php
+++ src/applications/herald/engine/HeraldEngine.php
@@ -27,6 +27,7 @@
public static function loadAndApplyRules(HeraldAdapter $adapter) {
$rules = id(new HeraldRuleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withDisabled(false)
->withContentTypes(array($adapter->getAdapterContentType()))
->needConditionsAndActions(true)
->needAppliedToPHIDs(array($adapter->getPHID()))
@@ -233,15 +234,23 @@
$local_version = id(new HeraldRule())->getConfigVersion();
if ($rule->getConfigVersion() > $local_version) {
- $reason = "Rule could not be processed, it was created with a newer ".
- "version of Herald.";
+ $reason = pht(
+ "Rule could not be processed, it was created with a newer version ".
+ "of Herald.");
$result = false;
} else if (!$conditions) {
- $reason = "Rule failed automatically because it has no conditions.";
+ $reason = pht(
+ "Rule failed automatically because it has no conditions.");
$result = false;
} else if (!$rule->hasValidAuthor()) {
- $reason = "Rule failed automatically because its owner is invalid ".
- "or disabled.";
+ $reason = pht(
+ "Rule failed automatically because its owner is invalid ".
+ "or disabled.");
+ $result = false;
+ } else if (!$this->canAuthorViewObject($rule, $object)) {
+ $reason = pht(
+ "Rule failed automatically because it is a personal rule and its ".
+ "owner can not see the object.");
$result = false;
} else {
foreach ($conditions as $condition) {
@@ -361,4 +370,32 @@
return $effects;
}
+ private function canAuthorViewObject(
+ HeraldRule $rule,
+ HeraldAdapter $adapter) {
+
+ // Authorship is irrelevant for global rules.
+ if ($rule->isGlobalRule()) {
+ return true;
+ }
+
+ // The author must be able to create rules for the adapter's content type.
+ // In particular, this means that the application must be installed and
+ // accessible to the user. For example, if a user writes a Differential
+ // rule and then loses access to Differential, this disables the rule.
+ $enabled = HeraldAdapter::getEnabledAdapterMap($rule->getAuthor());
+ if (empty($enabled[$adapter->getAdapterContentType()])) {
+ return false;
+ }
+
+ // Finally, the author must be able to see the object itself. You can't
+ // write a personal rule that CC's you on revisions you wouldn't otherwise
+ // be able to see, for example.
+ $object = $adapter->getObject();
+ return PhabricatorPolicyFilter::hasCapability(
+ $rule->getAuthor(),
+ $object,
+ PhabricatorPolicyCapability::CAN_VIEW);
+ }
+
}
Index: src/applications/herald/query/HeraldRuleQuery.php
===================================================================
--- src/applications/herald/query/HeraldRuleQuery.php
+++ src/applications/herald/query/HeraldRuleQuery.php
@@ -8,6 +8,7 @@
private $authorPHIDs;
private $ruleTypes;
private $contentTypes;
+ private $disabled;
private $needConditionsAndActions;
private $needAppliedToPHIDs;
@@ -43,6 +44,11 @@
return $this;
}
+ public function withDisabled($disabled) {
+ $this->disabled = $disabled;
+ return $this;
+ }
+
public function needConditionsAndActions($need) {
$this->needConditionsAndActions = $need;
return $this;
@@ -82,6 +88,7 @@
$types = HeraldAdapter::getEnabledAdapterMap($this->getViewer());
foreach ($rules as $key => $rule) {
if (empty($types[$rule->getContentType()])) {
+ $this->didRejectResult($rule);
unset($rules[$key]);
}
}
@@ -171,6 +178,13 @@
$this->contentTypes);
}
+ if ($this->disabled !== null) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'rule.isDisabled = %d',
+ (int)$this->disabled);
+ }
+
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
@@ -211,6 +225,7 @@
}
$rule->attachValidAuthor(true);
+ $rule->attachAuthor($users[$author_phid]);
}
}
Index: src/applications/herald/query/HeraldRuleSearchEngine.php
===================================================================
--- src/applications/herald/query/HeraldRuleSearchEngine.php
+++ src/applications/herald/query/HeraldRuleSearchEngine.php
@@ -12,6 +12,9 @@
$saved->setParameter('contentType', $request->getStr('contentType'));
$saved->setParameter('ruleType', $request->getStr('ruleType'));
+ $saved->setParameter(
+ 'disabled',
+ $this->readBoolFromRequest($request, 'disabled'));
return $saved;
}
@@ -36,6 +39,11 @@
$query->withRuleTypes(array($rule_type));
}
+ $disabled = $saved->getParameter('disabled');
+ if ($disabled !== null) {
+ $query->withDisabled($disabled);
+ }
+
return $query;
}
@@ -44,11 +52,10 @@
PhabricatorSavedQuery $saved_query) {
$phids = $saved_query->getParameter('authorPHIDs', array());
- $handles = id(new PhabricatorHandleQuery())
+ $author_handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireViewer())
->withPHIDs($phids)
->execute();
- $author_tokens = mpull($handles, 'getFullName', 'getPHID');
$content_type = $saved_query->getParameter('contentType');
$rule_type = $saved_query->getParameter('ruleType');
@@ -59,7 +66,7 @@
->setDatasource('/typeahead/common/users/')
->setName('authors')
->setLabel(pht('Authors'))
- ->setValue($author_tokens))
+ ->setValue($author_handles))
->appendChild(
id(new AphrontFormSelectControl())
->setName('contentType')
@@ -71,7 +78,18 @@
->setName('ruleType')
->setLabel(pht('Rule Type'))
->setValue($rule_type)
- ->setOptions($this->getRuleTypeOptions()));
+ ->setOptions($this->getRuleTypeOptions()))
+ ->appendChild(
+ id(new AphrontFormSelectControl())
+ ->setName('disabled')
+ ->setLabel(pht('Rule Status'))
+ ->setValue($this->getBoolFromQuery($saved_query, 'disabled'))
+ ->setOptions(
+ array(
+ '' => pht('Show Enabled and Disabled Rules'),
+ 'false' => pht('Show Only Enabled Rules'),
+ 'true' => pht('Show Only Disabled Rules'),
+ )));
}
protected function getURI($path) {
@@ -85,6 +103,7 @@
$names['authored'] = pht('Authored');
}
+ $names['active'] = pht('Active');
$names['all'] = pht('All');
return $names;
@@ -95,13 +114,17 @@
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
+ $viewer_phid = $this->requireViewer()->getPHID();
+
switch ($query_key) {
case 'all':
return $query;
+ case 'active':
+ return $query->setParameter('disabled', false);
case 'authored':
- return $query->setParameter(
- 'authorPHIDs',
- array($this->requireViewer()->getPHID()));
+ return $query
+ ->setParameter('authorPHIDs', array($viewer_phid))
+ ->setParameter('disabled', false);
}
return parent::buildSavedQueryFromBuiltin($query_key);
Index: src/applications/herald/query/HeraldTransactionQuery.php
===================================================================
--- /dev/null
+++ src/applications/herald/query/HeraldTransactionQuery.php
@@ -0,0 +1,10 @@
+<?php
+
+final class HeraldTransactionQuery
+ extends PhabricatorApplicationTransactionQuery {
+
+ public function getTemplateApplicationTransaction() {
+ return new HeraldRuleTransaction();
+ }
+
+}
Index: src/applications/herald/query/HeraldTranscriptQuery.php
===================================================================
--- /dev/null
+++ src/applications/herald/query/HeraldTranscriptQuery.php
@@ -0,0 +1,98 @@
+<?php
+
+final class HeraldTranscriptQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $needPartialRecords;
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function needPartialRecords($need_partial) {
+ $this->needPartialRecords = $need_partial;
+ return $this;
+ }
+
+ public function loadPage() {
+ $transcript = new HeraldTranscript();
+ $conn_r = $transcript->establishConnection('r');
+
+ // NOTE: Transcripts include a potentially enormous amount of serialized
+ // data, so we're loading only some of the fields here if the caller asked
+ // for partial records.
+
+ if ($this->needPartialRecords) {
+ $fields = implode(
+ ', ',
+ array(
+ 'id',
+ 'phid',
+ 'objectPHID',
+ 'time',
+ 'duration',
+ 'dryRun',
+ 'host',
+ ));
+ } else {
+ $fields = '*';
+ }
+
+ $rows = queryfx_all(
+ $conn_r,
+ 'SELECT %Q FROM %T t %Q %Q %Q',
+ $fields,
+ $transcript->getTableName(),
+ $this->buildWhereClause($conn_r),
+ $this->buildOrderClause($conn_r),
+ $this->buildLimitClause($conn_r));
+
+ $transcripts = $transcript->loadAllFromArray($rows);
+
+ if ($this->needPartialRecords) {
+ // Make sure nothing tries to write these; they aren't complete.
+ foreach ($transcripts as $transcript) {
+ $transcript->makeEphemeral();
+ }
+ }
+
+ return $transcripts;
+ }
+
+ public function willFilterPage(array $transcripts) {
+ $phids = mpull($transcripts, 'getObjectPHID');
+
+ $objects = id(new PhabricatorObjectQuery())
+ ->setViewer($this->getViewer())
+ ->withPHIDs($phids)
+ ->execute();
+
+ foreach ($transcripts as $key => $transcript) {
+ if (empty($objects[$transcript->getObjectPHID()])) {
+ $this->didRejectResult($transcript);
+ unset($transcripts[$key]);
+ }
+ }
+
+ return $transcripts;
+ }
+
+ public function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+ $where = array();
+
+ if ($this->ids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ $where[] = $this->buildPagingClause($conn_r);
+
+ return $this->formatWhereClause($where);
+ }
+
+
+}
Index: src/applications/herald/storage/HeraldRule.php
===================================================================
--- src/applications/herald/storage/HeraldRule.php
+++ src/applications/herald/storage/HeraldRule.php
@@ -12,11 +12,13 @@
protected $mustMatchAll;
protected $repetitionPolicy;
protected $ruleType;
+ protected $isDisabled = 0;
- protected $configVersion = 12;
+ protected $configVersion = 14;
private $ruleApplied = self::ATTACHABLE; // phids for which this rule has been applied
private $validAuthor = self::ATTACHABLE;
+ private $author = self::ATTACHABLE;
private $conditions;
private $actions;
@@ -167,6 +169,15 @@
return $this;
}
+ public function getAuthor() {
+ return $this->assertAttached($this->author);
+ }
+
+ public function attachAuthor(PhabricatorUser $user) {
+ $this->author = $user;
+ return $this;
+ }
+
public function isGlobalRule() {
return ($this->getRuleType() === HeraldRuleTypeConfig::RULE_TYPE_GLOBAL);
}
@@ -188,7 +199,15 @@
public function getPolicy($capability) {
if ($this->isGlobalRule()) {
- return PhabricatorPolicies::POLICY_USER;
+ switch ($capability) {
+ case PhabricatorPolicyCapability::CAN_VIEW:
+ return PhabricatorPolicies::POLICY_USER;
+ case PhabricatorPolicyCapability::CAN_EDIT:
+ $app = 'PhabricatorApplicationHerald';
+ $herald = PhabricatorApplication::getByClass($app);
+ $global = HeraldCapabilityManageGlobalRules::CAPABILITY;
+ return $herald->getPolicy($global);
+ }
} else {
return PhabricatorPolicies::POLICY_NOONE;
}
@@ -203,9 +222,11 @@
}
public function describeAutomaticCapability($capability) {
- // TODO: (T603) Sort this out.
+ if ($this->isPersonalRule()) {
+ return pht("A personal rule's owner can always view and edit it.");
+ }
+
return null;
}
-
}
Index: src/applications/herald/storage/HeraldRuleTransaction.php
===================================================================
--- src/applications/herald/storage/HeraldRuleTransaction.php
+++ src/applications/herald/storage/HeraldRuleTransaction.php
@@ -4,6 +4,7 @@
extends PhabricatorApplicationTransaction {
const TYPE_EDIT = 'herald:edit';
+ const TYPE_DISABLE = 'herald:disable';
public function getApplicationName() {
return 'herald';
@@ -17,5 +18,75 @@
return new HeraldRuleTransactionComment();
}
-}
+ public function getColor() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ switch ($this->getTransactionType()) {
+ case self::TYPE_DISABLE:
+ if ($new) {
+ return 'red';
+ } else {
+ return 'green';
+ }
+ }
+
+ return parent::getColor();
+ }
+
+ public function getActionName() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ switch ($this->getTransactionType()) {
+ case self::TYPE_DISABLE:
+ if ($new) {
+ return pht('Disabled');
+ } else {
+ return pht('Enabled');
+ }
+ }
+
+ return parent::getActionName();
+ }
+
+ public function getIcon() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ switch ($this->getTransactionType()) {
+ case self::TYPE_DISABLE:
+ if ($new) {
+ return 'disable';
+ } else {
+ return 'enable';
+ }
+ }
+ return parent::getIcon();
+ }
+
+
+ public function getTitle() {
+ $author_phid = $this->getAuthorPHID();
+
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ switch ($this->getTransactionType()) {
+ case self::TYPE_DISABLE:
+ if ($new) {
+ return pht(
+ '%s disabled this rule.',
+ $this->renderHandleLink($author_phid));
+ } else {
+ return pht(
+ '%s enabled this rule.',
+ $this->renderHandleLink($author_phid));
+ }
+ }
+
+ return parent::getTitle();
+ }
+
+}
Index: src/applications/herald/storage/transcript/HeraldTranscript.php
===================================================================
--- src/applications/herald/storage/transcript/HeraldTranscript.php
+++ src/applications/herald/storage/transcript/HeraldTranscript.php
@@ -1,9 +1,7 @@
<?php
-final class HeraldTranscript extends HeraldDAO {
-
- protected $id;
- protected $phid;
+final class HeraldTranscript extends HeraldDAO
+ implements PhabricatorPolicyInterface {
protected $objectTranscript;
protected $ruleTranscripts = array();
@@ -166,4 +164,30 @@
return PhabricatorPHID::generateNewPHID('HLXS');
}
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ );
+ }
+
+ public function getPolicy($capability) {
+ switch ($capability) {
+ case PhabricatorPolicyCapability::CAN_VIEW:
+ return PhabricatorPolicies::POLICY_USER;
+ }
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return false;
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return pht(
+ 'To view a transcript, you must be able to view the object the '.
+ 'transcript is about.');
+ }
+
+
}
Index: src/applications/legalpad/controller/LegalpadDocumentViewController.php
===================================================================
--- src/applications/legalpad/controller/LegalpadDocumentViewController.php
+++ src/applications/legalpad/controller/LegalpadDocumentViewController.php
@@ -67,7 +67,7 @@
->setPolicyObject($document);
$actions = $this->buildActionView($document);
- $properties = $this->buildPropertyView($document, $engine);
+ $properties = $this->buildPropertyView($document, $engine, $actions);
$comment_form_id = celerity_generate_unique_node_id();
@@ -88,8 +88,7 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
$content = array(
$crumbs,
@@ -149,13 +148,15 @@
private function buildPropertyView(
LegalpadDocument $document,
- PhabricatorMarkupEngine $engine) {
+ PhabricatorMarkupEngine $engine,
+ PhabricatorActionListView $actions) {
$user = $this->getRequest()->getUser();
- $properties = id(new PhabricatorPropertyListView())
+ $properties = id(new PHUIPropertyListView())
->setUser($user)
- ->setObject($document);
+ ->setObject($document)
+ ->setActionList($actions);
$properties->addProperty(
pht('Last Updated'),
Index: src/applications/legalpad/mail/LegalpadReplyHandler.php
===================================================================
--- src/applications/legalpad/mail/LegalpadReplyHandler.php
+++ src/applications/legalpad/mail/LegalpadReplyHandler.php
@@ -37,8 +37,8 @@
$actor = $this->getActor();
$document = $this->getMailReceiver();
- $body = $mail->getCleanTextBody();
- $body = trim($body);
+ $body_data = $mail->parseBody();
+ $body = $body_data['body'];
$body = $this->enhanceBodyWithAttachments($body, $mail->getAttachments());
$content_source = PhabricatorContentSource::newForSource(
@@ -47,19 +47,9 @@
'id' => $mail->getID(),
));
- $lines = explode("\n", trim($body));
- $first_line = head($lines);
$xactions = array();
- $command = null;
- $matches = null;
- if (preg_match('/^!(\w+)/', $first_line, $matches)) {
- $lines = array_slice($lines, 1);
- $body = implode("\n", $lines);
- $body = trim($body);
-
- $command = $matches[1];
- }
+ $command = $body_data['command'];
switch ($command) {
case 'unsubscribe':
Index: src/applications/legalpad/query/LegalpadDocumentSearchEngine.php
===================================================================
--- src/applications/legalpad/query/LegalpadDocumentSearchEngine.php
+++ src/applications/legalpad/query/LegalpadDocumentSearchEngine.php
@@ -53,7 +53,6 @@
->setViewer($this->requireViewer())
->withPHIDs($phids)
->execute();
- $tokens = mpull($handles, 'getFullName', 'getPHID');
$form
->appendChild(
@@ -61,13 +60,13 @@
->setDatasource('/typeahead/common/users/')
->setName('creators')
->setLabel(pht('Creators'))
- ->setValue(array_select_keys($tokens, $creator_phids)))
+ ->setValue(array_select_keys($handles, $creator_phids)))
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/users/')
->setName('contributors')
->setLabel(pht('Contributors'))
- ->setValue(array_select_keys($tokens, $contributor_phids)));
+ ->setValue(array_select_keys($handles, $contributor_phids)));
$this->buildDateRange(
$form,
Index: src/applications/legalpad/storage/LegalpadDocument.php
===================================================================
--- src/applications/legalpad/storage/LegalpadDocument.php
+++ src/applications/legalpad/storage/LegalpadDocument.php
@@ -9,7 +9,6 @@
PhabricatorSubscribableInterface,
PhabricatorApplicationTransactionInterface {
- protected $phid;
protected $title;
protected $contributorCount;
protected $recentContributorPHIDs = array();
Index: src/applications/macro/application/PhabricatorApplicationMacro.php
===================================================================
--- src/applications/macro/application/PhabricatorApplicationMacro.php
+++ src/applications/macro/application/PhabricatorApplicationMacro.php
@@ -42,4 +42,12 @@
);
}
+ protected function getCustomCapabilities() {
+ return array(
+ PhabricatorMacroCapabilityManage::CAPABILITY => array(
+ 'caption' => pht('Allows creating and editing macros.')
+ ),
+ );
+ }
+
}
Index: src/applications/macro/capability/PhabricatorMacroCapabilityManage.php
===================================================================
--- /dev/null
+++ src/applications/macro/capability/PhabricatorMacroCapabilityManage.php
@@ -0,0 +1,20 @@
+<?php
+
+final class PhabricatorMacroCapabilityManage
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'macro.manage';
+
+ public function getCapabilityKey() {
+ return self::CAPABILITY;
+ }
+
+ public function getCapabilityName() {
+ return pht('Can Manage Macros');
+ }
+
+ public function describeCapabilityRejection() {
+ return pht('You do not have permission to manage image macros.');
+ }
+
+}
Index: src/applications/macro/controller/PhabricatorMacroAudioController.php
===================================================================
--- src/applications/macro/controller/PhabricatorMacroAudioController.php
+++ src/applications/macro/controller/PhabricatorMacroAudioController.php
@@ -10,6 +10,10 @@
}
public function processRequest() {
+
+ $this->requireApplicationCapability(
+ PhabricatorMacroCapabilityManage::CAPABILITY);
+
$request = $this->getRequest();
$viewer = $request->getUser();
Index: src/applications/macro/controller/PhabricatorMacroController.php
===================================================================
--- src/applications/macro/controller/PhabricatorMacroController.php
+++ src/applications/macro/controller/PhabricatorMacroController.php
@@ -28,11 +28,16 @@
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
+ $can_manage = $this->hasApplicationCapability(
+ PhabricatorMacroCapabilityManage::CAPABILITY);
+
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('Create Macro'))
->setHref($this->getApplicationURI('/create/'))
- ->setIcon('create'));
+ ->setIcon('create')
+ ->setDisabled(!$can_manage)
+ ->setWorkflow(!$can_manage));
return $crumbs;
}
Index: src/applications/macro/controller/PhabricatorMacroDisableController.php
===================================================================
--- src/applications/macro/controller/PhabricatorMacroDisableController.php
+++ src/applications/macro/controller/PhabricatorMacroDisableController.php
@@ -10,6 +10,10 @@
}
public function processRequest() {
+
+ $this->requireApplicationCapability(
+ PhabricatorMacroCapabilityManage::CAPABILITY);
+
$request = $this->getRequest();
$user = $request->getUser();
Index: src/applications/macro/controller/PhabricatorMacroEditController.php
===================================================================
--- src/applications/macro/controller/PhabricatorMacroEditController.php
+++ src/applications/macro/controller/PhabricatorMacroEditController.php
@@ -11,17 +11,15 @@
public function processRequest() {
+ $this->requireApplicationCapability(
+ PhabricatorMacroCapabilityManage::CAPABILITY);
+
$request = $this->getRequest();
$user = $request->getUser();
if ($this->id) {
$macro = id(new PhabricatorMacroQuery())
->setViewer($user)
- ->requireCapabilities(
- array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
->withIDs(array($this->id))
->executeOne();
if (!$macro) {
Index: src/applications/macro/controller/PhabricatorMacroViewController.php
===================================================================
--- src/applications/macro/controller/PhabricatorMacroViewController.php
+++ src/applications/macro/controller/PhabricatorMacroViewController.php
@@ -35,7 +35,17 @@
->setHref($this->getApplicationURI('/view/'.$macro->getID().'/'))
->setName($title_short));
- $properties = $this->buildPropertyView($macro, $file);
+ $properties = $this->buildPropertyView($macro, $actions);
+ if ($file) {
+ $file_view = new PHUIPropertyListView();
+ $file_view->addImageContent(
+ phutil_tag(
+ 'img',
+ array(
+ 'src' => $file->getViewURI(),
+ 'class' => 'phabricator-image-macro-hero',
+ )));
+ }
$xactions = id(new PhabricatorMacroTransactionQuery())
->setViewer($request->getUser())
@@ -60,6 +70,8 @@
->setMarkupEngine($engine);
$header = id(new PHUIHeaderView())
+ ->setUser($user)
+ ->setPolicyObject($macro)
->setHeader($title_long);
if ($macro->getIsDisabled()) {
@@ -93,8 +105,11 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
+
+ if ($file_view) {
+ $object_box->addPropertyList($file_view);
+ }
$comment_box = id(new PHUIObjectBoxView())
->setFlush(true)
@@ -115,6 +130,10 @@
}
private function buildActionView(PhabricatorFileImageMacro $macro) {
+
+ $can_manage = $this->hasApplicationCapability(
+ PhabricatorMacroCapabilityManage::CAPABILITY);
+
$request = $this->getRequest();
$view = id(new PhabricatorActionListView())
->setUser($request->getUser())
@@ -124,12 +143,16 @@
id(new PhabricatorActionView())
->setName(pht('Edit Macro'))
->setHref($this->getApplicationURI('/edit/'.$macro->getID().'/'))
+ ->setDisabled(!$can_manage)
+ ->setWorkflow(!$can_manage)
->setIcon('edit'));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Audio'))
->setHref($this->getApplicationURI('/audio/'.$macro->getID().'/'))
+ ->setDisabled(!$can_manage)
+ ->setWorkflow(!$can_manage)
->setIcon('herald'));
if ($macro->getIsDisabled()) {
@@ -138,6 +161,7 @@
->setName(pht('Restore Macro'))
->setHref($this->getApplicationURI('/disable/'.$macro->getID().'/'))
->setWorkflow(true)
+ ->setDisabled(!$can_manage)
->setIcon('undo'));
} else {
$view->addAction(
@@ -145,6 +169,7 @@
->setName(pht('Disable Macro'))
->setHref($this->getApplicationURI('/disable/'.$macro->getID().'/'))
->setWorkflow(true)
+ ->setDisabled(!$can_manage)
->setIcon('delete'));
}
@@ -153,11 +178,12 @@
private function buildPropertyView(
PhabricatorFileImageMacro $macro,
- PhabricatorFile $file = null) {
+ PhabricatorActionListView $actions) {
- $view = id(new PhabricatorPropertyListView())
+ $view = id(new PHUIPropertyListView())
->setUser($this->getRequest()->getUser())
- ->setObject($macro);
+ ->setObject($macro)
+ ->setActionList($actions);
switch ($macro->getAudioBehavior()) {
case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE:
@@ -171,25 +197,13 @@
$audio_phid = $macro->getAudioPHID();
if ($audio_phid) {
$this->loadHandles(array($audio_phid));
-
$view->addProperty(
pht('Audio'),
$this->getHandle($audio_phid)->renderLink());
}
-
$view->invokeWillRenderEvent();
- if ($file) {
- $view->addImageContent(
- phutil_tag(
- 'img',
- array(
- 'src' => $file->getViewURI(),
- 'class' => 'phabricator-image-macro-hero',
- )));
- }
-
return $view;
}
Index: src/applications/macro/editor/PhabricatorMacroEditor.php
===================================================================
--- src/applications/macro/editor/PhabricatorMacroEditor.php
+++ src/applications/macro/editor/PhabricatorMacroEditor.php
@@ -77,6 +77,18 @@
return;
}
+ protected function extractFilePHIDsFromCustomTransaction(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ switch ($xaction->getTransactionType()) {
+ case PhabricatorMacroTransactionType::TYPE_FILE:
+ return array($xaction->getNewValue());
+ }
+
+ return array();
+ }
+
protected function mergeTransactions(
PhabricatorApplicationTransaction $u,
PhabricatorApplicationTransaction $v) {
Index: src/applications/macro/query/PhabricatorMacroQuery.php
===================================================================
--- src/applications/macro/query/PhabricatorMacroQuery.php
+++ src/applications/macro/query/PhabricatorMacroQuery.php
@@ -191,10 +191,11 @@
return $this->formatWhereClause($where);
}
- protected function willFilterPage(array $macros) {
+ protected function didFilterPage(array $macros) {
$file_phids = mpull($macros, 'getFilePHID');
$files = id(new PhabricatorFileQuery())
->setViewer($this->getViewer())
+ ->setParentQuery($this)
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
Index: src/applications/macro/query/PhabricatorMacroSearchEngine.php
===================================================================
--- src/applications/macro/query/PhabricatorMacroSearchEngine.php
+++ src/applications/macro/query/PhabricatorMacroSearchEngine.php
@@ -66,11 +66,10 @@
PhabricatorSavedQuery $saved_query) {
$phids = $saved_query->getParameter('authorPHIDs', array());
- $handles = id(new PhabricatorHandleQuery())
+ $author_handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireViewer())
->withPHIDs($phids)
->execute();
- $author_tokens = mpull($handles, 'getFullName', 'getPHID');
$status = $saved_query->getParameter('status');
$names = implode(', ', $saved_query->getParameter('names', array()));
@@ -89,7 +88,7 @@
->setDatasource('/typeahead/common/users/')
->setName('authors')
->setLabel(pht('Authors'))
- ->setValue($author_tokens))
+ ->setValue($author_handles))
->appendChild(
id(new AphrontFormTextControl())
->setName('nameLike')
Index: src/applications/macro/storage/PhabricatorFileImageMacro.php
===================================================================
--- src/applications/macro/storage/PhabricatorFileImageMacro.php
+++ src/applications/macro/storage/PhabricatorFileImageMacro.php
@@ -8,7 +8,6 @@
protected $authorPHID;
protected $filePHID;
- protected $phid;
protected $name;
protected $isDisabled = 0;
protected $audioPHID;
@@ -65,12 +64,11 @@
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
- return PhabricatorPolicies::POLICY_USER;
+ return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
Index: src/applications/mailinglists/storage/PhabricatorMetaMTAMailingList.php
===================================================================
--- src/applications/mailinglists/storage/PhabricatorMetaMTAMailingList.php
+++ src/applications/mailinglists/storage/PhabricatorMetaMTAMailingList.php
@@ -4,7 +4,6 @@
implements PhabricatorPolicyInterface {
protected $name;
- protected $phid;
protected $email;
protected $uri;
Index: src/applications/maniphest/application/PhabricatorApplicationManiphest.php
===================================================================
--- src/applications/maniphest/application/PhabricatorApplicationManiphest.php
+++ src/applications/maniphest/application/PhabricatorApplicationManiphest.php
@@ -90,5 +90,18 @@
return $status;
}
+ protected function getCustomCapabilities() {
+ return array(
+ ManiphestCapabilityDefaultView::CAPABILITY => array(
+ 'caption' => pht(
+ 'Default view policy for newly created tasks.'),
+ ),
+ ManiphestCapabilityDefaultEdit::CAPABILITY => array(
+ 'caption' => pht(
+ 'Default edit policy for newly created tasks.'),
+ ),
+ );
+ }
+
}
Index: src/applications/maniphest/capability/ManiphestCapabilityDefaultEdit.php
===================================================================
--- /dev/null
+++ src/applications/maniphest/capability/ManiphestCapabilityDefaultEdit.php
@@ -0,0 +1,16 @@
+<?php
+
+final class ManiphestCapabilityDefaultEdit
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'maniphest.default.edit';
+
+ public function getCapabilityKey() {
+ return self::CAPABILITY;
+ }
+
+ public function getCapabilityName() {
+ return pht('Default Edit Policy');
+ }
+
+}
Index: src/applications/maniphest/capability/ManiphestCapabilityDefaultView.php
===================================================================
--- /dev/null
+++ src/applications/maniphest/capability/ManiphestCapabilityDefaultView.php
@@ -0,0 +1,20 @@
+<?php
+
+final class ManiphestCapabilityDefaultView
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'maniphest.default.view';
+
+ public function getCapabilityKey() {
+ return self::CAPABILITY;
+ }
+
+ public function getCapabilityName() {
+ return pht('Default View Policy');
+ }
+
+ public function shouldAllowPublicPolicySetting() {
+ return true;
+ }
+
+}
Index: src/applications/maniphest/conduit/ConduitAPI_maniphest_createtask_Method.php
===================================================================
--- src/applications/maniphest/conduit/ConduitAPI_maniphest_createtask_Method.php
+++ src/applications/maniphest/conduit/ConduitAPI_maniphest_createtask_Method.php
@@ -25,9 +25,7 @@
}
protected function execute(ConduitAPIRequest $request) {
- $task = new ManiphestTask();
- $task->setPriority(ManiphestTaskPriority::getDefaultPriority());
- $task->setAuthorPHID($request->getUser()->getPHID());
+ $task = ManiphestTask::initializeNewTask($request->getUser());
$this->applyRequest($task, $request, $is_new = true);
Index: src/applications/maniphest/controller/ManiphestReportController.php
===================================================================
--- src/applications/maniphest/controller/ManiphestReportController.php
+++ src/applications/maniphest/controller/ManiphestReportController.php
@@ -265,9 +265,7 @@
$tokens = array();
if ($handle) {
- $tokens = array(
- $handle->getPHID() => $handle->getFullName(),
- );
+ $tokens = array($handle);
}
$filter = $this->renderReportFilters($tokens, $has_window = false);
@@ -635,9 +633,7 @@
$tokens = array();
if ($project_handle) {
- $tokens = array(
- $project_handle->getPHID() => $project_handle->getFullName(),
- );
+ $tokens = array($project_handle);
}
$filter = $this->renderReportFilters($tokens, $has_window = true);
Index: src/applications/maniphest/controller/ManiphestTaskDetailController.php
===================================================================
--- src/applications/maniphest/controller/ManiphestTaskDetailController.php
+++ src/applications/maniphest/controller/ManiphestTaskDetailController.php
@@ -352,7 +352,9 @@
->setActionList($actions);
$header = $this->buildHeaderView($task);
- $properties = $this->buildPropertyView($task, $field_list, $edges, $engine);
+ $properties = $this->buildPropertyView(
+ $task, $field_list, $edges, $actions);
+ $description = $this->buildDescriptionView($task, $engine);
if (!$user->isLoggedIn()) {
// TODO: Eventually, everything should run through this. For now, we're
@@ -365,8 +367,11 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
+
+ if ($description) {
+ $object_box->addPropertyList($description);
+ }
$comment_box = id(new PHUIObjectBoxView())
->setFlush(true)
@@ -457,7 +462,7 @@
->setWorkflow(true)
->setIcon('merge')
->setDisabled(!$can_edit)
- ->setWorkflow(!$can_edit));
+ ->setWorkflow(true));
$view->addAction(
id(new PhabricatorActionView())
@@ -503,13 +508,14 @@
ManiphestTask $task,
PhabricatorCustomFieldList $field_list,
array $edges,
- PhabricatorMarkupEngine $engine) {
+ PhabricatorActionListView $actions) {
$viewer = $this->getRequest()->getUser();
- $view = id(new PhabricatorPropertyListView())
+ $view = id(new PHUIPropertyListView())
->setUser($viewer)
- ->setObject($task);
+ ->setObject($task)
+ ->setActionList($actions);
$view->addProperty(
pht('Assigned To'),
@@ -633,9 +639,18 @@
$view->invokeWillRenderEvent();
+ return $view;
+ }
+
+ private function buildDescriptionView(
+ ManiphestTask $task,
+ PhabricatorMarkupEngine $engine) {
+
+ $section = null;
if (strlen($task->getDescription())) {
- $view->addSectionHeader(pht('Description'));
- $view->addTextContent(
+ $section = new PHUIPropertyListView();
+ $section->addSectionHeader(pht('Description'));
+ $section->addTextContent(
phutil_tag(
'div',
array(
@@ -644,7 +659,7 @@
$engine->getOutput($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION)));
}
- return $view;
+ return $section;
}
}
Index: src/applications/maniphest/controller/ManiphestTaskEditController.php
===================================================================
--- src/applications/maniphest/controller/ManiphestTaskEditController.php
+++ src/applications/maniphest/controller/ManiphestTaskEditController.php
@@ -34,9 +34,7 @@
return new Aphront404Response();
}
} else {
- $task = new ManiphestTask();
- $task->setPriority(ManiphestTaskPriority::getDefaultPriority());
- $task->setAuthorPHID($user->getPHID());
+ $task = ManiphestTask::initializeNewTask($user);
// These allow task creation with defaults.
if (!$request->isFormPost()) {
@@ -343,8 +341,6 @@
$handles = $this->loadViewerHandles($phids);
- $tvalues = mpull($handles, 'getFullName', 'getPHID');
-
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
@@ -355,21 +351,19 @@
$priority_map = ManiphestTaskPriority::getTaskPriorityMap();
if ($task->getOwnerPHID()) {
- $assigned_value = array(
- $task->getOwnerPHID() => $handles[$task->getOwnerPHID()]->getFullName(),
- );
+ $assigned_value = array($handles[$task->getOwnerPHID()]);
} else {
$assigned_value = array();
}
if ($task->getCCPHIDs()) {
- $cc_value = array_select_keys($tvalues, $task->getCCPHIDs());
+ $cc_value = array_select_keys($handles, $task->getCCPHIDs());
} else {
$cc_value = array();
}
if ($task->getProjectPHIDs()) {
- $projects_value = array_select_keys($tvalues, $task->getProjectPHIDs());
+ $projects_value = array_select_keys($handles, $task->getProjectPHIDs());
} else {
$projects_value = array();
}
Index: src/applications/maniphest/editor/ManiphestTransactionEditorPro.php
===================================================================
--- src/applications/maniphest/editor/ManiphestTransactionEditorPro.php
+++ src/applications/maniphest/editor/ManiphestTransactionEditorPro.php
@@ -28,6 +28,9 @@
switch ($xaction->getTransactionType()) {
case ManiphestTransaction::TYPE_PRIORITY:
+ if ($this->getIsNewObject()) {
+ return null;
+ }
return (int)$object->getPriority();
case ManiphestTransaction::TYPE_STATUS:
if ($this->getIsNewObject()) {
Index: src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php
===================================================================
--- src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php
+++ src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php
@@ -7,9 +7,8 @@
$authorPHID = $this->loadPhabrictorUserPHID();
$author = id(new PhabricatorUser())
->loadOneWhere('phid = %s', $authorPHID);
- $task = id(new ManiphestTask())
+ $task = ManiphestTask::initializeNewTask($author)
->setSubPriority($this->generateTaskSubPriority())
- ->setAuthorPHID($authorPHID)
->setTitle($this->generateTitle())
->setStatus(ManiphestTaskStatus::STATUS_OPEN);
@@ -49,7 +48,7 @@
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($task, $transactions);
- return $task->save();
+ return $task;
}
public function getCCPHIDs() {
Index: src/applications/maniphest/mail/ManiphestCreateMailReceiver.php
===================================================================
--- src/applications/maniphest/mail/ManiphestCreateMailReceiver.php
+++ src/applications/maniphest/mail/ManiphestCreateMailReceiver.php
@@ -60,11 +60,8 @@
PhabricatorMetaMTAReceivedMail $mail,
PhabricatorUser $sender) {
- $task = new ManiphestTask();
-
- $task->setAuthorPHID($sender->getPHID());
+ $task = ManiphestTask::initializeNewTask($sender);
$task->setOriginalEmailSource($mail->getHeader('From'));
- $task->setPriority(ManiphestTaskPriority::getDefaultPriority());
$editor = new ManiphestTransactionEditor();
$editor->setActor($sender);
Index: src/applications/maniphest/mail/ManiphestReplyHandler.php
===================================================================
--- src/applications/maniphest/mail/ManiphestReplyHandler.php
+++ src/applications/maniphest/mail/ManiphestReplyHandler.php
@@ -27,8 +27,8 @@
public function getReplyHandlerInstructions() {
if ($this->supportsReplies()) {
- return "Reply to comment or attach files, or !close, !claim, or ".
- "!unsubscribe.";
+ return "Reply to comment or attach files, or !close, !claim, ".
+ "!unsubscribe or !assign <username>.";
} else {
return null;
}
@@ -45,8 +45,8 @@
$user = $this->getActor();
- $body = $mail->getCleanTextBody();
- $body = trim($body);
+ $body_data = $mail->parseBody();
+ $body = $body_data['body'];
$body = $this->enhanceBodyWithAttachments($body, $mail->getAttachments());
$xactions = array();
@@ -73,18 +73,9 @@
$task->setPriority(ManiphestTaskPriority::getDefaultPriority());
} else {
- $lines = explode("\n", trim($body));
- $first_line = head($lines);
- $command = null;
- $matches = null;
- if (preg_match('/^!(\w+)/', $first_line, $matches)) {
- $lines = array_slice($lines, 1);
- $body = implode("\n", $lines);
- $body = trim($body);
-
- $command = $matches[1];
- }
+ $command = $body_data['command'];
+ $command_value = $body_data['command_value'];
$ttype = PhabricatorTransactions::TYPE_COMMENT;
$new_value = null;
@@ -97,6 +88,23 @@
$ttype = ManiphestTransaction::TYPE_OWNER;
$new_value = $user->getPHID();
break;
+ case 'assign':
+ $ttype = ManiphestTransaction::TYPE_OWNER;
+ if ($command_value) {
+ $assign_users = id(new PhabricatorPeopleQuery())
+ ->setViewer($user)
+ ->withUsernames(array($command_value))
+ ->execute();
+ if ($assign_users) {
+ $assign_user = head($assign_users);
+ $new_value = $assign_user->getPHID();
+ }
+ }
+ // assign to the user by default
+ if (!$new_value) {
+ $new_value = $user->getPHID();
+ }
+ break;
case 'unsubscribe':
$is_unsub = true;
$ttype = ManiphestTransaction::TYPE_CCS;
Index: src/applications/maniphest/storage/ManiphestTask.php
===================================================================
--- src/applications/maniphest/storage/ManiphestTask.php
+++ src/applications/maniphest/storage/ManiphestTask.php
@@ -1,8 +1,5 @@
<?php
-/**
- * @group maniphest
- */
final class ManiphestTask extends ManiphestDAO
implements
PhabricatorMarkupInterface,
@@ -13,7 +10,6 @@
const MARKUP_FIELD_DESCRIPTION = 'markup:desc';
- protected $phid;
protected $authorPHID;
protected $ownerPHID;
protected $ccPHIDs = array();
@@ -23,7 +19,7 @@
protected $subpriority = 0;
protected $title = '';
- protected $originalTitle;
+ protected $originalTitle = '';
protected $description = '';
protected $originalEmailSource;
protected $mailKey;
@@ -40,6 +36,22 @@
private $groupByProjectPHID = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;
+ public static function initializeNewTask(PhabricatorUser $actor) {
+ $app = id(new PhabricatorApplicationQuery())
+ ->setViewer($actor)
+ ->withClasses(array('PhabricatorApplicationManiphest'))
+ ->executeOne();
+
+ $view_policy = $app->getPolicy(ManiphestCapabilityDefaultView::CAPABILITY);
+ $edit_policy = $app->getPolicy(ManiphestCapabilityDefaultEdit::CAPABILITY);
+
+ return id(new ManiphestTask())
+ ->setPriority(ManiphestTaskPriority::getDefaultPriority())
+ ->setAuthorPHID($actor->getPHID())
+ ->setViewPolicy($view_policy)
+ ->setEditPolicy($edit_policy);
+ }
+
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
Index: src/applications/maniphest/storage/ManiphestTransaction.php
===================================================================
--- src/applications/maniphest/storage/ManiphestTransaction.php
+++ src/applications/maniphest/storage/ManiphestTransaction.php
@@ -78,6 +78,7 @@
switch ($this->getTransactionType()) {
case self::TYPE_TITLE:
case self::TYPE_DESCRIPTION:
+ case self::TYPE_PRIORITY:
if ($this->getOldValue() === null) {
return true;
} else {
Index: src/applications/meta/controller/PhabricatorApplicationDetailViewController.php
===================================================================
--- src/applications/meta/controller/PhabricatorApplicationDetailViewController.php
+++ src/applications/meta/controller/PhabricatorApplicationDetailViewController.php
@@ -29,36 +29,22 @@
->setName($selected->getName()));
$header = id(new PHUIHeaderView())
- ->setHeader($title);
-
- $status_tag = id(new PhabricatorTagView())
- ->setType(PhabricatorTagView::TYPE_STATE);
+ ->setHeader($title)
+ ->setUser($user)
+ ->setPolicyObject($selected);
if ($selected->isInstalled()) {
- $status_tag->setName(pht('Installed'));
- $status_tag->setBackgroundColor(PhabricatorTagView::COLOR_GREEN);
+ $header->setStatus('open', 'green', pht('Installed'));
} else {
- $status_tag->setName(pht('Uninstalled'));
- $status_tag->setBackgroundColor(PhabricatorTagView::COLOR_RED);
+ $header->setStatus('open', 'red', pht('Uninstalled'));
}
- if ($selected->isBeta()) {
- $beta_tag = id(new PhabricatorTagView())
- ->setType(PhabricatorTagView::TYPE_STATE)
- ->setName(pht('Beta'))
- ->setBackgroundColor(PhabricatorTagView::COLOR_GREY);
- $header->addTag($beta_tag);
- }
-
- $header->addTag($status_tag);
-
- $properties = $this->buildPropertyView($selected);
$actions = $this->buildActionView($user, $selected);
+ $properties = $this->buildPropertyView($selected, $actions);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
return $this->buildApplicationPage(
array(
@@ -71,11 +57,21 @@
));
}
- private function buildPropertyView(PhabricatorApplication $application) {
+ private function buildPropertyView(
+ PhabricatorApplication $application,
+ PhabricatorActionListView $actions) {
+
$viewer = $this->getRequest()->getUser();
- $properties = id(new PhabricatorPropertyListView())
+ $properties = id(new PHUIPropertyListView())
->addProperty(pht('Description'), $application->getShortDescription());
+ $properties->setActionList($actions);
+
+ if ($application->isBeta()) {
+ $properties->addProperty(
+ pht('Release'),
+ pht('Beta'));
+ }
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$viewer,
Index: src/applications/meta/controller/PhabricatorApplicationEditController.php
===================================================================
--- src/applications/meta/controller/PhabricatorApplicationEditController.php
+++ src/applications/meta/controller/PhabricatorApplicationEditController.php
@@ -47,14 +47,25 @@
}
if (empty($policies[$new])) {
- // Can't set the policy to something invalid.
- continue;
+ // Not a standard policy, check for a custom policy.
+ $policy = id(new PhabricatorPolicyQuery())
+ ->setViewer($user)
+ ->withPHIDs(array($new))
+ ->executeOne();
+ if (!$policy) {
+ // Not a custom policy either. Can't set the policy to something
+ // invalid, so skip this.
+ continue;
+ }
}
- if ($new == PhabricatorPolicies::POLICY_PUBLIC &&
- $capability != PhabricatorPolicyCapability::CAN_VIEW) {
- // Can't set policies other than "view" to public.
- continue;
+ if ($new == PhabricatorPolicies::POLICY_PUBLIC) {
+ $capobj = PhabricatorPolicyCapability::getCapabilityByKey(
+ $capability);
+ if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {
+ // Can't set non-public policies to public.
+ continue;
+ }
}
$result[$capability] = $new;
Index: src/applications/meta/query/PhabricatorAppSearchEngine.php
===================================================================
--- src/applications/meta/query/PhabricatorAppSearchEngine.php
+++ src/applications/meta/query/PhabricatorAppSearchEngine.php
@@ -12,9 +12,15 @@
$saved->setParameter('name', $request->getStr('name'));
- $saved->setParameter('installed', $this->readBool($request, 'installed'));
- $saved->setParameter('beta', $this->readBool($request, 'beta'));
- $saved->setParameter('firstParty', $this->readBool($request, 'firstParty'));
+ $saved->setParameter(
+ 'installed',
+ $this->readBoolFromRequest($request, 'installed'));
+ $saved->setParameter(
+ 'beta',
+ $this->readBoolFromRequest($request, 'beta'));
+ $saved->setParameter(
+ 'firstParty',
+ $this->readBoolFromRequest($request, 'firstParty'));
return $saved;
}
@@ -61,7 +67,7 @@
id(new AphrontFormSelectControl())
->setLabel(pht('Installed'))
->setName('installed')
- ->setValue($this->getBool($saved, 'installed'))
+ ->setValue($this->getBoolFromQuery($saved, 'installed'))
->setOptions(
array(
'' => pht('Show All Applications'),
@@ -72,7 +78,7 @@
id(new AphrontFormSelectControl())
->setLabel(pht('Beta'))
->setName('beta')
- ->setValue($this->getBool($saved, 'beta'))
+ ->setValue($this->getBoolFromQuery($saved, 'beta'))
->setOptions(
array(
'' => pht('Show All Applications'),
@@ -83,7 +89,7 @@
id(new AphrontFormSelectControl())
->setLabel(pht('Provenance'))
->setName('firstParty')
- ->setValue($this->getBool($saved, 'firstParty'))
+ ->setValue($this->getBoolFromQuery($saved, 'firstParty'))
->setOptions(
array(
'' => pht('Show All Applications'),
@@ -118,19 +124,4 @@
return parent::buildSavedQueryFromBuiltin($query_key);
}
- private function readBool(AphrontRequest $request, $key) {
- if (!strlen($request->getStr($key))) {
- return null;
- }
- return $request->getBool($key);
- }
-
- private function getBool(PhabricatorSavedQuery $query, $key) {
- $value = $query->getParameter($key);
- if ($value === null) {
- return $value;
- }
- return $value ? 'true' : 'false';
- }
-
}
Index: src/applications/metamta/management/PhabricatorMailManagementShowInboundWorkflow.php
===================================================================
--- src/applications/metamta/management/PhabricatorMailManagementShowInboundWorkflow.php
+++ src/applications/metamta/management/PhabricatorMailManagementShowInboundWorkflow.php
@@ -54,6 +54,12 @@
$info[] = pht('Author PHID: %s', $message->getAuthorPHID());
$info[] = pht('Message ID Hash: %s', $message->getMessageIDHash());
+ if ($message->getMessage()) {
+ $info[] = null;
+ $info[] = pht('MESSAGE');
+ $info[] = $message->getMessage();
+ }
+
$info[] = null;
$info[] = pht('HEADERS');
foreach ($message->getHeaders() as $key => $value) {
Index: src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php
===================================================================
--- src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php
+++ src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php
@@ -2,8 +2,51 @@
final class PhabricatorMetaMTAEmailBodyParser {
+ /**
+ * Mails can have bodies such as
+ *
+ * !claim
+ *
+ * taking this task
+ *
+ * Or
+ *
+ * !assign epriestley
+ *
+ * please, take this task I took; its hard
+ *
+ * This function parses such an email body and returns a dictionary
+ * containing a clean body text (e.g. "taking this task"), a $command
+ * (e.g. !claim, !assign) and a $command_value (e.g. "epriestley" in the
+ * !assign example.)
+ *
+ * @return dict
+ */
+ public function parseBody($body) {
+ $body = $this->stripTextBody($body);
+ $lines = explode("\n", trim($body));
+ $first_line = head($lines);
+
+ $command = null;
+ $command_value = null;
+ $matches = null;
+ if (preg_match('/^!(\w+)\s*(\S+)?/', $first_line, $matches)) {
+ $lines = array_slice($lines, 1);
+ $body = implode("\n", $lines);
+ $body = trim($body);
+
+ $command = $matches[1];
+ $command_value = idx($matches, 2);
+ }
+
+ return array(
+ 'body' => $body,
+ 'command' => $command,
+ 'command_value' => $command_value);
+ }
+
public function stripTextBody($body) {
- return $this->stripSignature($this->stripQuotedText($body));
+ return trim($this->stripSignature($this->stripQuotedText($body)));
}
private function stripQuotedText($body) {
Index: src/applications/metamta/parser/__tests__/PhabricatorMetaMTAEmailBodyParserTestCase.php
===================================================================
--- src/applications/metamta/parser/__tests__/PhabricatorMetaMTAEmailBodyParserTestCase.php
+++ src/applications/metamta/parser/__tests__/PhabricatorMetaMTAEmailBodyParserTestCase.php
@@ -12,6 +12,44 @@
}
}
+ public function testEmailBodyCommandParsing() {
+ $bodies = $this->getEmailBodiesWithFullCommands();
+ foreach ($bodies as $body) {
+ $parser = new PhabricatorMetaMTAEmailBodyParser();
+ $body_data = $parser->parseBody($body);
+ $this->assertEqual('OKAY', $body_data['body']);
+ $this->assertEqual('whatevs', $body_data['command']);
+ $this->assertEqual('dude', $body_data['command_value']);
+ }
+ $bodies = $this->getEmailBodiesWithPartialCommands();
+ foreach ($bodies as $body) {
+ $parser = new PhabricatorMetaMTAEmailBodyParser();
+ $body_data = $parser->parseBody($body);
+ $this->assertEqual('OKAY', $body_data['body']);
+ $this->assertEqual('whatevs', $body_data['command']);
+ $this->assertEqual(null, $body_data['command_value']);
+ }
+ }
+
+ private function getEmailBodiesWithFullCommands() {
+ $bodies = $this->getEmailBodies();
+ $with_commands = array();
+ foreach ($bodies as $body) {
+ $with_commands[] = "!whatevs dude\n" . $body;
+ }
+ return $with_commands;
+ }
+
+ private function getEmailBodiesWithPartialCommands() {
+ $bodies = $this->getEmailBodies();
+ $with_commands = array();
+ foreach ($bodies as $body) {
+ $with_commands[] = "!whatevs\n" . $body;
+ }
+ return $with_commands;
+ }
+
+
private function getEmailBodies() {
$trailing_space = ' ';
Index: src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php
===================================================================
--- src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php
+++ src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php
@@ -307,7 +307,7 @@
return $this->getSingleReplyHandlerPrefix($address);
}
- final protected function enhanceBodyWithAttachments(
+ final protected function enhanceBodyWithAttachments(
$body,
array $attachments,
$format = '- {F%d, layout=link}') {
Index: src/applications/metamta/storage/PhabricatorMetaMTAMail.php
===================================================================
--- src/applications/metamta/storage/PhabricatorMetaMTAMail.php
+++ src/applications/metamta/storage/PhabricatorMetaMTAMail.php
@@ -383,10 +383,13 @@
switch ($key) {
case 'from':
$from = $value;
- $actor = $actors[$from];
-
- $actor_email = $actor->getEmailAddress();
- $actor_name = $actor->getName();
+ $actor_email = null;
+ $actor_name = null;
+ $actor = idx($actors, $from);
+ if ($actor) {
+ $actor_email = $actor->getEmailAddress();
+ $actor_name = $actor->getName();
+ }
$can_send_as_user = $actor_email &&
PhabricatorEnv::getEnvConfig('metamta.can-send-as-user');
Index: src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php
===================================================================
--- src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php
+++ src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php
@@ -120,12 +120,17 @@
}
public function getCleanTextBody() {
- $body = idx($this->bodies, 'text');
-
+ $body = $this->getRawTextBody();
$parser = new PhabricatorMetaMTAEmailBodyParser();
return $parser->stripTextBody($body);
}
+ public function parseBody() {
+ $body = $this->getRawTextBody();
+ $parser = new PhabricatorMetaMTAEmailBodyParser();
+ return $parser->parseBody($body);
+ }
+
public function getRawTextBody() {
return idx($this->bodies, 'text');
}
Index: src/applications/notification/PhabricatorNotificationQuery.php
===================================================================
--- src/applications/notification/PhabricatorNotificationQuery.php
+++ src/applications/notification/PhabricatorNotificationQuery.php
@@ -107,4 +107,8 @@
return $this->formatWhereClause($where);
}
+ protected function getPagingValue($item) {
+ return $item->getChronologicalKey();
+ }
+
}
Index: src/applications/owners/controller/PhabricatorOwnersEditController.php
===================================================================
--- src/applications/owners/controller/PhabricatorOwnersEditController.php
+++ src/applications/owners/controller/PhabricatorOwnersEditController.php
@@ -123,15 +123,12 @@
$primary = $package->getPrimaryOwnerPHID();
if ($primary && isset($handles[$primary])) {
- $token_primary_owner = array(
- $primary => $handles[$primary]->getFullName(),
- );
+ $handle_primary_owner = array($handles[$primary]);
} else {
- $token_primary_owner = array();
+ $handle_primary_owner = array();
}
- $token_all_owners = array_select_keys($handles, $owners);
- $token_all_owners = mpull($token_all_owners, 'getFullName');
+ $handles_all_owners = array_select_keys($handles, $owners);
if ($package->getID()) {
$title = pht('Edit Package');
@@ -195,14 +192,14 @@
->setLabel(pht('Primary Owner'))
->setName('primary')
->setLimit(1)
- ->setValue($token_primary_owner)
+ ->setValue($handle_primary_owner)
->setError($e_primary))
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/usersorprojects/')
->setLabel(pht('Owners'))
->setName('owners')
- ->setValue($token_all_owners))
+ ->setValue($handles_all_owners))
->appendChild(
id(new AphrontFormSelectControl())
->setName('auditing')
Index: src/applications/owners/controller/PhabricatorOwnersListController.php
===================================================================
--- src/applications/owners/controller/PhabricatorOwnersListController.php
+++ src/applications/owners/controller/PhabricatorOwnersListController.php
@@ -153,9 +153,7 @@
$phids = $request->getArr('owner');
$phid = reset($phids);
$handles = $this->loadViewerHandles(array($phid));
- $owners_search_value = array(
- $phid => $handles[$phid]->getFullName(),
- );
+ $owners_search_value = array($handles[$phid]);
}
$callsigns = array('' => pht('(Any Repository)'));
Index: src/applications/owners/storage/PhabricatorOwnersPackage.php
===================================================================
--- src/applications/owners/storage/PhabricatorOwnersPackage.php
+++ src/applications/owners/storage/PhabricatorOwnersPackage.php
@@ -3,7 +3,6 @@
final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO
implements PhabricatorPolicyInterface {
- protected $phid;
protected $name;
protected $originalName;
protected $auditingEnabled;
Index: src/applications/paste/application/PhabricatorApplicationPaste.php
===================================================================
--- src/applications/paste/application/PhabricatorApplicationPaste.php
+++ src/applications/paste/application/PhabricatorApplicationPaste.php
@@ -1,8 +1,5 @@
<?php
-/**
- * group paste
- */
final class PhabricatorApplicationPaste extends PhabricatorApplication {
public function getBaseURI() {
@@ -44,4 +41,13 @@
);
}
+ protected function getCustomCapabilities() {
+ return array(
+ PasteCapabilityDefaultView::CAPABILITY => array(
+ 'caption' => pht(
+ 'Default view policy for newly created pastes.')
+ ),
+ );
+ }
+
}
Index: src/applications/paste/capability/PasteCapabilityDefaultView.php
===================================================================
--- /dev/null
+++ src/applications/paste/capability/PasteCapabilityDefaultView.php
@@ -0,0 +1,20 @@
+<?php
+
+final class PasteCapabilityDefaultView
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'paste.default.view';
+
+ public function getCapabilityKey() {
+ return self::CAPABILITY;
+ }
+
+ public function getCapabilityName() {
+ return pht('Default View Policy');
+ }
+
+ public function shouldAllowPublicPolicySetting() {
+ return true;
+ }
+
+}
Index: src/applications/paste/conduit/ConduitAPI_paste_create_Method.php
===================================================================
--- src/applications/paste/conduit/ConduitAPI_paste_create_Method.php
+++ src/applications/paste/conduit/ConduitAPI_paste_create_Method.php
@@ -49,14 +49,16 @@
'authorPHID' => $user->getPHID(),
));
- $paste = new PhabricatorPaste();
+ // TODO: This should use PhabricatorPasteEditor.
+
+ $paste = PhabricatorPaste::initializeNewPaste($user);
$paste->setTitle($title);
$paste->setLanguage($language);
$paste->setFilePHID($paste_file->getPHID());
- $paste->setAuthorPHID($user->getPHID());
- $paste->setViewPolicy(PhabricatorPolicies::POLICY_USER);
$paste->save();
+ $paste_file->attachToObject($user, $paste->getPHID());
+
$paste->attachRawContent($content);
return $this->buildPasteInfoDictionary($paste);
Index: src/applications/paste/controller/PhabricatorPasteEditController.php
===================================================================
--- src/applications/paste/controller/PhabricatorPasteEditController.php
+++ src/applications/paste/controller/PhabricatorPasteEditController.php
@@ -21,7 +21,7 @@
if (!$this->id) {
$is_create = true;
- $paste = new PhabricatorPaste();
+ $paste = PhabricatorPaste::initializeNewPaste($user);
$parent_id = $request->getStr('parent');
if ($parent_id) {
Index: src/applications/paste/controller/PhabricatorPasteViewController.php
===================================================================
--- src/applications/paste/controller/PhabricatorPasteViewController.php
+++ src/applications/paste/controller/PhabricatorPasteViewController.php
@@ -68,12 +68,11 @@
$header = $this->buildHeaderView($paste);
$actions = $this->buildActionView($user, $paste, $file);
- $properties = $this->buildPropertyView($paste, $fork_phids);
+ $properties = $this->buildPropertyView($paste, $fork_phids, $actions);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
$source_code = $this->buildSourceCodeView(
$paste,
@@ -206,12 +205,14 @@
private function buildPropertyView(
PhabricatorPaste $paste,
- array $child_phids) {
+ array $child_phids,
+ PhabricatorActionListView $actions) {
$user = $this->getRequest()->getUser();
- $properties = id(new PhabricatorPropertyListView())
+ $properties = id(new PHUIPropertyListView())
->setUser($user)
- ->setObject($paste);
+ ->setObject($paste)
+ ->setActionList($actions);
$properties->addProperty(
pht('Author'),
Index: src/applications/paste/editor/PhabricatorPasteEditor.php
===================================================================
--- src/applications/paste/editor/PhabricatorPasteEditor.php
+++ src/applications/paste/editor/PhabricatorPasteEditor.php
@@ -1,11 +1,10 @@
<?php
-/**
- * @group paste
- */
final class PhabricatorPasteEditor
extends PhabricatorApplicationTransactionEditor {
+ private $pasteFile;
+
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
@@ -95,11 +94,28 @@
'authorPHID' => $this->getActor()->getPHID(),
));
$object->setFilePHID($paste_file->getPHID());
+
+ $this->pasteFile = $paste_file;
break;
}
}
}
+ protected function applyFinalEffects(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ // TODO: This should use extractFilePHIDs() instead, but the way
+ // the transactions work right now makes pretty messy.
+
+ if ($this->pasteFile) {
+ $this->pasteFile->attachToObject(
+ $this->getActor(),
+ $object->getPHID());
+ }
+ }
+
+
protected function shouldSendMail(
PhabricatorLiskDAO $object,
array $xactions) {
Index: src/applications/paste/mail/PasteReplyHandler.php
===================================================================
--- src/applications/paste/mail/PasteReplyHandler.php
+++ src/applications/paste/mail/PasteReplyHandler.php
@@ -32,8 +32,8 @@
$actor = $this->getActor();
$paste = $this->getMailReceiver();
- $body = $mail->getCleanTextBody();
- $body = trim($body);
+ $body_data = $mail->parseBody();
+ $body = $body_data['body'];
$body = $this->enhanceBodyWithAttachments($body, $mail->getAttachments());
$content_source = PhabricatorContentSource::newForSource(
@@ -46,15 +46,7 @@
$first_line = head($lines);
$xactions = array();
- $command = null;
- $matches = null;
- if (preg_match('/^!(\w+)/', $first_line, $matches)) {
- $lines = array_slice($lines, 1);
- $body = implode("\n", $lines);
- $body = trim($body);
-
- $command = $matches[1];
- }
+ $command = $body_data['command'];
switch ($command) {
case 'unsubscribe':
Index: src/applications/paste/query/PhabricatorPasteQuery.php
===================================================================
--- src/applications/paste/query/PhabricatorPasteQuery.php
+++ src/applications/paste/query/PhabricatorPasteQuery.php
@@ -87,7 +87,7 @@
return $pastes;
}
- protected function willFilterPage(array $pastes) {
+ protected function didFilterPage(array $pastes) {
if ($this->needRawContent) {
$pastes = $this->loadRawContent($pastes);
}
@@ -163,6 +163,7 @@
private function loadRawContent(array $pastes) {
$file_phids = mpull($pastes, 'getFilePHID');
$files = id(new PhabricatorFileQuery())
+ ->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($file_phids)
->execute();
@@ -174,7 +175,14 @@
unset($pastes[$key]);
continue;
}
- $paste->attachRawContent($file->loadFileData());
+ try {
+ $paste->attachRawContent($file->loadFileData());
+ } catch (Exception $ex) {
+ // We can hit various sorts of file storage issues here. Just drop the
+ // paste if the file is dead.
+ unset($pastes[$key]);
+ continue;
+ }
}
return $pastes;
Index: src/applications/paste/query/PhabricatorPasteSearchEngine.php
===================================================================
--- src/applications/paste/query/PhabricatorPasteSearchEngine.php
+++ src/applications/paste/query/PhabricatorPasteSearchEngine.php
@@ -48,11 +48,10 @@
AphrontFormView $form,
PhabricatorSavedQuery $saved_query) {
$phids = $saved_query->getParameter('authorPHIDs', array());
- $handles = id(new PhabricatorHandleQuery())
+ $author_handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireViewer())
->withPHIDs($phids)
->execute();
- $author_tokens = mpull($handles, 'getFullName', 'getPHID');
$languages = $saved_query->getParameter('languages', array());
$no_language = false;
@@ -70,7 +69,7 @@
->setDatasource('/typeahead/common/users/')
->setName('authors')
->setLabel(pht('Authors'))
- ->setValue($author_tokens))
+ ->setValue($author_handles))
->appendChild(
id(new AphrontFormTextControl())
->setName('languages')
Index: src/applications/paste/storage/PhabricatorPaste.php
===================================================================
--- src/applications/paste/storage/PhabricatorPaste.php
+++ src/applications/paste/storage/PhabricatorPaste.php
@@ -1,15 +1,11 @@
<?php
-/**
- * @group paste
- */
final class PhabricatorPaste extends PhabricatorPasteDAO
implements
PhabricatorSubscribableInterface,
PhabricatorTokenReceiverInterface,
PhabricatorPolicyInterface {
- protected $phid;
protected $title;
protected $authorPHID;
protected $filePHID;
@@ -21,6 +17,20 @@
private $content = self::ATTACHABLE;
private $rawContent = self::ATTACHABLE;
+ public static function initializeNewPaste(PhabricatorUser $actor) {
+ $app = id(new PhabricatorApplicationQuery())
+ ->setViewer($actor)
+ ->withClasses(array('PhabricatorApplicationPaste'))
+ ->executeOne();
+
+ $view_policy = $app->getPolicy(PasteCapabilityDefaultView::CAPABILITY);
+
+ return id(new PhabricatorPaste())
+ ->setTitle('')
+ ->setAuthorPHID($actor->getPHID())
+ ->setViewPolicy($view_policy);
+ }
+
public function getURI() {
return '/P'.$this->getID();
}
@@ -43,29 +53,6 @@
return parent::save();
}
- public function getCapabilities() {
- return array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- );
- }
-
- public function getPolicy($capability) {
- if ($capability == PhabricatorPolicyCapability::CAN_VIEW) {
- return $this->viewPolicy;
- }
- return PhabricatorPolicies::POLICY_NOONE;
- }
-
- public function hasAutomaticCapability($capability, PhabricatorUser $user) {
- return ($user->getPHID() == $this->getAuthorPHID());
- }
-
- public function describeAutomaticCapability($capability) {
- return pht(
- 'The author of a paste can always view and edit it.');
- }
-
public function getFullName() {
$title = $this->getTitle();
if (!$title) {
@@ -92,7 +79,7 @@
return $this;
}
-/* -( PhabricatorSubscribableInterface Implementation )-------------------- */
+/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
@@ -108,4 +95,31 @@
);
}
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ );
+ }
+
+ public function getPolicy($capability) {
+ if ($capability == PhabricatorPolicyCapability::CAN_VIEW) {
+ return $this->viewPolicy;
+ }
+ return PhabricatorPolicies::POLICY_NOONE;
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $user) {
+ return ($user->getPHID() == $this->getAuthorPHID());
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return pht('The author of a paste can always view and edit it.');
+ }
+
+
}
Index: src/applications/paste/storage/PhabricatorPasteTransaction.php
===================================================================
--- src/applications/paste/storage/PhabricatorPasteTransaction.php
+++ src/applications/paste/storage/PhabricatorPasteTransaction.php
@@ -1,8 +1,5 @@
<?php
-/**
- * @group paste
- */
final class PhabricatorPasteTransaction
extends PhabricatorApplicationTransaction {
Index: src/applications/people/controller/PhabricatorPeopleLogsController.php
===================================================================
--- src/applications/people/controller/PhabricatorPeopleLogsController.php
+++ src/applications/people/controller/PhabricatorPeopleLogsController.php
@@ -22,16 +22,12 @@
$handles = $this->loadViewerHandles($phids);
if ($filter_user) {
$filter_user = reset($filter_user);
- $user_value = array(
- $filter_user => $handles[$filter_user]->getFullName(),
- );
+ $user_value = array($handles[$filter_user]);
}
if ($filter_actor) {
$filter_actor = reset($filter_actor);
- $actor_value = array(
- $filter_actor => $handles[$filter_actor]->getFullName(),
- );
+ $actor_value = array($handles[$filter_actor]);
}
}
Index: src/applications/people/controller/PhabricatorPeopleProfileController.php
===================================================================
--- src/applications/people/controller/PhabricatorPeopleProfileController.php
+++ src/applications/people/controller/PhabricatorPeopleProfileController.php
@@ -68,7 +68,7 @@
->setHref($this->getApplicationURI('edit/'.$user->getID().'/')));
}
- $properties = $this->buildPropertyView($user);
+ $properties = $this->buildPropertyView($user, $actions);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
@@ -78,8 +78,7 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
return $this->buildApplicationPage(
array(
@@ -93,12 +92,15 @@
));
}
- private function buildPropertyView(PhabricatorUser $user) {
- $viewer = $this->getRequest()->getUser();
+ private function buildPropertyView(
+ PhabricatorUser $user,
+ PhabricatorActionListView $actions) {
- $view = id(new PhabricatorPropertyListView())
+ $viewer = $this->getRequest()->getUser();
+ $view = id(new PHUIPropertyListView())
->setUser($viewer)
- ->setObject($user);
+ ->setObject($user)
+ ->setActionList($actions);
$field_list = PhabricatorCustomField::getObjectFields(
$user,
Index: src/applications/people/controller/PhabricatorPeopleProfilePictureController.php
===================================================================
--- src/applications/people/controller/PhabricatorPeopleProfilePictureController.php
+++ src/applications/people/controller/PhabricatorPeopleProfilePictureController.php
@@ -82,6 +82,7 @@
$user->setProfileImagePHID(null);
} else {
$user->setProfileImagePHID($xformed->getPHID());
+ $xformed->attachToObject($viewer, $user->getPHID());
}
$user->save();
return id(new AphrontRedirectResponse())->setURI($profile_uri);
Index: src/applications/people/query/PhabricatorPeopleQuery.php
===================================================================
--- src/applications/people/query/PhabricatorPeopleQuery.php
+++ src/applications/people/query/PhabricatorPeopleQuery.php
@@ -113,8 +113,10 @@
$table->putInSet(new LiskDAOSet());
}
- $users = $table->loadAllFromArray($data);
+ return $table->loadAllFromArray($data);
+ }
+ protected function didFilterPage(array $users) {
if ($this->needProfile) {
$user_list = mpull($users, null, 'getPHID');
$profiles = new PhabricatorUserProfile();
@@ -138,6 +140,7 @@
$user_profile_file_phids = array_filter($user_profile_file_phids);
if ($user_profile_file_phids) {
$files = id(new PhabricatorFileQuery())
+ ->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($user_profile_file_phids)
->execute();
Index: src/applications/people/storage/PhabricatorUser.php
===================================================================
--- src/applications/people/storage/PhabricatorUser.php
+++ src/applications/people/storage/PhabricatorUser.php
@@ -11,7 +11,6 @@
const NAMETOKEN_TABLE = 'user_nametoken';
const MAXIMUM_USERNAME_LENGTH = 64;
- protected $phid;
protected $userName;
protected $realName;
protected $sex;
Index: src/applications/phame/controller/blog/PhameBlogViewController.php
===================================================================
--- src/applications/phame/controller/blog/PhameBlogViewController.php
+++ src/applications/phame/controller/blog/PhameBlogViewController.php
@@ -44,7 +44,7 @@
$this->loadHandles($handle_phids);
$actions = $this->renderActions($blog, $user);
- $properties = $this->renderProperties($blog, $user);
+ $properties = $this->renderProperties($blog, $user, $actions);
$post_list = $this->renderPostList(
$posts,
$user,
@@ -65,8 +65,7 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
$nav->appendChild(
array(
@@ -83,11 +82,16 @@
));
}
- private function renderProperties(PhameBlog $blog, PhabricatorUser $user) {
+ private function renderProperties(
+ PhameBlog $blog,
+ PhabricatorUser $user,
+ PhabricatorActionListView $actions) {
+
require_celerity_resource('aphront-tooltip-css');
Javelin::initBehavior('phabricator-tooltips');
- $properties = new PhabricatorPropertyListView();
+ $properties = new PHUIPropertyListView();
+ $properties->setActionList($actions);
$properties->addProperty(
pht('Skin'),
Index: src/applications/phame/controller/post/PhamePostViewController.php
===================================================================
--- src/applications/phame/controller/post/PhamePostViewController.php
+++ src/applications/phame/controller/post/PhamePostViewController.php
@@ -32,7 +32,7 @@
$post->getBloggerPHID(),
));
$actions = $this->renderActions($post, $user);
- $properties = $this->renderProperties($post, $user);
+ $properties = $this->renderProperties($post, $user, $actions);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
@@ -50,8 +50,7 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
if ($post->isDraft()) {
$object_box->appendChild(
@@ -167,11 +166,13 @@
private function renderProperties(
PhamePost $post,
- PhabricatorUser $user) {
+ PhabricatorUser $user,
+ PhabricatorActionListView $actions) {
- $properties = id(new PhabricatorPropertyListView())
+ $properties = id(new PHUIPropertyListView())
->setUser($user)
- ->setObject($post);
+ ->setObject($post)
+ ->setActionList($actions);
$properties->addProperty(
pht('Blog'),
Index: src/applications/phame/storage/PhameBlog.php
===================================================================
--- src/applications/phame/storage/PhameBlog.php
+++ src/applications/phame/storage/PhameBlog.php
@@ -10,8 +10,6 @@
const SKIN_DEFAULT = 'oblivious';
- protected $id;
- protected $phid;
protected $name;
protected $description;
protected $domain;
Index: src/applications/phame/storage/PhamePost.php
===================================================================
--- src/applications/phame/storage/PhamePost.php
+++ src/applications/phame/storage/PhamePost.php
@@ -15,8 +15,6 @@
const VISIBILITY_DRAFT = 0;
const VISIBILITY_PUBLISHED = 1;
- protected $id;
- protected $phid;
protected $bloggerPHID;
protected $title;
protected $phameTitle;
Index: src/applications/phid/PhabricatorObjectHandle.php
===================================================================
--- src/applications/phid/PhabricatorObjectHandle.php
+++ src/applications/phid/PhabricatorObjectHandle.php
@@ -14,6 +14,19 @@
private $status = PhabricatorObjectHandleStatus::STATUS_OPEN;
private $complete;
private $disabled;
+ private $objectName;
+
+ public function setObjectName($object_name) {
+ $this->objectName = $object_name;
+ return $this;
+ }
+
+ public function getObjectName() {
+ if (!$this->objectName) {
+ return $this->getName();
+ }
+ return $this->objectName;
+ }
public function setURI($uri) {
$this->uri = $uri;
Index: src/applications/phid/query/PhabricatorObjectQuery.php
===================================================================
--- src/applications/phid/query/PhabricatorObjectQuery.php
+++ src/applications/phid/query/PhabricatorObjectQuery.php
@@ -104,13 +104,27 @@
}
private function loadObjectsByPHID(array $types, array $phids) {
+ $results = array();
+
+ $workspace = $this->getObjectsFromWorkspace($phids);
+
+ foreach ($phids as $key => $phid) {
+ if (isset($workspace[$phid])) {
+ $results[$phid] = $workspace[$phid];
+ unset($phids[$key]);
+ }
+ }
+
+ if (!$phids) {
+ return $results;
+ }
+
$groups = array();
foreach ($phids as $phid) {
$type = phid_get_type($phid);
$groups[$type][] = $phid;
}
- $results = array();
foreach ($groups as $type => $group) {
if (isset($types[$type])) {
$objects = $types[$type]->loadObjects($this, $group);
Index: src/applications/phlux/controller/PhluxViewController.php
===================================================================
--- src/applications/phlux/controller/PhluxViewController.php
+++ src/applications/phlux/controller/PhluxViewController.php
@@ -55,9 +55,10 @@
$display_value = json_encode($var->getVariableValue());
- $properties = id(new PhabricatorPropertyListView())
+ $properties = id(new PHUIPropertyListView())
->setUser($user)
->setObject($var)
+ ->setActionList($actions)
->addProperty(pht('Value'), $display_value);
$xactions = id(new PhluxTransactionQuery())
@@ -76,8 +77,7 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
return $this->buildApplicationPage(
array(
Index: src/applications/pholio/controller/PholioMockEditController.php
===================================================================
--- src/applications/pholio/controller/PholioMockEditController.php
+++ src/applications/pholio/controller/PholioMockEditController.php
@@ -230,8 +230,6 @@
->withPHIDs($v_cc)
->execute();
- $cc_tokens = mpull($handles, 'getFullName', 'getPHID');
-
$image_elements = array();
foreach ($mock_images as $mock_image) {
$image_elements[] = id(new PholioUploadedImageView())
@@ -303,7 +301,7 @@
id(new AphrontFormTokenizerControl())
->setLabel(pht('CC'))
->setName('cc')
- ->setValue($cc_tokens)
+ ->setValue($handles)
->setUser($user)
->setDatasource('/typeahead/common/mailable/'))
->appendChild(
Index: src/applications/pholio/controller/PholioMockViewController.php
===================================================================
--- src/applications/pholio/controller/PholioMockViewController.php
+++ src/applications/pholio/controller/PholioMockViewController.php
@@ -73,7 +73,7 @@
->setPolicyObject($mock);
$actions = $this->buildActionView($mock);
- $properties = $this->buildPropertyView($mock, $engine);
+ $properties = $this->buildPropertyView($mock, $engine, $actions);
require_celerity_resource('pholio-css');
require_celerity_resource('pholio-inline-comments-css');
@@ -105,8 +105,7 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
$content = array(
$crumbs,
@@ -197,13 +196,15 @@
private function buildPropertyView(
PholioMock $mock,
- PhabricatorMarkupEngine $engine) {
+ PhabricatorMarkupEngine $engine,
+ PhabricatorActionListView $actions) {
$user = $this->getRequest()->getUser();
- $properties = id(new PhabricatorPropertyListView())
+ $properties = id(new PHUIPropertyListView())
->setUser($user)
- ->setObject($mock);
+ ->setObject($mock)
+ ->setActionList($actions);
$properties->addProperty(
pht('Author'),
@@ -222,7 +223,7 @@
$properties->invokeWillRenderEvent();
$properties->addImageContent(
- $engine->getOutput($mock, PholioMock::MARKUP_FIELD_DESCRIPTION));
+ $engine->getOutput($mock, PholioMock::MARKUP_FIELD_DESCRIPTION));
return $properties;
}
Index: src/applications/pholio/editor/PholioMockEditor.php
===================================================================
--- src/applications/pholio/editor/PholioMockEditor.php
+++ src/applications/pholio/editor/PholioMockEditor.php
@@ -106,6 +106,26 @@
}
}
+ protected function extractFilePHIDsFromCustomTransaction(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ switch ($xaction->getTransactionType()) {
+ case PholioTransactionType::TYPE_IMAGE_FILE:
+ $new = $xaction->getNewValue();
+ $phids = array();
+ foreach ($new as $key => $images) {
+ $phids[] = mpull($images, 'getFilePHID');
+ }
+ return array_mergev($phids);
+ case PholioTransactionType::TYPE_IMAGE_REPLACE:
+ return array($xaction->getNewValue()->getFilePHID());
+ }
+
+ return array();
+ }
+
+
protected function transactionHasEffect(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
Index: src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php
===================================================================
--- src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php
+++ src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php
@@ -92,9 +92,18 @@
->loadAllWhere("mimeType = %s", "image/jpeg");
$rand_images = array();
$quantity = rand(2, 10);
+ $quantity = min($quantity, count($images));
foreach (array_rand($images, $quantity) as $random) {
$rand_images[] = $images[$random]->getPHID();
}
+ // this means you don't have any jpegs yet. we'll
+ // just use a builtin image
+ if (empty($rand_images)) {
+ $default = PhabricatorFile::loadBuiltin(
+ PhabricatorUser::getOmnipotentUser(),
+ 'profile.png');
+ $rand_images[] = $default->getPHID();
+ }
return $rand_images;
}
Index: src/applications/pholio/query/PholioImageQuery.php
===================================================================
--- src/applications/pholio/query/PholioImageQuery.php
+++ src/applications/pholio/query/PholioImageQuery.php
@@ -103,9 +103,37 @@
protected function willFilterPage(array $images) {
assert_instances_of($images, 'PholioImage');
+ if ($this->getMockCache()) {
+ $mocks = $this->getMockCache();
+ } else {
+ $mock_ids = mpull($images, 'getMockID');
+ // DO NOT set needImages to true; recursion results!
+ $mocks = id(new PholioMockQuery())
+ ->setViewer($this->getViewer())
+ ->withIDs($mock_ids)
+ ->execute();
+ $mocks = mpull($mocks, null, 'getID');
+ }
+ foreach ($images as $index => $image) {
+ $mock = idx($mocks, $image->getMockID());
+ if ($mock) {
+ $image->attachMock($mock);
+ } else {
+ // mock is missing or we can't see it
+ unset($images[$index]);
+ }
+ }
+
+ return $images;
+ }
+
+ protected function didFilterPage(array $images) {
+ assert_instances_of($images, 'PholioImage');
+
$file_phids = mpull($images, 'getFilePHID');
$all_files = id(new PhabricatorFileQuery())
+ ->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($file_phids)
->execute();
@@ -130,27 +158,6 @@
}
}
- if ($this->getMockCache()) {
- $mocks = $this->getMockCache();
- } else {
- $mock_ids = mpull($images, 'getMockID');
- // DO NOT set needImages to true; recursion results!
- $mocks = id(new PholioMockQuery())
- ->setViewer($this->getViewer())
- ->withIDs($mock_ids)
- ->execute();
- $mocks = mpull($mocks, null, 'getID');
- }
- foreach ($images as $index => $image) {
- $mock = idx($mocks, $image->getMockID());
- if ($mock) {
- $image->attachMock($mock);
- } else {
- // mock is missing or we can't see it
- unset($images[$index]);
- }
- }
-
return $images;
}
Index: src/applications/pholio/query/PholioMockSearchEngine.php
===================================================================
--- src/applications/pholio/query/PholioMockSearchEngine.php
+++ src/applications/pholio/query/PholioMockSearchEngine.php
@@ -27,11 +27,10 @@
PhabricatorSavedQuery $saved_query) {
$phids = $saved_query->getParameter('authorPHIDs', array());
- $handles = id(new PhabricatorHandleQuery())
+ $author_handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireViewer())
->withPHIDs($phids)
->execute();
- $author_tokens = mpull($handles, 'getFullName', 'getPHID');
$form
->appendChild(
@@ -39,8 +38,7 @@
->setDatasource('/typeahead/common/users/')
->setName('authors')
->setLabel(pht('Authors'))
- ->setValue($author_tokens));
-
+ ->setValue($author_handles));
}
protected function getURI($path) {
Index: src/applications/phortune/controller/PhortuneAccountViewController.php
===================================================================
--- src/applications/phortune/controller/PhortuneAccountViewController.php
+++ src/applications/phortune/controller/PhortuneAccountViewController.php
@@ -50,11 +50,12 @@
$crumbs->setActionList($actions);
- $properties = id(new PhabricatorPropertyListView())
+ $properties = id(new PHUIPropertyListView())
->setObject($account)
->setUser($user);
$properties->addProperty(pht('Balance'), $account->getBalanceInCents());
+ $properties->setActionList($actions);
$payment_methods = $this->buildPaymentMethodsSection($account);
$purchase_history = $this->buildPurchaseHistorySection($account);
@@ -62,8 +63,7 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
return $this->buildApplicationPage(
array(
Index: src/applications/phortune/controller/PhortuneProductViewController.php
===================================================================
--- src/applications/phortune/controller/PhortuneProductViewController.php
+++ src/applications/phortune/controller/PhortuneProductViewController.php
@@ -59,8 +59,9 @@
->setName(pht('#%d', $product->getID()))
->setHref($request->getRequestURI()));
- $properties = id(new PhabricatorPropertyListView())
+ $properties = id(new PHUIPropertyListView())
->setUser($user)
+ ->setActionList($actions)
->addProperty(pht('Type'), $product->getTypeName())
->addProperty(
pht('Price'),
@@ -83,8 +84,7 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
return $this->buildApplicationPage(
array(
Index: src/applications/phriction/controller/PhrictionDocumentController.php
===================================================================
--- src/applications/phriction/controller/PhrictionDocumentController.php
+++ src/applications/phriction/controller/PhrictionDocumentController.php
@@ -162,13 +162,19 @@
$header = id(new PHUIHeaderView())
->setHeader($page_title);
+ $prop_list = null;
+ if ($properties) {
+ $prop_list = new PHUIPropertyGroupView();
+ $prop_list->addPropertyList($properties);
+ }
+
$page_content = id(new PHUIDocumentView())
->setOffset(true)
->setHeader($header)
->appendChild(
array(
$actions,
- $properties,
+ $prop_list,
$move_notice,
$core_content,
));
@@ -202,7 +208,7 @@
$slug) {
$viewer = $this->getRequest()->getUser();
- $view = id(new PhabricatorPropertyListView())
+ $view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($document);
Index: src/applications/phriction/storage/PhrictionDocument.php
===================================================================
--- src/applications/phriction/storage/PhrictionDocument.php
+++ src/applications/phriction/storage/PhrictionDocument.php
@@ -9,8 +9,6 @@
PhabricatorSubscribableInterface,
PhabricatorTokenReceiverInterface {
- protected $id;
- protected $phid;
protected $slug;
protected $depth;
protected $contentID;
Index: src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php
===================================================================
--- src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php
+++ src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php
@@ -31,4 +31,117 @@
$this->assertEqual(0, count($results));
}
+
+ public function testCustomPolicyRuleUser() {
+ $user_a = $this->generateNewTestUser();
+ $user_b = $this->generateNewTestUser();
+ $author = $this->generateNewTestUser();
+
+ $policy = id(new PhabricatorPolicy())
+ ->setRules(
+ array(
+ array(
+ 'action' => PhabricatorPolicy::ACTION_ALLOW,
+ 'rule' => 'PhabricatorPolicyRuleUsers',
+ 'value' => array($user_a->getPHID()),
+ ),
+ ))
+ ->save();
+
+ $task = ManiphestTask::initializeNewTask($author);
+ $task->setViewPolicy($policy->getPHID());
+ $task->save();
+
+ $can_a_view = PhabricatorPolicyFilter::hasCapability(
+ $user_a,
+ $task,
+ PhabricatorPolicyCapability::CAN_VIEW);
+
+ $this->assertEqual(true, $can_a_view);
+
+ $can_b_view = PhabricatorPolicyFilter::hasCapability(
+ $user_b,
+ $task,
+ PhabricatorPolicyCapability::CAN_VIEW);
+
+ $this->assertEqual(false, $can_b_view);
+ }
+
+ public function testCustomPolicyRuleAdministrators() {
+ $user_a = $this->generateNewTestUser();
+ $user_a->setIsAdmin(true)->save();
+ $user_b = $this->generateNewTestUser();
+ $author = $this->generateNewTestUser();
+
+ $policy = id(new PhabricatorPolicy())
+ ->setRules(
+ array(
+ array(
+ 'action' => PhabricatorPolicy::ACTION_ALLOW,
+ 'rule' => 'PhabricatorPolicyRuleAdministrators',
+ 'value' => null,
+ ),
+ ))
+ ->save();
+
+ $task = ManiphestTask::initializeNewTask($author);
+ $task->setViewPolicy($policy->getPHID());
+ $task->save();
+
+ $can_a_view = PhabricatorPolicyFilter::hasCapability(
+ $user_a,
+ $task,
+ PhabricatorPolicyCapability::CAN_VIEW);
+
+ $this->assertEqual(true, $can_a_view);
+
+ $can_b_view = PhabricatorPolicyFilter::hasCapability(
+ $user_b,
+ $task,
+ PhabricatorPolicyCapability::CAN_VIEW);
+
+ $this->assertEqual(false, $can_b_view);
+ }
+
+ public function testCustomPolicyRuleLunarPhase() {
+ $user_a = $this->generateNewTestUser();
+ $author = $this->generateNewTestUser();
+
+ $policy = id(new PhabricatorPolicy())
+ ->setRules(
+ array(
+ array(
+ 'action' => PhabricatorPolicy::ACTION_ALLOW,
+ 'rule' => 'PhabricatorPolicyRuleLunarPhase',
+ 'value' => 'new',
+ ),
+ ))
+ ->save();
+
+ $task = ManiphestTask::initializeNewTask($author);
+ $task->setViewPolicy($policy->getPHID());
+ $task->save();
+
+ $time_a = PhabricatorTime::pushTime(934354800, 'UTC');
+
+ $can_a_view = PhabricatorPolicyFilter::hasCapability(
+ $user_a,
+ $task,
+ PhabricatorPolicyCapability::CAN_VIEW);
+ $this->assertEqual(true, $can_a_view);
+
+ unset($time_a);
+
+
+ $time_b = PhabricatorTime::pushTime(1116745200, 'UTC');
+
+ $can_a_view = PhabricatorPolicyFilter::hasCapability(
+ $user_a,
+ $task,
+ PhabricatorPolicyCapability::CAN_VIEW);
+ $this->assertEqual(false, $can_a_view);
+
+ unset($time_b);
+ }
+
}
Index: src/applications/policy/__tests__/PhabricatorPolicyTestObject.php
===================================================================
--- src/applications/policy/__tests__/PhabricatorPolicyTestObject.php
+++ src/applications/policy/__tests__/PhabricatorPolicyTestObject.php
@@ -10,6 +10,10 @@
private $policies = array();
private $automaticCapabilities = array();
+ public function getPHID() {
+ return null;
+ }
+
public function getCapabilities() {
return $this->capabilities;
}
Index: src/applications/policy/application/PhabricatorApplicationPolicy.php
===================================================================
--- src/applications/policy/application/PhabricatorApplicationPolicy.php
+++ src/applications/policy/application/PhabricatorApplicationPolicy.php
@@ -15,6 +15,7 @@
'/policy/' => array(
'explain/(?P<phid>[^/]+)/(?P<capability>[^/]+)/'
=> 'PhabricatorPolicyExplainController',
+ 'edit/(?:(?P<phid>[^/]+)/)?' => 'PhabricatorPolicyEditController',
),
);
}
Index: src/applications/policy/capability/PhabricatorPolicyCapability.php
===================================================================
--- /dev/null
+++ src/applications/policy/capability/PhabricatorPolicyCapability.php
@@ -0,0 +1,72 @@
+<?php
+
+abstract class PhabricatorPolicyCapability extends Phobject {
+
+ const CAN_VIEW = 'view';
+ const CAN_EDIT = 'edit';
+ const CAN_JOIN = 'join';
+
+ /**
+ * Get the unique key identifying this capability. This key must be globally
+ * unique. Application capabilities should be namespaced. For example:
+ *
+ * application.create
+ *
+ * @return string Globally unique capability key.
+ */
+ abstract public function getCapabilityKey();
+
+
+ /**
+ * Return a human-readable descriptive name for this capability, like
+ * "Can View".
+ *
+ * @return string Human-readable name describing the capability.
+ */
+ abstract public function getCapabilityName();
+
+
+ /**
+ * Return a human-readable string describing what not having this capability
+ * prevents the user from doing. For example:
+ *
+ * - You do not have permission to edit this object.
+ * - You do not have permission to create new tasks.
+ *
+ * @return string Human-readable name describing what failing a check for this
+ * capability prevents the user from doing.
+ */
+ public function describeCapabilityRejection() {
+ return null;
+ }
+
+ /**
+ * Can this capability be set to "public"? Broadly, this is only appropriate
+ * for view and view-related policies.
+ *
+ * @return bool True to allow the "public" policy. Returns false by default.
+ */
+ public function shouldAllowPublicPolicySetting() {
+ return false;
+ }
+
+ final public static function getCapabilityByKey($key) {
+ return idx(self::getCapabilityMap(), $key);
+ }
+
+ final public static function getCapabilityMap() {
+ static $map;
+ if ($map === null) {
+ $capabilities = id(new PhutilSymbolLoader())
+ ->setAncestorClass(__CLASS__)
+ ->loadObjects();
+
+ $map = mpull($capabilities, null, 'getCapabilityKey');
+ }
+
+ return $map;
+ }
+
+}
+
+
Index: src/applications/policy/capability/PhabricatorPolicyCapabilityCanEdit.php
===================================================================
--- /dev/null
+++ src/applications/policy/capability/PhabricatorPolicyCapabilityCanEdit.php
@@ -0,0 +1,18 @@
+<?php
+
+final class PhabricatorPolicyCapabilityCanEdit
+ extends PhabricatorPolicyCapability {
+
+ public function getCapabilityKey() {
+ return self::CAN_EDIT;
+ }
+
+ public function getCapabilityName() {
+ return pht('Can Edit');
+ }
+
+ public function describeCapabilityRejection() {
+ return pht('You do not have permission to edit this object.');
+ }
+
+}
Index: src/applications/policy/capability/PhabricatorPolicyCapabilityCanJoin.php
===================================================================
--- /dev/null
+++ src/applications/policy/capability/PhabricatorPolicyCapabilityCanJoin.php
@@ -0,0 +1,18 @@
+<?php
+
+final class PhabricatorPolicyCapabilityCanJoin
+ extends PhabricatorPolicyCapability {
+
+ public function getCapabilityKey() {
+ return self::CAN_JOIN;
+ }
+
+ public function getCapabilityName() {
+ return pht('Can Join');
+ }
+
+ public function describeCapabilityRejection() {
+ return pht('You do not have permission to join this object.');
+ }
+
+}
Index: src/applications/policy/capability/PhabricatorPolicyCapabilityCanView.php
===================================================================
--- /dev/null
+++ src/applications/policy/capability/PhabricatorPolicyCapabilityCanView.php
@@ -0,0 +1,22 @@
+<?php
+
+final class PhabricatorPolicyCapabilityCanView
+ extends PhabricatorPolicyCapability {
+
+ public function getCapabilityKey() {
+ return self::CAN_VIEW;
+ }
+
+ public function getCapabilityName() {
+ return pht('Can View');
+ }
+
+ public function describeCapabilityRejection() {
+ return pht('You do not have permission to view this object.');
+ }
+
+ public function shouldAllowPublicPolicySetting() {
+ return true;
+ }
+
+}
Index: src/applications/policy/constants/PhabricatorPolicies.php
===================================================================
--- src/applications/policy/constants/PhabricatorPolicies.php
+++ src/applications/policy/constants/PhabricatorPolicies.php
@@ -7,4 +7,19 @@
const POLICY_ADMIN = 'admin';
const POLICY_NOONE = 'no-one';
+ /**
+ * Returns the most public policy this install's configuration permits.
+ * This is either "public" (if available) or "all users" (if not).
+ *
+ * @return const Most open working policy constant.
+ */
+ public static function getMostOpenPolicy() {
+ if (PhabricatorEnv::getEnvConfig('policy.allow-public')) {
+ return PhabricatorPolicies::POLICY_PUBLIC;
+ } else {
+ return PhabricatorPolicies::POLICY_USER;
+ }
+ }
+
+
}
Index: src/applications/policy/constants/PhabricatorPolicyCapability.php
===================================================================
--- src/applications/policy/constants/PhabricatorPolicyCapability.php
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-final class PhabricatorPolicyCapability extends PhabricatorPolicyConstants {
-
- const CAN_VIEW = 'view';
- const CAN_EDIT = 'edit';
- const CAN_JOIN = 'join';
-
-}
Index: src/applications/policy/constants/PhabricatorPolicyType.php
===================================================================
--- src/applications/policy/constants/PhabricatorPolicyType.php
+++ src/applications/policy/constants/PhabricatorPolicyType.php
@@ -3,13 +3,15 @@
final class PhabricatorPolicyType extends PhabricatorPolicyConstants {
const TYPE_GLOBAL = 'global';
+ const TYPE_CUSTOM = 'custom';
const TYPE_PROJECT = 'project';
const TYPE_MASKED = 'masked';
public static function getPolicyTypeOrder($type) {
static $map = array(
self::TYPE_GLOBAL => 0,
- self::TYPE_PROJECT => 1,
+ self::TYPE_CUSTOM => 1,
+ self::TYPE_PROJECT => 2,
self::TYPE_MASKED => 9,
);
return idx($map, $type, 9);
@@ -18,9 +20,11 @@
public static function getPolicyTypeName($type) {
switch ($type) {
case self::TYPE_GLOBAL:
- return pht('Global Policies');
+ return pht('Basic Policies');
+ case self::TYPE_CUSTOM:
+ return pht('Advanced');
case self::TYPE_PROJECT:
- return pht('Members of Project');
+ return pht('Members of Project...');
case self::TYPE_MASKED:
default:
return pht('Other Policies');
Index: src/applications/policy/controller/PhabricatorPolicyEditController.php
===================================================================
--- /dev/null
+++ src/applications/policy/controller/PhabricatorPolicyEditController.php
@@ -0,0 +1,219 @@
+<?php
+
+final class PhabricatorPolicyEditController
+ extends PhabricatorPolicyController {
+
+ private $phid;
+
+ public function willProcessRequest(array $data) {
+ $this->phid = idx($data, 'phid');
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $action_options = array(
+ PhabricatorPolicy::ACTION_ALLOW => pht('Allow'),
+ PhabricatorPolicy::ACTION_DENY => pht('Deny'),
+ );
+
+ $rules = id(new PhutilSymbolLoader())
+ ->setAncestorClass('PhabricatorPolicyRule')
+ ->loadObjects();
+ $rules = msort($rules, 'getRuleOrder');
+
+ $default_rule = array(
+ 'action' => head_key($action_options),
+ 'rule' => head_key($rules),
+ 'value' => null,
+ );
+
+ if ($this->phid) {
+ $policies = id(new PhabricatorPolicyQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($this->phid))
+ ->execute();
+ if (!$policies) {
+ return new Aphront404Response();
+ }
+ $policy = head($policies);
+ } else {
+ $policy = id(new PhabricatorPolicy())
+ ->setRules(array($default_rule))
+ ->setDefaultAction(PhabricatorPolicy::ACTION_DENY);
+ }
+
+ $root_id = celerity_generate_unique_node_id();
+
+ $default_action = $policy->getDefaultAction();
+ $rule_data = $policy->getRules();
+
+ $errors = array();
+ if ($request->isFormPost()) {
+ $data = $request->getStr('rules');
+ $data = @json_decode($data, true);
+ if (!is_array($data)) {
+ throw new Exception("Failed to JSON decode rule data!");
+ }
+
+ $rule_data = array();
+ foreach ($data as $rule) {
+ $action = idx($rule, 'action');
+ switch ($action) {
+ case 'allow':
+ case 'deny':
+ break;
+ default:
+ throw new Exception("Invalid action '{$action}'!");
+ }
+
+ $rule_class = idx($rule, 'rule');
+ if (empty($rules[$rule_class])) {
+ throw new Exception("Invalid rule class '{$rule_class}'!");
+ }
+
+ $rule_obj = $rules[$rule_class];
+
+ $value = $rule_obj->getValueForStorage(idx($rule, 'value'));
+
+ $rule_data[] = array(
+ 'action' => $action,
+ 'rule' => $rule_class,
+ 'value' => $value,
+ );
+ }
+
+ // Filter out nonsense rules, like a "users" rule without any users
+ // actually specified.
+ $valid_rules = array();
+ foreach ($rule_data as $rule) {
+ $rule_class = $rule['rule'];
+ if ($rules[$rule_class]->ruleHasEffect($rule['value'])) {
+ $valid_rules[] = $rule;
+ }
+ }
+
+ if (!$valid_rules) {
+ $errors[] = pht('None of these policy rules have any effect.');
+ }
+
+ // NOTE: Policies are immutable once created, and we always create a new
+ // policy here. If we didn't, we would need to lock this endpoint down,
+ // as users could otherwise just go edit the policies of objects with
+ // custom policies.
+
+ if (!$errors) {
+ $new_policy = new PhabricatorPolicy();
+ $new_policy->setRules($valid_rules);
+ $new_policy->setDefaultAction($request->getStr('default'));
+ $new_policy->save();
+
+ $data = array(
+ 'phid' => $new_policy->getPHID(),
+ 'info' => array(
+ 'name' => $new_policy->getName(),
+ 'full' => $new_policy->getName(),
+ 'icon' => $new_policy->getIcon(),
+ ),
+ );
+
+ return id(new AphrontAjaxResponse())->setContent($data);
+ }
+ }
+
+ // Convert rule values to display format (for example, expanding PHIDs
+ // into tokens).
+ foreach ($rule_data as $key => $rule) {
+ $rule_data[$key]['value'] = $rules[$rule['rule']]->getValueForDisplay(
+ $viewer,
+ $rule['value']);
+ }
+
+ $default_select = AphrontFormSelectControl::renderSelectTag(
+ $default_action,
+ $action_options,
+ array(
+ 'name' => 'default',
+ ));
+
+ if ($errors) {
+ $errors = id(new AphrontErrorView())
+ ->setErrors($errors);
+ }
+
+ $form = id(new PHUIFormLayoutView())
+ ->appendChild($errors)
+ ->appendChild(
+ javelin_tag(
+ 'input',
+ array(
+ 'type' => 'hidden',
+ 'name' => 'rules',
+ 'sigil' => 'rules',
+ )))
+ ->appendChild(
+ id(new AphrontFormInsetView())
+ ->setTitle(pht('Rules'))
+ ->setRightButton(
+ javelin_tag(
+ 'a',
+ array(
+ 'href' => '#',
+ 'class' => 'button green',
+ 'sigil' => 'create-rule',
+ 'mustcapture' => true
+ ),
+ pht('New Rule')))
+ ->setDescription(
+ pht('These rules are processed in order.'))
+ ->setContent(javelin_tag(
+ 'table',
+ array(
+ 'sigil' => 'rules',
+ 'class' => 'policy-rules-table'
+ ),
+ '')))
+ ->appendChild(
+ id(new AphrontFormMarkupControl())
+ ->setLabel(pht('If No Rules Match'))
+ ->setValue(pht(
+ "%s all other users.",
+ $default_select)));
+
+ $form = phutil_tag(
+ 'div',
+ array(
+ 'id' => $root_id,
+ ),
+ $form);
+
+ $rule_options = mpull($rules, 'getRuleDescription');
+ $type_map = mpull($rules, 'getValueControlType');
+ $templates = mpull($rules, 'getValueControlTemplate');
+
+ require_celerity_resource('policy-edit-css');
+ Javelin::initBehavior(
+ 'policy-rule-editor',
+ array(
+ 'rootID' => $root_id,
+ 'actions' => $action_options,
+ 'rules' => $rule_options,
+ 'types' => $type_map,
+ 'templates' => $templates,
+ 'data' => $rule_data,
+ 'defaultRule' => $default_rule,
+ ));
+
+ $dialog = id(new AphrontDialogView())
+ ->setWidth(AphrontDialogView::WIDTH_FULL)
+ ->setUser($viewer)
+ ->setTitle(pht('Edit Policy'))
+ ->appendChild($form)
+ ->addSubmitButton(pht('Save Policy'))
+ ->addCancelButton('#');
+
+ return id(new AphrontDialogResponse())->setDialog($dialog);
+ }
+
+}
Index: src/applications/policy/controller/PhabricatorPolicyExplainController.php
===================================================================
--- src/applications/policy/controller/PhabricatorPolicyExplainController.php
+++ src/applications/policy/controller/PhabricatorPolicyExplainController.php
@@ -45,9 +45,17 @@
->executeOne();
$object_uri = $handle->getURI();
- $explanation = $policy->getExplanation($capability);
+ $explanation = PhabricatorPolicy::getPolicyExplanation(
+ $viewer,
+ $policy->getPHID());
+
$auto_info = (array)$object->describeAutomaticCapability($capability);
+ $auto_info = array_merge(
+ array($explanation),
+ $auto_info);
+ $auto_info = array_filter($auto_info);
+
foreach ($auto_info as $key => $info) {
$auto_info[$key] = phutil_tag('li', array(), $info);
}
@@ -55,15 +63,26 @@
$auto_info = phutil_tag('ul', array(), $auto_info);
}
+ $capability_name = $capability;
+ $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
+ if ($capobj) {
+ $capability_name = $capobj->getCapabilityName();
+ }
+
$content = array(
- $explanation,
+ pht('Users with the "%s" capability:', $capability_name),
$auto_info,
);
+ $object_name = pht(
+ '%s %s',
+ $handle->getTypeName(),
+ $handle->getObjectName());
+
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setClass('aphront-access-dialog')
- ->setTitle(pht('Policy Details'))
+ ->setTitle(pht('Policy Details: %s', $object_name))
->appendChild($content)
->addCancelButton($object_uri, pht('Done'));
Index: src/applications/policy/exception/PhabricatorPolicyException.php
===================================================================
--- src/applications/policy/exception/PhabricatorPolicyException.php
+++ src/applications/policy/exception/PhabricatorPolicyException.php
@@ -2,8 +2,38 @@
final class PhabricatorPolicyException extends Exception {
+ private $title;
+ private $rejection;
+ private $capabilityName;
private $moreInfo = array();
+ public function setTitle($title) {
+ $this->title = $title;
+ return $this;
+ }
+
+ public function getTitle() {
+ return $this->title;
+ }
+
+ public function setCapabilityName($capability_name) {
+ $this->capabilityName = $capability_name;
+ return $this;
+ }
+
+ public function getCapabilityName() {
+ return $this->capabilityName;
+ }
+
+ public function setRejection($rejection) {
+ $this->rejection = $rejection;
+ return $this;
+ }
+
+ public function getRejection() {
+ return $this->rejection;
+ }
+
public function setMoreInfo(array $more_info) {
$this->moreInfo = $more_info;
return $this;
Index: src/applications/policy/filter/PhabricatorPolicy.php
===================================================================
--- src/applications/policy/filter/PhabricatorPolicy.php
+++ /dev/null
@@ -1,230 +0,0 @@
-<?php
-
-final class PhabricatorPolicy {
-
- private $phid;
- private $name;
- private $type;
- private $href;
- private $icon;
-
- public static function newFromPolicyAndHandle(
- $policy_identifier,
- PhabricatorObjectHandle $handle = null) {
-
- $is_global = PhabricatorPolicyQuery::isGlobalPolicy($policy_identifier);
- if ($is_global) {
- return PhabricatorPolicyQuery::getGlobalPolicy($policy_identifier);
- }
-
- if (!$handle) {
- throw new Exception(
- "Policy identifier is an object PHID ('{$policy_identifier}'), but no ".
- "object handle was provided. A handle must be provided for object ".
- "policies.");
- }
-
- $handle_phid = $handle->getPHID();
- if ($policy_identifier != $handle_phid) {
- throw new Exception(
- "Policy identifier is an object PHID ('{$policy_identifier}'), but ".
- "the provided handle has a different PHID ('{$handle_phid}'). The ".
- "handle must correspond to the policy identifier.");
- }
-
- $policy = id(new PhabricatorPolicy())
- ->setPHID($policy_identifier)
- ->setHref($handle->getURI());
-
- $phid_type = phid_get_type($policy_identifier);
- switch ($phid_type) {
- case PhabricatorProjectPHIDTypeProject::TYPECONST:
- $policy->setType(PhabricatorPolicyType::TYPE_PROJECT);
- $policy->setName($handle->getName());
- break;
- default:
- $policy->setType(PhabricatorPolicyType::TYPE_MASKED);
- $policy->setName($handle->getFullName());
- break;
- }
-
- return $policy;
- }
-
- public function setType($type) {
- $this->type = $type;
- return $this;
- }
-
- public function getType() {
- return $this->type;
- }
-
- public function setName($name) {
- $this->name = $name;
- return $this;
- }
-
- public function getName() {
- return $this->name;
- }
-
- public function setPHID($phid) {
- $this->phid = $phid;
- return $this;
- }
-
- public function getPHID() {
- return $this->phid;
- }
-
- public function setHref($href) {
- $this->href = $href;
- return $this;
- }
-
- public function getHref() {
- return $this->href;
- }
-
- public function getIcon() {
- switch ($this->getType()) {
- case PhabricatorPolicyType::TYPE_GLOBAL:
- static $map = array(
- PhabricatorPolicies::POLICY_PUBLIC => 'policy-public',
- PhabricatorPolicies::POLICY_USER => 'policy-all',
- PhabricatorPolicies::POLICY_ADMIN => 'policy-admin',
- PhabricatorPolicies::POLICY_NOONE => 'policy-noone',
- );
- return idx($map, $this->getPHID(), 'policy-unknown');
- break;
- case PhabricatorPolicyType::TYPE_PROJECT:
- return 'policy-project';
- break;
- case PhabricatorPolicyType::TYPE_MASKED:
- return 'policy-custom';
- break;
- default:
- return 'policy-unknown';
- break;
- }
- }
-
- public function getSortKey() {
- return sprintf(
- '%02d%s',
- PhabricatorPolicyType::getPolicyTypeOrder($this->getType()),
- $this->getSortName());
- }
-
- private function getSortName() {
- if ($this->getType() == PhabricatorPolicyType::TYPE_GLOBAL) {
- static $map = array(
- PhabricatorPolicies::POLICY_PUBLIC => 0,
- PhabricatorPolicies::POLICY_USER => 1,
- PhabricatorPolicies::POLICY_ADMIN => 2,
- PhabricatorPolicies::POLICY_NOONE => 3,
- );
- return idx($map, $this->getPHID());
- }
- return $this->getName();
- }
-
- public function getExplanation($capability) {
- switch ($capability) {
- case PhabricatorPolicyCapability::CAN_VIEW:
- switch ($this->getPHID()) {
- case PhabricatorPolicies::POLICY_PUBLIC:
- return pht('Visible to the entire internet.');
- case PhabricatorPolicies::POLICY_USER:
- return pht('Visible to all logged in users.');
- case PhabricatorPolicies::POLICY_ADMIN:
- return pht('Visible to all administrators.');
- case PhabricatorPolicies::POLICY_NOONE:
- return pht('Not visible to anyone by default.');
- }
-
- switch ($this->getType()) {
- case PhabricatorPolicyType::TYPE_PROJECT:
- return pht(
- 'Visible to members of the project "%s".',
- $this->getName());
- case PhabricatorPolicyType::TYPE_MASKED:
- return pht('Other: %s', $this->getName());
- }
- break;
- case PhabricatorPolicyCapability::CAN_EDIT:
- switch ($this->getPHID()) {
- case PhabricatorPolicies::POLICY_USER:
- return pht('Editable by all logged in users.');
- case PhabricatorPolicies::POLICY_ADMIN:
- return pht('Editable by all administrators.');
- case PhabricatorPolicies::POLICY_NOONE:
- return pht('Not editable by default.');
- }
-
- switch ($this->getType()) {
- case PhabricatorPolicyType::TYPE_PROJECT:
- return pht(
- 'Editable by members of the project "%s".',
- $this->getName());
- case PhabricatorPolicyType::TYPE_MASKED:
- return pht('Other: %s', $this->getName());
- }
- break;
- }
-
-
- return pht('?');
- }
-
- public function getFullName() {
- switch ($this->getType()) {
- case PhabricatorPolicyType::TYPE_PROJECT:
- return pht('Project: %s', $this->getName());
- case PhabricatorPolicyType::TYPE_MASKED:
- return pht('Other: %s', $this->getName());
- default:
- return $this->getName();
- }
- }
-
- public function renderDescription($icon=false) {
- $img = null;
- if ($icon) {
- $img = id(new PHUIIconView())
- ->setSpriteSheet(PHUIIconView::SPRITE_STATUS)
- ->setSpriteIcon($this->getIcon());
- }
-
- if ($this->getHref()) {
- $desc = phutil_tag(
- 'a',
- array(
- 'href' => $this->getHref(),
- 'class' => 'policy-link',
- ),
- array(
- $img,
- $this->getName(),
- ));
- } else {
- if ($img) {
- $desc = array($img, $this->getName());
- } else {
- $desc = $this->getName();
- }
- }
-
- switch ($this->getType()) {
- case PhabricatorPolicyType::TYPE_PROJECT:
- return pht('%s (Project)', $desc);
- case PhabricatorPolicyType::TYPE_MASKED:
- return pht(
- '%s (You do not have permission to view policy details.)',
- $desc);
- default:
- return $desc;
- }
- }
-}
Index: src/applications/policy/filter/PhabricatorPolicyFilter.php
===================================================================
--- src/applications/policy/filter/PhabricatorPolicyFilter.php
+++ src/applications/policy/filter/PhabricatorPolicyFilter.php
@@ -7,6 +7,7 @@
private $capabilities;
private $raisePolicyExceptions;
private $userProjects;
+ private $customPolicies = array();
public static function mustRetainCapability(
PhabricatorUser $user,
@@ -85,6 +86,7 @@
}
$need_projects = array();
+ $need_policies = array();
foreach ($objects as $key => $object) {
$object_capabilities = $object->getCapabilities();
foreach ($capabilities as $capability) {
@@ -99,9 +101,17 @@
if ($type == PhabricatorProjectPHIDTypeProject::TYPECONST) {
$need_projects[$policy] = $policy;
}
+
+ if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
+ $need_policies[$policy] = $policy;
+ }
}
}
+ if ($need_policies) {
+ $this->loadCustomPolicies(array_keys($need_policies));
+ }
+
// If we need projects, check if any of the projects we need are also the
// objects we're filtering. Because of how project rules work, this is a
// common case.
@@ -173,9 +183,10 @@
$policy = PhabricatorPolicies::POLICY_USER;
}
- // If the object is set to "public" but the capability is anything other
- // than "view", restrict the policy to "user".
- if ($capability != PhabricatorPolicyCapability::CAN_VIEW) {
+ // If the object is set to "public" but the capability is not a public
+ // capability, restrict the policy to "user".
+ $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
+ if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {
$policy = PhabricatorPolicies::POLICY_USER;
}
}
@@ -224,6 +235,12 @@
} else {
$this->rejectObject($object, $policy, $capability);
}
+ } else if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
+ if ($this->checkCustomPolicy($policy)) {
+ return true;
+ } else {
+ $this->rejectObject($object, $policy, $capability);
+ }
} else {
// Reject objects with unknown policies.
$this->rejectObject($object, false, $capability);
@@ -233,7 +250,7 @@
return false;
}
- private function rejectImpossiblePolicy(
+ public function rejectObject(
PhabricatorPolicyInterface $object,
$policy,
$capability) {
@@ -242,146 +259,138 @@
return;
}
- switch ($capability) {
- case PhabricatorPolicyCapability::CAN_VIEW:
- $message = pht("This object has an impossible view policy.");
- break;
- case PhabricatorPolicyCapability::CAN_EDIT:
- $message = pht("This object has an impossible edit policy.");
- break;
- case PhabricatorPolicyCapability::CAN_JOIN:
- $message = pht("This object has an impossible join policy.");
- break;
- default:
- $message = pht("This object has an impossible policy.");
- break;
+ $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
+ $rejection = null;
+ if ($capobj) {
+ $rejection = $capobj->describeCapabilityRejection();
+ $capability_name = $capobj->getCapabilityName();
+ } else {
+ $capability_name = $capability;
}
- throw new PhabricatorPolicyException($message);
+ if (!$rejection) {
+ // We couldn't find the capability object, or it doesn't provide a
+ // tailored rejection string.
+ $rejection = pht(
+ 'You do not have the required capability ("%s") to do whatever you '.
+ 'are trying to do.',
+ $capability);
+ }
+
+ $more = PhabricatorPolicy::getPolicyExplanation($this->viewer, $policy);
+ $exceptions = $object->describeAutomaticCapability($capability);
+
+ $details = array_filter(array_merge(array($more), (array)$exceptions));
+
+ // NOTE: Not every type of policy object has a real PHID; just load an
+ // empty handle if a real PHID isn't available.
+ $phid = nonempty($object->getPHID(), PhabricatorPHIDConstants::PHID_VOID);
+
+ $handle = id(new PhabricatorHandleQuery())
+ ->setViewer($this->viewer)
+ ->withPHIDs(array($phid))
+ ->executeOne();
+
+ $object_name = pht(
+ '%s %s',
+ $handle->getTypeName(),
+ $handle->getObjectName());
+
+ $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
+ if ($is_serious) {
+ $title = pht(
+ 'Access Denied: %s',
+ $object_name);
+ } else {
+ $title = pht(
+ 'You Shall Not Pass: %s',
+ $object_name);
+ }
+
+ $full_message = pht(
+ '[%s] (%s) %s // %s',
+ $title,
+ $capability_name,
+ $rejection,
+ implode(' ', $details));
+
+ $exception = id(new PhabricatorPolicyException($full_message))
+ ->setTitle($title)
+ ->setRejection($rejection)
+ ->setCapabilityName($capability_name)
+ ->setMoreInfo($details);
+
+ throw $exception;
}
- public function rejectObject(
- PhabricatorPolicyInterface $object,
- $policy,
- $capability) {
+ private function loadCustomPolicies(array $phids) {
+ $viewer = $this->viewer;
+ $viewer_phid = $viewer->getPHID();
- if (!$this->raisePolicyExceptions) {
- return;
+ $custom_policies = id(new PhabricatorPolicyQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($phids)
+ ->execute();
+ $custom_policies = mpull($custom_policies, null, 'getPHID');
+
+
+ $classes = array();
+ $values = array();
+ foreach ($custom_policies as $policy) {
+ foreach ($policy->getCustomRuleClasses() as $class) {
+ $classes[$class] = $class;
+ $values[$class][] = $policy->getCustomRuleValues($class);
+ }
}
- $more = array();
- switch ($capability) {
- case PhabricatorPolicyCapability::CAN_VIEW:
- $message = pht(
- 'This object exists, but you do not have permission to view it.');
- break;
- case PhabricatorPolicyCapability::CAN_EDIT:
- $message = pht('You do not have permission to edit this object.');
- break;
- case PhabricatorPolicyCapability::CAN_JOIN:
- $message = pht('You do not have permission to join this object.');
- break;
- default:
- // TODO: Farm these out to applications?
- $message = pht(
- 'You do not have a required capability ("%s") to do whatever you '.
- 'are trying to do.',
- $capability);
- break;
+ foreach ($classes as $class => $ignored) {
+ $object = newv($class, array());
+ $object->willApplyRules($viewer, array_mergev($values[$class]));
+ $classes[$class] = $object;
}
- switch ($policy) {
- case PhabricatorPolicies::POLICY_PUBLIC:
- // Presumably, this is a bug, so we don't bother specializing the
- // strings.
- $more = pht('This object is public.');
- break;
- case PhabricatorPolicies::POLICY_USER:
- // We always raise this as "log in", so we don't need to specialize.
- $more = pht('This object is available to logged in users.');
- break;
- case PhabricatorPolicies::POLICY_ADMIN:
- switch ($capability) {
- case PhabricatorPolicyCapability::CAN_VIEW:
- $more = pht('Administrators can view this object.');
- break;
- case PhabricatorPolicyCapability::CAN_EDIT:
- $more = pht('Administrators can edit this object.');
- break;
- case PhabricatorPolicyCapability::CAN_JOIN:
- $more = pht('Administrators can join this object.');
- break;
- }
- break;
- case PhabricatorPolicies::POLICY_NOONE:
- switch ($capability) {
- case PhabricatorPolicyCapability::CAN_VIEW:
- $more = pht('By default, no one can view this object.');
- break;
- case PhabricatorPolicyCapability::CAN_EDIT:
- $more = pht('By default, no one can edit this object.');
- break;
- case PhabricatorPolicyCapability::CAN_JOIN:
- $more = pht('By default, no one can join this object.');
- break;
- }
- break;
- default:
- $handle = id(new PhabricatorHandleQuery())
- ->setViewer($this->viewer)
- ->withPHIDs(array($policy))
- ->executeOne();
+ foreach ($custom_policies as $policy) {
+ $policy->attachRuleObjects($classes);
+ }
- $type = phid_get_type($policy);
- if ($type == PhabricatorProjectPHIDTypeProject::TYPECONST) {
- switch ($capability) {
- case PhabricatorPolicyCapability::CAN_VIEW:
- $more = pht(
- 'This object is visible to members of the project "%s".',
- $handle->getFullName());
- break;
- case PhabricatorPolicyCapability::CAN_EDIT:
- $more = pht(
- 'This object can be edited by members of the project "%s".',
- $handle->getFullName());
- break;
- case PhabricatorPolicyCapability::CAN_JOIN:
- $more = pht(
- 'This object can be joined by members of the project "%s".',
- $handle->getFullName());
- break;
- }
- } else if ($type == PhabricatorPeoplePHIDTypeUser::TYPECONST) {
- switch ($capability) {
- case PhabricatorPolicyCapability::CAN_VIEW:
- $more = pht(
- '%s can view this object.',
- $handle->getFullName());
- break;
- case PhabricatorPolicyCapability::CAN_EDIT:
- $more = pht(
- '%s can edit this object.',
- $handle->getFullName());
- break;
- case PhabricatorPolicyCapability::CAN_JOIN:
- $more = pht(
- '%s can join this object.',
- $handle->getFullName());
- break;
- }
- } else {
- $more = pht("This object has an unknown or invalid policy setting.");
- }
+ if (empty($this->customPolicies[$viewer_phid])) {
+ $this->customPolicies[$viewer_phid] = array();
+ }
+
+ $this->customPolicies[$viewer->getPHID()] += $custom_policies;
+ }
+
+ private function checkCustomPolicy($policy_phid) {
+ $viewer = $this->viewer;
+ $viewer_phid = $viewer->getPHID();
+
+ $policy = $this->customPolicies[$viewer_phid][$policy_phid];
+
+ $objects = $policy->getRuleObjects();
+ $action = null;
+ foreach ($policy->getRules() as $rule) {
+ $object = idx($objects, idx($rule, 'rule'));
+ if (!$object) {
+ // Reject, this policy has a bogus rule.
+ return false;
+ }
+
+ // If the user matches this rule, use this action.
+ if ($object->applyRule($viewer, idx($rule, 'value'))) {
+ $action = idx($rule, 'action');
break;
+ }
}
- $more = array_merge(
- array_filter(array($more)),
- array_filter((array)$object->describeAutomaticCapability($capability)));
+ if ($action === null) {
+ $action = $policy->getDefaultAction();
+ }
- $exception = new PhabricatorPolicyException($message);
- $exception->setMoreInfo($more);
+ if ($action === PhabricatorPolicy::ACTION_ALLOW) {
+ return true;
+ }
- throw $exception;
+ return false;
}
+
}
Index: src/applications/policy/interface/PhabricatorPolicyInterface.php
===================================================================
--- src/applications/policy/interface/PhabricatorPolicyInterface.php
+++ src/applications/policy/interface/PhabricatorPolicyInterface.php
@@ -2,6 +2,7 @@
interface PhabricatorPolicyInterface {
+ public function getPHID();
public function getCapabilities();
public function getPolicy($capability);
public function hasAutomaticCapability($capability, PhabricatorUser $viewer);
Index: src/applications/policy/management/PhabricatorPolicyManagementShowWorkflow.php
===================================================================
--- src/applications/policy/management/PhabricatorPolicyManagementShowWorkflow.php
+++ src/applications/policy/management/PhabricatorPolicyManagementShowWorkflow.php
@@ -64,7 +64,8 @@
foreach ($policies as $capability => $policy) {
$console->writeOut(" **%s**\n", $capability);
$console->writeOut(" %s\n", $policy->renderDescription());
- $console->writeOut(" %s\n", $policy->getExplanation($capability));
+ $console->writeOut(" %s\n",
+ PhabricatorPolicy::getPolicyExplanation($viewer, $policy->getPHID()));
$console->writeOut("\n");
$more = (array)$object->describeAutomaticCapability($capability);
Index: src/applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php
===================================================================
--- src/applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php
+++ src/applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php
@@ -1,26 +1,27 @@
<?php
-final class PhabricatorProjectPHIDTypeProject extends PhabricatorPHIDType {
+final class PhabricatorPolicyPHIDTypePolicy
+ extends PhabricatorPHIDType {
- const TYPECONST = 'PROJ';
+ const TYPECONST = 'PLCY';
public function getTypeConstant() {
return self::TYPECONST;
}
public function getTypeName() {
- return pht('Project');
+ return pht('Policy');
}
public function newObject() {
- return new PhabricatorProject();
+ return new PhabricatorPolicy();
}
public function loadObjects(
PhabricatorObjectQuery $query,
array $phids) {
- return id(new PhabricatorProjectQuery())
+ return id(new PhabricatorPolicyQuery())
->setViewer($query->getViewer())
->setParentQuery($query)
->withPHIDs($phids)
@@ -33,18 +34,14 @@
array $objects) {
foreach ($handles as $phid => $handle) {
- $project = $objects[$phid];
+ $policy = $objects[$phid];
- $name = $project->getName();
- $id = $project->getID();
-
- $handle->setName($name);
- $handle->setURI("/project/view/{$id}/");
+ $handle->setName($policy->getName());
+ $handle->setURI($policy->getHref());
}
}
public function canLoadNamedObject($name) {
- // TODO: We should be able to load named projects by hashtag, e.g. "#yolo".
return false;
}
Index: src/applications/policy/query/PhabricatorPolicyQuery.php
===================================================================
--- src/applications/policy/query/PhabricatorPolicyQuery.php
+++ src/applications/policy/query/PhabricatorPolicyQuery.php
@@ -1,17 +1,18 @@
<?php
-final class PhabricatorPolicyQuery extends PhabricatorQuery {
+final class PhabricatorPolicyQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
- private $viewer;
private $object;
+ private $phids;
- public function setViewer(PhabricatorUser $viewer) {
- $this->viewer = $viewer;
+ public function setObject(PhabricatorPolicyInterface $object) {
+ $this->object = $object;
return $this;
}
- public function setObject(PhabricatorPolicyInterface $object) {
- $this->object = $object;
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
return $this;
}
@@ -20,31 +21,19 @@
PhabricatorPolicyInterface $object) {
$results = array();
- $policies = null;
- $global = self::getGlobalPolicies();
-
- $capabilities = $object->getCapabilities();
- foreach ($capabilities as $capability) {
- $policy = $object->getPolicy($capability);
- if (!$policy) {
- continue;
- }
- if (isset($global[$policy])) {
- $results[$capability] = $global[$policy];
- continue;
- }
+ $map = array();
+ foreach ($object->getCapabilities() as $capability) {
+ $map[$capability] = $object->getPolicy($capability);
+ }
- if ($policies === null) {
- // This slightly overfetches data, but it shouldn't generally
- // be a problem.
- $policies = id(new PhabricatorPolicyQuery())
- ->setViewer($viewer)
- ->setObject($object)
- ->execute();
- }
+ $policies = id(new PhabricatorPolicyQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($map)
+ ->execute();
- $results[$capability] = $policies[$policy];
+ foreach ($map as $capability => $phid) {
+ $results[$capability] = $policies[$phid];
}
return $results;
@@ -64,64 +53,61 @@
return $policies;
}
- public function execute() {
- if (!$this->viewer) {
- throw new Exception('Call setViewer() before execute()!');
+ public function loadPage() {
+ if ($this->object && $this->phids) {
+ throw new Exception(
+ "You can not issue a policy query with both setObject() and ".
+ "setPHIDs().");
+ } else if ($this->object) {
+ $phids = $this->loadObjectPolicyPHIDs();
+ } else {
+ $phids = $this->phids;
}
- if (!$this->object) {
- throw new Exception('Call setObject() before execute()!');
- }
-
- $results = $this->getGlobalPolicies();
- if ($this->viewer->getPHID()) {
- $projects = id(new PhabricatorProjectQuery())
- ->setViewer($this->viewer)
- ->withMemberPHIDs(array($this->viewer->getPHID()))
- ->execute();
- if ($projects) {
- foreach ($projects as $project) {
- $results[] = id(new PhabricatorPolicy())
- ->setType(PhabricatorPolicyType::TYPE_PROJECT)
- ->setPHID($project->getPHID())
- ->setHref('/project/view/'.$project->getID().'/')
- ->setName($project->getName());
- }
- }
- }
+ $phids = array_fuse($phids);
- $results = mpull($results, null, 'getPHID');
+ $results = array();
- $other_policies = array();
- $capabilities = $this->object->getCapabilities();
- foreach ($capabilities as $capability) {
- $policy = $this->object->getPolicy($capability);
- if (!$policy) {
- continue;
+ // First, load global policies.
+ foreach ($this->getGlobalPolicies() as $phid => $policy) {
+ if (isset($phids[$phid])) {
+ $results[$phid] = $policy;
+ unset($phids[$phid]);
}
- $other_policies[$policy] = $policy;
}
- // If this install doesn't have "Public" enabled, remove it as an option
- // unless the object already has a "Public" policy. In this case we retain
- // the policy but enforce it as thought it was "All Users".
- $show_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
- if (!$show_public &&
- empty($other_policies[PhabricatorPolicies::POLICY_PUBLIC])) {
- unset($results[PhabricatorPolicies::POLICY_PUBLIC]);
- }
+ // If we still need policies, we're going to have to fetch data. Bucket
+ // the remaining policies into rule-based policies and handle-based
+ // policies.
+ if ($phids) {
+ $rule_policies = array();
+ $handle_policies = array();
+ foreach ($phids as $phid) {
+ $phid_type = phid_get_type($phid);
+ if ($phid_type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
+ $rule_policies[$phid] = $phid;
+ } else {
+ $handle_policies[$phid] = $phid;
+ }
+ }
- $other_policies = array_diff_key($other_policies, $results);
+ if ($handle_policies) {
+ $handles = id(new PhabricatorHandleQuery())
+ ->setViewer($this->getViewer())
+ ->withPHIDs($handle_policies)
+ ->execute();
+ foreach ($handle_policies as $phid) {
+ $results[$phid] = PhabricatorPolicy::newFromPolicyAndHandle(
+ $phid,
+ $handles[$phid]);
+ }
+ }
- if ($other_policies) {
- $handles = id(new PhabricatorHandleQuery())
- ->setViewer($this->viewer)
- ->withPHIDs($other_policies)
- ->execute();
- foreach ($other_policies as $phid) {
- $results[$phid] = PhabricatorPolicy::newFromPolicyAndHandle(
- $phid,
- $handles[$phid]);
+ if ($rule_policies) {
+ $rules = id(new PhabricatorPolicy())->loadAllWhere(
+ 'phid IN (%Ls)',
+ $rule_policies);
+ $results += mpull($rules, null, 'getPHID');
}
}
@@ -160,7 +146,9 @@
$results[$constant] = id(new PhabricatorPolicy())
->setType(PhabricatorPolicyType::TYPE_GLOBAL)
->setPHID($constant)
- ->setName(self::getGlobalPolicyName($constant));
+ ->setName(self::getGlobalPolicyName($constant))
+ ->setShortName(self::getGlobalPolicyShortName($constant))
+ ->makeEphemeral();
}
return $results;
@@ -181,5 +169,59 @@
}
}
+ private static function getGlobalPolicyShortName($policy) {
+ switch ($policy) {
+ case PhabricatorPolicies::POLICY_PUBLIC:
+ return pht('Public');
+ default:
+ return null;
+ }
+ }
+
+ private function loadObjectPolicyPHIDs() {
+ $phids = array();
+ $viewer = $this->getViewer();
+
+ if ($viewer->getPHID()) {
+ $projects = id(new PhabricatorProjectQuery())
+ ->setViewer($viewer)
+ ->withMemberPHIDs(array($viewer->getPHID()))
+ ->execute();
+ foreach ($projects as $project) {
+ $phids[] = $project->getPHID();
+ }
+ }
+
+ $capabilities = $this->object->getCapabilities();
+ foreach ($capabilities as $capability) {
+ $policy = $this->object->getPolicy($capability);
+ if (!$policy) {
+ continue;
+ }
+ $phids[] = $policy;
+ }
+
+ // If this install doesn't have "Public" enabled, don't include it as an
+ // option unless the object already has a "Public" policy. In this case we
+ // retain the policy but enforce it as though it was "All Users".
+ $show_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
+ foreach ($this->getGlobalPolicies() as $phid => $policy) {
+ if ($phid == PhabricatorPolicies::POLICY_PUBLIC) {
+ if (!$show_public) {
+ continue;
+ }
+ }
+ $phids[] = $phid;
+ }
+
+ return $phids;
+ }
+
+ protected function shouldDisablePolicyFiltering() {
+ // Policy filtering of policies is currently perilous and not required by
+ // the application.
+ return true;
+ }
+
}
Index: src/applications/policy/rule/PhabricatorPolicyRule.php
===================================================================
--- /dev/null
+++ src/applications/policy/rule/PhabricatorPolicyRule.php
@@ -0,0 +1,48 @@
+<?php
+
+abstract class PhabricatorPolicyRule {
+
+ const CONTROL_TYPE_TEXT = 'text';
+ const CONTROL_TYPE_SELECT = 'select';
+ const CONTROL_TYPE_TOKENIZER = 'tokenizer';
+ const CONTROL_TYPE_NONE = 'none';
+
+ abstract public function getRuleDescription();
+ abstract public function applyRule(PhabricatorUser $viewer, $value);
+
+ public function willApplyRules(PhabricatorUser $viewer, array $values) {
+ return;
+ }
+
+ public function getValueControlType() {
+ return self::CONTROL_TYPE_TEXT;
+ }
+
+ public function getValueControlTemplate() {
+ return null;
+ }
+
+ public function getRuleOrder() {
+ return 500;
+ }
+
+ public function getValueForStorage($value) {
+ return $value;
+ }
+
+ public function getValueForDisplay(PhabricatorUser $viewer, $value) {
+ return $value;
+ }
+
+ /**
+ * Return true if the given value creates a rule with a meaningful effect.
+ * An example of a rule with no meaningful effect is a "users" rule with no
+ * users specified.
+ *
+ * @return bool True if the value creates a meaningful rule.
+ */
+ public function ruleHasEffect($value) {
+ return true;
+ }
+
+}
Index: src/applications/policy/rule/PhabricatorPolicyRuleAdministrators.php
===================================================================
--- /dev/null
+++ src/applications/policy/rule/PhabricatorPolicyRuleAdministrators.php
@@ -0,0 +1,18 @@
+<?php
+
+final class PhabricatorPolicyRuleAdministrators
+ extends PhabricatorPolicyRule {
+
+ public function getRuleDescription() {
+ return pht('administrators');
+ }
+
+ public function applyRule(PhabricatorUser $viewer, $value) {
+ return $viewer->getIsAdmin();
+ }
+
+ public function getValueControlType() {
+ return self::CONTROL_TYPE_NONE;
+ }
+
+}
Index: src/applications/policy/rule/PhabricatorPolicyRuleLunarPhase.php
===================================================================
--- /dev/null
+++ src/applications/policy/rule/PhabricatorPolicyRuleLunarPhase.php
@@ -0,0 +1,51 @@
+<?php
+
+final class PhabricatorPolicyRuleLunarPhase
+ extends PhabricatorPolicyRule {
+
+ const PHASE_FULL = 'full';
+ const PHASE_NEW = 'new';
+ const PHASE_WAXING = 'waxing';
+ const PHASE_WANING = 'waning';
+
+ public function getRuleDescription() {
+ return pht('when the moon');
+ }
+
+ public function applyRule(PhabricatorUser $viewer, $value) {
+ $moon = new PhutilLunarPhase(PhabricatorTime::getNow());
+
+ switch ($value) {
+ case 'full':
+ return $moon->isFull();
+ case 'new':
+ return $moon->isNew();
+ case 'waxing':
+ return $moon->isWaxing();
+ case 'waning':
+ return $moon->isWaning();
+ }
+
+ return false;
+ }
+
+ public function getValueControlType() {
+ return self::CONTROL_TYPE_SELECT;
+ }
+
+ public function getValueControlTemplate() {
+ return array(
+ 'options' => array(
+ self::PHASE_FULL => pht('is full'),
+ self::PHASE_NEW => pht('is new'),
+ self::PHASE_WAXING => pht('is waxing'),
+ self::PHASE_WANING => pht('is waning'),
+ ),
+ );
+ }
+
+ public function getRuleOrder() {
+ return 1000;
+ }
+
+}
Index: src/applications/policy/rule/PhabricatorPolicyRuleProjects.php
===================================================================
--- /dev/null
+++ src/applications/policy/rule/PhabricatorPolicyRuleProjects.php
@@ -0,0 +1,71 @@
+<?php
+
+final class PhabricatorPolicyRuleProjects
+ extends PhabricatorPolicyRule {
+
+ private $memberships = array();
+
+ public function getRuleDescription() {
+ return pht('members of projects');
+ }
+
+ public function willApplyRules(PhabricatorUser $viewer, array $values) {
+ $values = array_unique(array_filter(array_mergev($values)));
+ if (!$values) {
+ return;
+ }
+
+ $projects = id(new PhabricatorProjectQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withMemberPHIDs(array($viewer->getPHID()))
+ ->withPHIDs($values)
+ ->execute();
+ foreach ($projects as $project) {
+ $this->memberships[$viewer->getPHID()][$project->getPHID()] = true;
+ }
+ }
+
+ public function applyRule(PhabricatorUser $viewer, $value) {
+ foreach ($value as $project_phid) {
+ if (isset($this->memberships[$viewer->getPHID()][$project_phid])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function getValueControlType() {
+ return self::CONTROL_TYPE_TOKENIZER;
+ }
+
+ public function getValueControlTemplate() {
+ return array(
+ 'markup' => new AphrontTokenizerTemplateView(),
+ 'uri' => '/typeahead/common/projects/',
+ 'placeholder' => pht('Type a project name...'),
+ );
+ }
+
+ public function getRuleOrder() {
+ return 200;
+ }
+
+ public function getValueForStorage($value) {
+ PhutilTypeSpec::newFromString('list<string>')->check($value);
+ return array_values($value);
+ }
+
+ public function getValueForDisplay(PhabricatorUser $viewer, $value) {
+ $handles = id(new PhabricatorHandleQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($value)
+ ->execute();
+
+ return mpull($handles, 'getFullName', 'getPHID');
+ }
+
+ public function ruleHasEffect($value) {
+ return (bool)$value;
+ }
+
+}
Index: src/applications/policy/rule/PhabricatorPolicyRuleUsers.php
===================================================================
--- /dev/null
+++ src/applications/policy/rule/PhabricatorPolicyRuleUsers.php
@@ -0,0 +1,57 @@
+<?php
+
+final class PhabricatorPolicyRuleUsers
+ extends PhabricatorPolicyRule {
+
+ public function getRuleDescription() {
+ return pht('users');
+ }
+
+ public function applyRule(PhabricatorUser $viewer, $value) {
+ foreach ($value as $phid) {
+ if ($phid == $viewer->getPHID()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function getValueControlType() {
+ return self::CONTROL_TYPE_TOKENIZER;
+ }
+
+ public function getValueControlTemplate() {
+ return array(
+ 'markup' => new AphrontTokenizerTemplateView(),
+ 'uri' => '/typeahead/common/accounts/',
+ 'placeholder' => pht('Type a user name...'),
+ );
+ }
+
+ public function getRuleOrder() {
+ return 100;
+ }
+
+ public function getValueForStorage($value) {
+ PhutilTypeSpec::newFromString('list<string>')->check($value);
+ return array_values($value);
+ }
+
+ public function getValueForDisplay(PhabricatorUser $viewer, $value) {
+ if (!$value) {
+ return array();
+ }
+
+ $handles = id(new PhabricatorHandleQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($value)
+ ->execute();
+
+ return mpull($handles, 'getFullName', 'getPHID');
+ }
+
+ public function ruleHasEffect($value) {
+ return (bool)$value;
+ }
+
+}
Index: src/applications/policy/storage/PhabricatorPolicy.php
===================================================================
--- /dev/null
+++ src/applications/policy/storage/PhabricatorPolicy.php
@@ -0,0 +1,342 @@
+<?php
+
+final class PhabricatorPolicy
+ extends PhabricatorPolicyDAO
+ implements PhabricatorPolicyInterface {
+
+ const ACTION_ALLOW = 'allow';
+ const ACTION_DENY = 'deny';
+
+ private $name;
+ private $shortName;
+ private $type;
+ private $href;
+ private $icon;
+
+ protected $rules = array();
+ protected $defaultAction = self::ACTION_DENY;
+
+ private $ruleObjects = self::ATTACHABLE;
+
+ public function getConfiguration() {
+ return array(
+ self::CONFIG_AUX_PHID => true,
+ self::CONFIG_SERIALIZATION => array(
+ 'rules' => self::SERIALIZATION_JSON,
+ ),
+ ) + parent::getConfiguration();
+ }
+
+ public function generatePHID() {
+ return PhabricatorPHID::generateNewPHID(
+ PhabricatorPolicyPHIDTypePolicy::TYPECONST);
+ }
+
+ public static function newFromPolicyAndHandle(
+ $policy_identifier,
+ PhabricatorObjectHandle $handle = null) {
+
+ $is_global = PhabricatorPolicyQuery::isGlobalPolicy($policy_identifier);
+ if ($is_global) {
+ return PhabricatorPolicyQuery::getGlobalPolicy($policy_identifier);
+ }
+
+ if (!$handle) {
+ throw new Exception(
+ "Policy identifier is an object PHID ('{$policy_identifier}'), but no ".
+ "object handle was provided. A handle must be provided for object ".
+ "policies.");
+ }
+
+ $handle_phid = $handle->getPHID();
+ if ($policy_identifier != $handle_phid) {
+ throw new Exception(
+ "Policy identifier is an object PHID ('{$policy_identifier}'), but ".
+ "the provided handle has a different PHID ('{$handle_phid}'). The ".
+ "handle must correspond to the policy identifier.");
+ }
+
+ $policy = id(new PhabricatorPolicy())
+ ->setPHID($policy_identifier)
+ ->setHref($handle->getURI());
+
+ $phid_type = phid_get_type($policy_identifier);
+ switch ($phid_type) {
+ case PhabricatorProjectPHIDTypeProject::TYPECONST:
+ $policy->setType(PhabricatorPolicyType::TYPE_PROJECT);
+ $policy->setName($handle->getName());
+ break;
+ case PhabricatorPolicyPHIDTypePolicy::TYPECONST:
+ // TODO: This creates a weird handle-based version of a rule policy.
+ // It behaves correctly, but can't be applied since it doesn't have
+ // any rules. It is used to render transactions, and might need some
+ // cleanup.
+ break;
+ default:
+ $policy->setType(PhabricatorPolicyType::TYPE_MASKED);
+ $policy->setName($handle->getFullName());
+ break;
+ }
+
+ $policy->makeEphemeral();
+
+ return $policy;
+ }
+
+ public function setType($type) {
+ $this->type = $type;
+ return $this;
+ }
+
+ public function getType() {
+ if (!$this->type) {
+ return PhabricatorPolicyType::TYPE_CUSTOM;
+ }
+ return $this->type;
+ }
+
+ public function setName($name) {
+ $this->name = $name;
+ return $this;
+ }
+
+ public function getName() {
+ if (!$this->name) {
+ return pht('Custom Policy');
+ }
+ return $this->name;
+ }
+
+ public function setShortName($short_name) {
+ $this->shortName = $short_name;
+ return $this;
+ }
+
+ public function getShortName() {
+ if ($this->shortName) {
+ return $this->shortName;
+ }
+ return $this->getName();
+ }
+
+ public function setHref($href) {
+ $this->href = $href;
+ return $this;
+ }
+
+ public function getHref() {
+ return $this->href;
+ }
+
+ public function getIcon() {
+ switch ($this->getType()) {
+ case PhabricatorPolicyType::TYPE_GLOBAL:
+ static $map = array(
+ PhabricatorPolicies::POLICY_PUBLIC => 'policy-public',
+ PhabricatorPolicies::POLICY_USER => 'policy-all',
+ PhabricatorPolicies::POLICY_ADMIN => 'policy-admin',
+ PhabricatorPolicies::POLICY_NOONE => 'policy-noone',
+ );
+ return idx($map, $this->getPHID(), 'policy-unknown');
+ break;
+ case PhabricatorPolicyType::TYPE_PROJECT:
+ return 'policy-project';
+ break;
+ case PhabricatorPolicyType::TYPE_CUSTOM:
+ case PhabricatorPolicyType::TYPE_MASKED:
+ return 'policy-custom';
+ break;
+ default:
+ return 'policy-unknown';
+ break;
+ }
+ }
+
+ public function getSortKey() {
+ return sprintf(
+ '%02d%s',
+ PhabricatorPolicyType::getPolicyTypeOrder($this->getType()),
+ $this->getSortName());
+ }
+
+ private function getSortName() {
+ if ($this->getType() == PhabricatorPolicyType::TYPE_GLOBAL) {
+ static $map = array(
+ PhabricatorPolicies::POLICY_PUBLIC => 0,
+ PhabricatorPolicies::POLICY_USER => 1,
+ PhabricatorPolicies::POLICY_ADMIN => 2,
+ PhabricatorPolicies::POLICY_NOONE => 3,
+ );
+ return idx($map, $this->getPHID());
+ }
+ return $this->getName();
+ }
+
+ public static function getPolicyExplanation(
+ PhabricatorUser $viewer,
+ $policy) {
+
+ switch ($policy) {
+ case PhabricatorPolicies::POLICY_PUBLIC:
+ return pht('This object is public.');
+ case PhabricatorPolicies::POLICY_USER:
+ return pht('Logged in users can take this action.');
+ case PhabricatorPolicies::POLICY_ADMIN:
+ return pht('Administrators can take this action.');
+ case PhabricatorPolicies::POLICY_NOONE:
+ return pht('By default, no one can take this action.');
+ default:
+ $handle = id(new PhabricatorHandleQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($policy))
+ ->executeOne();
+
+ $type = phid_get_type($policy);
+ if ($type == PhabricatorProjectPHIDTypeProject::TYPECONST) {
+ return pht(
+ 'Members of the project "%s" can take this action.',
+ $handle->getFullName());
+ } else if ($type == PhabricatorPeoplePHIDTypeUser::TYPECONST) {
+ return pht(
+ '%s can take this action.',
+ $handle->getFullName());
+ } else if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
+ return pht(
+ 'This object has a custom policy controlling who can take this '.
+ 'action.');
+ } else {
+ return pht(
+ 'This object has an unknown or invalid policy setting ("%s").',
+ $policy);
+ }
+ }
+ }
+
+ public function getFullName() {
+ switch ($this->getType()) {
+ case PhabricatorPolicyType::TYPE_PROJECT:
+ return pht('Project: %s', $this->getName());
+ case PhabricatorPolicyType::TYPE_MASKED:
+ return pht('Other: %s', $this->getName());
+ default:
+ return $this->getName();
+ }
+ }
+
+ public function renderDescription($icon=false) {
+ $img = null;
+ if ($icon) {
+ $img = id(new PHUIIconView())
+ ->setSpriteSheet(PHUIIconView::SPRITE_STATUS)
+ ->setSpriteIcon($this->getIcon());
+ }
+
+ if ($this->getHref()) {
+ $desc = phutil_tag(
+ 'a',
+ array(
+ 'href' => $this->getHref(),
+ 'class' => 'policy-link',
+ ),
+ array(
+ $img,
+ $this->getName(),
+ ));
+ } else {
+ if ($img) {
+ $desc = array($img, $this->getName());
+ } else {
+ $desc = $this->getName();
+ }
+ }
+
+ switch ($this->getType()) {
+ case PhabricatorPolicyType::TYPE_PROJECT:
+ return pht('%s (Project)', $desc);
+ case PhabricatorPolicyType::TYPE_CUSTOM:
+ return pht('Custom Policy');
+ case PhabricatorPolicyType::TYPE_MASKED:
+ return pht(
+ '%s (You do not have permission to view policy details.)',
+ $desc);
+ default:
+ return $desc;
+ }
+ }
+
+ /**
+ * Return a list of custom rule classes (concrete subclasses of
+ * @{class:PhabricatorPolicyRule}) this policy uses.
+ *
+ * @return list<string> List of class names.
+ */
+ public function getCustomRuleClasses() {
+ $classes = array();
+
+ foreach ($this->getRules() as $rule) {
+ $class = idx($rule, 'rule');
+ try {
+ if (class_exists($class)) {
+ $classes[$class] = $class;
+ }
+ } catch (Exception $ex) {
+ continue;
+ }
+ }
+
+ return array_keys($classes);
+ }
+
+ /**
+ * Return a list of all values used by a given rule class to implement this
+ * policy. This is used to bulk load data (like project memberships) in order
+ * to apply policy filters efficiently.
+ *
+ * @param string Policy rule classname.
+ * @return list<wild> List of values used in this policy.
+ */
+ public function getCustomRuleValues($rule_class) {
+ $values = array();
+ foreach ($this->getRules() as $rule) {
+ if ($rule['rule'] == $rule_class) {
+ $values[] = $rule['value'];
+ }
+ }
+ return $values;
+ }
+
+ public function attachRuleObjects(array $objects) {
+ $this->ruleObjects = $objects;
+ return $this;
+ }
+
+ public function getRuleObjects() {
+ return $this->assertAttached($this->ruleObjects);
+ }
+
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ );
+ }
+
+ public function getPolicy($capability) {
+ // NOTE: We implement policies only so we can comply with the interface.
+ // The actual query skips them, as enforcing policies on policies seems
+ // perilous and isn't currently required by the application.
+ return PhabricatorPolicies::POLICY_PUBLIC;
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return false;
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return null;
+ }
+
+}
Index: src/applications/policy/storage/PhabricatorPolicyDAO.php
===================================================================
--- /dev/null
+++ src/applications/policy/storage/PhabricatorPolicyDAO.php
@@ -0,0 +1,9 @@
+<?php
+
+abstract class PhabricatorPolicyDAO extends PhabricatorLiskDAO {
+
+ public function getApplicationName() {
+ return 'policy';
+ }
+
+}
Index: src/applications/ponder/controller/PonderQuestionViewController.php
===================================================================
--- src/applications/ponder/controller/PonderQuestionViewController.php
+++ src/applications/ponder/controller/PonderQuestionViewController.php
@@ -48,12 +48,11 @@
->setHeader($question->getTitle());
$actions = $this->buildActionListView($question);
- $properties = $this->buildPropertyListView($question);
+ $properties = $this->buildPropertyListView($question, $actions);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
$crumbs->setActionList($actions);
@@ -130,12 +129,14 @@
}
private function buildPropertyListView(
- PonderQuestion $question) {
+ PonderQuestion $question,
+ PhabricatorActionListView $actions) {
$viewer = $this->getRequest()->getUser();
- $view = id(new PhabricatorPropertyListView())
+ $view = id(new PHUIPropertyListView())
->setUser($viewer)
- ->setObject($question);
+ ->setObject($question)
+ ->setActionList($actions);
$this->loadHandles(array($question->getAuthorPHID()));
@@ -265,12 +266,11 @@
->setHeader($this->getHandle($author_phid)->getFullName());
$actions = $this->buildAnswerActions($answer);
- $properties = $this->buildAnswerProperties($answer);
+ $properties = $this->buildAnswerProperties($answer, $actions);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
$out[] = $object_box;
$details = array();
@@ -339,11 +339,15 @@
return $view;
}
- private function buildAnswerProperties(PonderAnswer $answer) {
+ private function buildAnswerProperties(
+ PonderAnswer $answer,
+ PhabricatorActionListView $actions) {
+
$viewer = $this->getRequest()->getUser();
- $view = id(new PhabricatorPropertyListView())
+ $view = id(new PHUIPropertyListView())
->setUser($viewer)
- ->setObject($answer);
+ ->setObject($answer)
+ ->setActionList($actions);
$view->addProperty(
pht('Created'),
Index: src/applications/ponder/query/PonderQuestionSearchEngine.php
===================================================================
--- src/applications/ponder/query/PonderQuestionSearchEngine.php
+++ src/applications/ponder/query/PonderQuestionSearchEngine.php
@@ -61,10 +61,6 @@
->setViewer($this->requireViewer())
->withPHIDs($phids)
->execute();
- $tokens = mpull($handles, 'getFullName', 'getPHID');
-
- $author_tokens = array_select_keys($tokens, $author_phids);
- $answerer_tokens = array_select_keys($tokens, $answerer_phids);
$form
->appendChild(
@@ -72,13 +68,13 @@
->setDatasource('/typeahead/common/users/')
->setName('authors')
->setLabel(pht('Authors'))
- ->setValue($author_tokens))
+ ->setValue(array_select_keys($handles, $author_phids)))
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/users/')
->setName('answerers')
->setLabel(pht('Answered By'))
- ->setValue($answerer_tokens))
+ ->setValue(array_select_keys($handles, $answerer_phids)))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Status'))
Index: src/applications/project/application/PhabricatorApplicationProject.php
===================================================================
--- src/applications/project/application/PhabricatorApplicationProject.php
+++ src/applications/project/application/PhabricatorApplicationProject.php
@@ -49,4 +49,11 @@
);
}
+ protected function getCustomCapabilities() {
+ return array(
+ ProjectCapabilityCreateProjects::CAPABILITY => array(
+ ),
+ );
+ }
+
}
Index: src/applications/project/capability/ProjectCapabilityCreateProjects.php
===================================================================
--- /dev/null
+++ src/applications/project/capability/ProjectCapabilityCreateProjects.php
@@ -0,0 +1,20 @@
+<?php
+
+final class ProjectCapabilityCreateProjects
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'project.create';
+
+ public function getCapabilityKey() {
+ return self::CAPABILITY;
+ }
+
+ public function getCapabilityName() {
+ return pht('Can Create Projects');
+ }
+
+ public function describeCapabilityRejection() {
+ return pht('You do not have permission to create new projects.');
+ }
+
+}
Index: src/applications/project/controller/PhabricatorProjectController.php
===================================================================
--- src/applications/project/controller/PhabricatorProjectController.php
+++ src/applications/project/controller/PhabricatorProjectController.php
@@ -24,11 +24,16 @@
public function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
+ $can_create = $this->hasApplicationCapability(
+ ProjectCapabilityCreateProjects::CAPABILITY);
+
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('Create Project'))
->setHref($this->getApplicationURI('create/'))
- ->setIcon('create'));
+ ->setIcon('create')
+ ->setWorkflow(!$can_create)
+ ->setDisabled(!$can_create));
return $crumbs;
}
Index: src/applications/project/controller/PhabricatorProjectCreateController.php
===================================================================
--- src/applications/project/controller/PhabricatorProjectCreateController.php
+++ src/applications/project/controller/PhabricatorProjectCreateController.php
@@ -9,8 +9,12 @@
$request = $this->getRequest();
$user = $request->getUser();
+ $this->requireApplicationCapability(
+ ProjectCapabilityCreateProjects::CAPABILITY);
+
$project = new PhabricatorProject();
$project->setAuthorPHID($user->getPHID());
+ $project->attachMemberPHIDs(array());
$profile = new PhabricatorProjectProfile();
$e_name = true;
Index: src/applications/project/controller/PhabricatorProjectMembersEditController.php
===================================================================
--- src/applications/project/controller/PhabricatorProjectMembersEditController.php
+++ src/applications/project/controller/PhabricatorProjectMembersEditController.php
@@ -16,6 +16,7 @@
$project = id(new PhabricatorProjectQuery())
->setViewer($user)
->withIDs(array($this->id))
+ ->needMembers(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
@@ -25,12 +26,8 @@
if (!$project) {
return new Aphront404Response();
}
- $profile = $project->loadProfile();
- if (empty($profile)) {
- $profile = new PhabricatorProjectProfile();
- }
- $member_phids = $project->loadMemberPHIDs();
+ $member_phids = $project->getMemberPHIDs();
$errors = array();
if ($request->isFormPost()) {
Index: src/applications/project/controller/PhabricatorProjectProfileController.php
===================================================================
--- src/applications/project/controller/PhabricatorProjectProfileController.php
+++ src/applications/project/controller/PhabricatorProjectProfileController.php
@@ -5,7 +5,10 @@
private $id;
private $page;
- private $project;
+
+ public function shouldAllowPublic() {
+ return true;
+ }
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
@@ -16,23 +19,18 @@
$request = $this->getRequest();
$user = $request->getUser();
- $query = id(new PhabricatorProjectQuery())
+ $project = id(new PhabricatorProjectQuery())
->setViewer($user)
->withIDs(array($this->id))
- ->needMembers(true);
-
- $project = $query->executeOne();
- $this->project = $project;
+ ->needMembers(true)
+ ->needProfiles(true)
+ ->executeOne();
if (!$project) {
return new Aphront404Response();
}
- $profile = $project->loadProfile();
- if (!$profile) {
- $profile = new PhabricatorProjectProfile();
- }
-
- $picture = $profile->loadProfileImageURI();
+ $profile = $project->getProfile();
+ $picture = $profile->getProfileImageURI();
require_celerity_resource('phabricator-profile-css');
@@ -65,7 +63,7 @@
->setImage($picture);
$actions = $this->buildActionListView($project);
- $properties = $this->buildPropertyListView($project);
+ $properties = $this->buildPropertyListView($project, $actions);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
@@ -74,8 +72,7 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
return $this->buildApplicationPage(
array(
@@ -246,13 +243,16 @@
return $view;
}
- private function buildPropertyListView(PhabricatorProject $project) {
+ private function buildPropertyListView(
+ PhabricatorProject $project,
+ PhabricatorActionListView $actions) {
$request = $this->getRequest();
$viewer = $request->getUser();
- $view = id(new PhabricatorPropertyListView())
+ $view = id(new PHUIPropertyListView())
->setUser($viewer)
- ->setObject($project);
+ ->setObject($project)
+ ->setActionList($actions);
$view->addProperty(
pht('Created'),
Index: src/applications/project/controller/PhabricatorProjectProfileEditController.php
===================================================================
--- src/applications/project/controller/PhabricatorProjectProfileEditController.php
+++ src/applications/project/controller/PhabricatorProjectProfileEditController.php
@@ -22,17 +22,14 @@
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
+ ->needProfiles(true)
->executeOne();
if (!$project) {
return new Aphront404Response();
}
- $profile = $project->loadProfile();
- if (empty($profile)) {
- $profile = new PhabricatorProjectProfile();
- }
-
- $img_src = $profile->loadProfileImageURI();
+ $profile = $project->getProfile();
+ $img_src = $profile->getProfileImageURI();
$options = PhabricatorProjectStatus::getStatusMap();
@@ -110,7 +107,10 @@
$file,
$x = 50,
$y = 50);
+
$profile->setProfileImagePHID($xformed->getPHID());
+ $xformed->attachToObject($user, $project->getPHID());
+
} else {
$e_image = pht('Not Supported');
$errors[] =
Index: src/applications/project/editor/PhabricatorProjectEditor.php
===================================================================
--- src/applications/project/editor/PhabricatorProjectEditor.php
+++ src/applications/project/editor/PhabricatorProjectEditor.php
@@ -213,8 +213,7 @@
$xaction->setOldValue($project->getStatus());
break;
case PhabricatorProjectTransactionType::TYPE_MEMBERS:
- $member_phids = $project->loadMemberPHIDs();
- $project->attachMemberPHIDs($member_phids);
+ $member_phids = $project->getMemberPHIDs();
$old_value = array_values($member_phids);
$xaction->setOldValue($old_value);
Index: src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php
===================================================================
--- src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php
+++ src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php
@@ -39,6 +39,7 @@
$id = $project->getID();
$handle->setName($name);
+ $handle->setObjectName('#'.rtrim($project->getPhrictionSlug(), '/'));
$handle->setURI("/project/view/{$id}/");
}
}
Index: src/applications/project/query/PhabricatorProjectQuery.php
===================================================================
--- src/applications/project/query/PhabricatorProjectQuery.php
+++ src/applications/project/query/PhabricatorProjectQuery.php
@@ -16,6 +16,7 @@
const STATUS_ARCHIVED = 'status-archived';
private $needMembers;
+ private $needProfiles;
public function withIDs(array $ids) {
$this->ids = $ids;
@@ -47,6 +48,11 @@
return $this;
}
+ public function needProfiles($need_profiles) {
+ $this->needProfiles = $need_profiles;
+ return $this;
+ }
+
protected function getPagingColumn() {
return 'name';
}
@@ -113,6 +119,56 @@
return $projects;
}
+ protected function didFilterPage(array $projects) {
+ if ($this->needProfiles) {
+ $profiles = id(new PhabricatorProjectProfile())->loadAllWhere(
+ 'projectPHID IN (%Ls)',
+ mpull($projects, 'getPHID'));
+ $profiles = mpull($profiles, null, 'getProjectPHID');
+
+ $default = null;
+
+ if ($profiles) {
+ $file_phids = mpull($profiles, 'getProfileImagePHID');
+ $files = id(new PhabricatorFileQuery())
+ ->setParentQuery($this)
+ ->setViewer($this->getViewer())
+ ->withPHIDs($file_phids)
+ ->execute();
+ $files = mpull($files, null, 'getPHID');
+ foreach ($profiles as $profile) {
+ $file = idx($files, $profile->getProfileImagePHID());
+ if (!$file) {
+ if (!$default) {
+ $default = PhabricatorFile::loadBuiltin(
+ $this->getViewer(),
+ 'profile.png');
+ }
+ $file = $default;
+ }
+ $profile->attachProfileImageFile($file);
+ }
+ }
+
+ foreach ($projects as $project) {
+ $profile = idx($profiles, $project->getPHID());
+ if (!$profile) {
+ if (!$default) {
+ $default = PhabricatorFile::loadBuiltin(
+ $this->getViewer(),
+ 'profile.png');
+ }
+ $profile = id(new PhabricatorProjectProfile())
+ ->setProjectPHID($project->getPHID())
+ ->attachProfileImageFile($default);
+ }
+ $project->attachProfile($profile);
+ }
+ }
+
+ return $projects;
+ }
+
private function buildWhereClause($conn_r) {
$where = array();
Index: src/applications/project/query/PhabricatorProjectSearchEngine.php
===================================================================
--- src/applications/project/query/PhabricatorProjectSearchEngine.php
+++ src/applications/project/query/PhabricatorProjectSearchEngine.php
@@ -36,11 +36,10 @@
PhabricatorSavedQuery $saved_query) {
$phids = $saved_query->getParameter('memberPHIDs', array());
- $handles = id(new PhabricatorHandleQuery())
+ $member_handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireViewer())
->withPHIDs($phids)
->execute();
- $member_tokens = mpull($handles, 'getFullName', 'getPHID');
$status = $saved_query->getParameter('status');
@@ -50,7 +49,7 @@
->setDatasource('/typeahead/common/users/')
->setName('members')
->setLabel(pht('Members'))
- ->setValue($member_tokens))
+ ->setValue($member_handles))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Status'))
Index: src/applications/project/storage/PhabricatorProject.php
===================================================================
--- src/applications/project/storage/PhabricatorProject.php
+++ src/applications/project/storage/PhabricatorProject.php
@@ -4,7 +4,6 @@
implements PhabricatorPolicyInterface {
protected $name;
- protected $phid;
protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
protected $authorPHID;
protected $subprojectPHIDs = array();
@@ -17,6 +16,7 @@
private $subprojectsNeedUpdate;
private $memberPHIDs = self::ATTACHABLE;
private $sparseMembers = self::ATTACHABLE;
+ private $profile = self::ATTACHABLE;
public function getCapabilities() {
return array(
@@ -96,11 +96,13 @@
PhabricatorProjectPHIDTypeProject::TYPECONST);
}
- public function loadProfile() {
- $profile = id(new PhabricatorProjectProfile())->loadOneWhere(
- 'projectPHID = %s',
- $this->getPHID());
- return $profile;
+ public function getProfile() {
+ return $this->assertAttached($this->profile);
+ }
+
+ public function attachProfile(PhabricatorProjectProfile $profile) {
+ $this->profile = $profile;
+ return $this;
}
public function attachMemberPHIDs(array $phids) {
@@ -112,15 +114,6 @@
return $this->assertAttached($this->memberPHIDs);
}
- public function loadMemberPHIDs() {
- if (!$this->getPHID()) {
- return array();
- }
- return PhabricatorEdgeQuery::loadDestinationPHIDs(
- $this->getPHID(),
- PhabricatorEdgeConfig::TYPE_PROJ_MEMBER);
- }
-
public function setPhrictionSlug($slug) {
// NOTE: We're doing a little magic here and stripping out '/' so that
Index: src/applications/project/storage/PhabricatorProjectProfile.php
===================================================================
--- src/applications/project/storage/PhabricatorProjectProfile.php
+++ src/applications/project/storage/PhabricatorProjectProfile.php
@@ -6,16 +6,19 @@
protected $blurb;
protected $profileImagePHID;
- public function loadProfileImageURI() {
- $src_phid = $this->getProfileImagePHID();
+ private $profileImageFile = self::ATTACHABLE;
- // TODO: (T603) Can we get rid of this and move it to a Query?
- $file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $src_phid);
- if ($file) {
- return $file->getBestURI();
- }
+ public function getProfileImageURI() {
+ return $this->getProfileImageFile()->getBestURI();
+ }
+
+ public function attachProfileImageFile(PhabricatorFile $file) {
+ $this->profileImageFile = $file;
+ return $this;
+ }
- return PhabricatorUser::getDefaultProfileImageURI();
+ public function getProfileImageFile() {
+ return $this->assertAttached($this->profileImageFile);
}
}
Index: src/applications/releeph/conduit/ConduitAPI_releeph_queryrequests_Method.php
===================================================================
--- src/applications/releeph/conduit/ConduitAPI_releeph_queryrequests_Method.php
+++ src/applications/releeph/conduit/ConduitAPI_releeph_queryrequests_Method.php
@@ -46,6 +46,9 @@
foreach ($releephRequests as $releephRequest) {
$branch = $releephRequest->loadReleephBranch();
+ if (!$branch) {
+ continue;
+ }
$request_commit_phid = $releephRequest->getRequestCommitPHID();
$revisionPHID =
$query->getRevisionPHID($request_commit_phid);
Index: src/applications/releeph/controller/branch/ReleephBranchViewController.php
===================================================================
--- src/applications/releeph/controller/branch/ReleephBranchViewController.php
+++ src/applications/releeph/controller/branch/ReleephBranchViewController.php
@@ -160,9 +160,10 @@
->setHref($history_uri)
->setIcon('transcript'));
- $properties = id(new PhabricatorPropertyListView())
+ $properties = id(new PHUIPropertyListView())
->setUser($viewer)
- ->setObject($branch);
+ ->setObject($branch)
+ ->setActionList($actions);
$properties->addProperty(
pht('Branch'),
@@ -170,8 +171,7 @@
return id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
}
}
Index: src/applications/releeph/controller/project/ReleephProjectEditController.php
===================================================================
--- src/applications/releeph/controller/project/ReleephProjectEditController.php
+++ src/applications/releeph/controller/project/ReleephProjectEditController.php
@@ -135,10 +135,7 @@
->withPHIDs($pusher_phids)
->execute();
- $pusher_tokens = array();
- foreach ($pusher_phids as $phid) {
- $pusher_tokens[$phid] = $handles[$phid]->getFullName();
- }
+ $pusher_handles = array_select_keys($handles, $pusher_phids);
$basic_inset = id(new AphrontFormInsetView())
->setTitle(pht('Basics'))
@@ -209,7 +206,7 @@
->setLabel(pht('Pushers'))
->setName('pushers')
->setDatasource('/typeahead/common/users/')
- ->setValue($pusher_tokens));
+ ->setValue($pusher_handles));
$commit_author_inset = $this->buildCommitAuthorInset($commit_author);
Index: src/applications/releeph/controller/project/ReleephProjectViewController.php
===================================================================
--- src/applications/releeph/controller/project/ReleephProjectViewController.php
+++ src/applications/releeph/controller/project/ReleephProjectViewController.php
@@ -220,7 +220,7 @@
->setHref($history_uri)
->setIcon('transcript'));
- $properties = id(new PhabricatorPropertyListView())
+ $properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($project);
@@ -228,6 +228,8 @@
pht('Repository'),
$project->getRepository()->getName());
+ $properties->setActionList($actions);
+
$pushers = $project->getPushers();
if ($pushers) {
$this->loadHandles($pushers);
@@ -238,8 +240,7 @@
return id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
}
}
Index: src/applications/releeph/query/ReleephRequestQuery.php
===================================================================
--- src/applications/releeph/query/ReleephRequestQuery.php
+++ src/applications/releeph/query/ReleephRequestQuery.php
@@ -10,6 +10,7 @@
private $severities;
private $requestorPHIDs;
private $branchIDs;
+ private $revisionPHIDs;
const STATUS_ALL = 'status-all';
const STATUS_OPEN = 'status-open';
@@ -67,22 +68,8 @@
}
public function withRevisionPHIDs(array $revision_phids) {
- $type = PhabricatorEdgeConfig::TYPE_DREV_HAS_COMMIT;
-
- $edges = id(new PhabricatorEdgeQuery())
- ->withSourcePHIDs($revision_phids)
- ->withEdgeTypes(array($type))
- ->execute();
-
- $this->commitToRevMap = array();
-
- foreach ($edges as $revision_phid => $edge) {
- foreach ($edge[$type] as $commitPHID => $item) {
- $this->commitToRevMap[$commitPHID] = $revision_phid;
- }
- }
-
- $this->requestedCommitPHIDs = array_keys($this->commitToRevMap);
+ $this->revisionPHIDs = $revision_phids;
+ return $this;
}
public function loadPage() {
@@ -172,6 +159,31 @@
$this->requestorPHIDs);
}
+ if ($this->revisionPHIDs) {
+ $type = PhabricatorEdgeConfig::TYPE_DREV_HAS_COMMIT;
+
+ $edges = id(new PhabricatorEdgeQuery())
+ ->withSourcePHIDs($this->revisionPHIDs)
+ ->withEdgeTypes(array($type))
+ ->execute();
+
+ $this->commitToRevMap = array();
+ foreach ($edges as $revision_phid => $edge) {
+ foreach ($edge[$type] as $commitPHID => $item) {
+ $this->commitToRevMap[$commitPHID] = $revision_phid;
+ }
+ }
+
+ if (!$this->commitToRevMap) {
+ throw new PhabricatorEmptyQueryException("Malformed Revision Phids");
+ }
+
+ $where[] = qsprintf(
+ $conn_r,
+ 'requestCommitPHID IN (%Ls)',
+ array_keys($this->commitToRevMap));
+ }
+
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
Index: src/applications/releeph/query/ReleephRequestSearchEngine.php
===================================================================
--- src/applications/releeph/query/ReleephRequestSearchEngine.php
+++ src/applications/releeph/query/ReleephRequestSearchEngine.php
@@ -60,11 +60,10 @@
PhabricatorSavedQuery $saved_query) {
$phids = $saved_query->getParameter('requestorPHIDs', array());
- $handles = id(new PhabricatorHandleQuery())
+ $requestor_handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireViewer())
->withPHIDs($phids)
->execute();
- $requestor_tokens = mpull($handles, 'getFullName', 'getPHID');
$form
->appendChild(
@@ -84,7 +83,7 @@
->setDatasource('/typeahead/common/users/')
->setName('requestors')
->setLabel(pht('Requestors'))
- ->setValue($requestor_tokens));
+ ->setValue($requestor_handles));
}
protected function getURI($path) {
Index: src/applications/releeph/storage/ReleephBranch.php
===================================================================
--- src/applications/releeph/storage/ReleephBranch.php
+++ src/applications/releeph/storage/ReleephBranch.php
@@ -3,7 +3,6 @@
final class ReleephBranch extends ReleephDAO
implements PhabricatorPolicyInterface {
- protected $phid;
protected $releephProjectID;
protected $isActive;
protected $createdByUserPHID;
Index: src/applications/releeph/storage/ReleephProject.php
===================================================================
--- src/applications/releeph/storage/ReleephProject.php
+++ src/applications/releeph/storage/ReleephProject.php
@@ -10,7 +10,6 @@
const COMMIT_AUTHOR_FROM_DIFF = 'commit-author-is-from-diff';
const COMMIT_AUTHOR_REQUESTOR = 'commit-author-is-requestor';
- protected $phid;
protected $name;
// Specifying the place to pick from is a requirement for svn, though not
Index: src/applications/releeph/storage/ReleephRequest.php
===================================================================
--- src/applications/releeph/storage/ReleephRequest.php
+++ src/applications/releeph/storage/ReleephRequest.php
@@ -5,7 +5,6 @@
PhabricatorPolicyInterface,
PhabricatorCustomFieldInterface {
- protected $phid;
protected $branchID;
protected $requestUserPHID;
protected $details = array();
@@ -56,6 +55,10 @@
*/
public function getPusherIntent() {
$project = $this->loadReleephProject();
+ if (!$project) {
+ return null;
+ }
+
if (!$project->getPushers()) {
return self::INTENT_WANT;
}
@@ -229,7 +232,10 @@
}
public function loadReleephProject() {
- return $this->loadReleephBranch()->loadReleephProject();
+ $branch = $this->loadReleephBranch();
+ if ($branch) {
+ return $branch->loadReleephProject();
+ }
}
public function loadPhabricatorRepositoryCommit() {
Index: src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php
===================================================================
--- src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php
+++ src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php
@@ -59,10 +59,10 @@
}
if ($project->getSymbolIndexProjects()) {
- $uses = id(new PhabricatorRepositoryArcanistProject())->loadAllWhere(
- 'phid in (%Ls)',
- $project->getSymbolIndexProjects());
- $uses = mpull($uses, 'getName', 'getPHID');
+ $uses = id(new PhabricatorHandleQuery())
+ ->setViewer($user)
+ ->withPHIDs($project->getSymbolIndexProjects())
+ ->execute();
} else {
$uses = array();
}
Index: src/applications/repository/storage/PhabricatorRepository.php
===================================================================
--- src/applications/repository/storage/PhabricatorRepository.php
+++ src/applications/repository/storage/PhabricatorRepository.php
@@ -25,7 +25,6 @@
const TABLE_BADCOMMIT = 'repository_badcommit';
const TABLE_LINTMESSAGE = 'repository_lintmessage';
- protected $phid;
protected $name;
protected $callsign;
protected $uuid;
Index: src/applications/repository/storage/PhabricatorRepositoryArcanistProject.php
===================================================================
--- src/applications/repository/storage/PhabricatorRepositoryArcanistProject.php
+++ src/applications/repository/storage/PhabricatorRepositoryArcanistProject.php
@@ -8,7 +8,6 @@
implements PhabricatorPolicyInterface {
protected $name;
- protected $phid;
protected $repositoryID;
protected $symbolIndexLanguages = array();
Index: src/applications/repository/storage/PhabricatorRepositoryAuditRequest.php
===================================================================
--- src/applications/repository/storage/PhabricatorRepositoryAuditRequest.php
+++ src/applications/repository/storage/PhabricatorRepositoryAuditRequest.php
@@ -16,4 +16,9 @@
) + parent::getConfiguration();
}
+ public function isUser() {
+ $user_type = PhabricatorPeoplePHIDTypeUser::TYPECONST;
+ return (phid_get_type($this->getAuditorPHID()) == $user_type);
+ }
+
}
Index: src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php
===================================================================
--- src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php
+++ src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php
@@ -29,6 +29,7 @@
$rules = id(new HeraldRuleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withContentTypes(array($adapter->getAdapterContentType()))
+ ->withDisabled(false)
->needConditionsAndActions(true)
->needAppliedToPHIDs(array($adapter->getPHID()))
->needValidateAuthors(true)
Index: src/applications/search/controller/PhabricatorSearchController.php
===================================================================
--- src/applications/search/controller/PhabricatorSearchController.php
+++ src/applications/search/controller/PhabricatorSearchController.php
@@ -128,22 +128,18 @@
$author_value = array_select_keys(
$handles,
$query->getParameter('author', array()));
- $author_value = mpull($author_value, 'getFullName', 'getPHID');
$owner_value = array_select_keys(
$handles,
$query->getParameter('owner', array()));
- $owner_value = mpull($owner_value, 'getFullName', 'getPHID');
$subscribers_value = array_select_keys(
$handles,
$query->getParameter('subscribers', array()));
- $subscribers_value = mpull($subscribers_value, 'getFullName', 'getPHID');
$project_value = array_select_keys(
$handles,
$query->getParameter('project', array()));
- $project_value = mpull($project_value, 'getFullName', 'getPHID');
$search_form = new AphrontFormView();
$search_form
Index: src/applications/search/engine/PhabricatorApplicationSearchEngine.php
===================================================================
--- src/applications/search/engine/PhabricatorApplicationSearchEngine.php
+++ src/applications/search/engine/PhabricatorApplicationSearchEngine.php
@@ -254,11 +254,15 @@
*
* @param AphrontRequest Request to read user PHIDs from.
* @param string Key to read in the request.
+ * @param list<const> Other permitted PHID types.
* @return list<phid> List of user PHIDs.
*
* @task read
*/
- protected function readUsersFromRequest(AphrontRequest $request, $key) {
+ protected function readUsersFromRequest(
+ AphrontRequest $request,
+ $key,
+ array $allow_types = array()) {
$list = $request->getArr($key, null);
if ($list === null) {
$list = $request->getStrList($key);
@@ -266,9 +270,14 @@
$phids = array();
$names = array();
+ $allow_types = array_fuse($allow_types);
$user_type = PhabricatorPHIDConstants::PHID_TYPE_USER;
foreach ($list as $item) {
- if (phid_get_type($item) == $user_type) {
+ $type = phid_get_type($item);
+ phlog($type);
+ if ($type == $user_type) {
+ $phids[] = $item;
+ } else if (isset($allow_types[$type])) {
$phids[] = $item;
} else {
$names[] = $item;
@@ -290,6 +299,25 @@
}
+ protected function readBoolFromRequest(
+ AphrontRequest $request,
+ $key) {
+ if (!strlen($request->getStr($key))) {
+ return null;
+ }
+ return $request->getBool($key);
+ }
+
+
+ protected function getBoolFromQuery(PhabricatorSavedQuery $query, $key) {
+ $value = $query->getParameter($key);
+ if ($value === null) {
+ return $value;
+ }
+ return $value ? 'true' : 'false';
+ }
+
+
/* -( Dates )-------------------------------------------------------------- */
Index: src/applications/slowvote/application/PhabricatorApplicationSlowvote.php
===================================================================
--- src/applications/slowvote/application/PhabricatorApplicationSlowvote.php
+++ src/applications/slowvote/application/PhabricatorApplicationSlowvote.php
@@ -50,4 +50,12 @@
);
}
+ public function getCustomCapabilities() {
+ return array(
+ PhabricatorSlowvoteCapabilityDefaultView::CAPABILITY => array(
+ 'caption' => pht('Default view policy for new polls.'),
+ ),
+ );
+ }
+
}
Index: src/applications/slowvote/capability/PhabricatorSlowvoteCapabilityDefaultView.php
===================================================================
--- /dev/null
+++ src/applications/slowvote/capability/PhabricatorSlowvoteCapabilityDefaultView.php
@@ -0,0 +1,20 @@
+<?php
+
+final class PhabricatorSlowvoteCapabilityDefaultView
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'slowvote.default.view';
+
+ public function getCapabilityKey() {
+ return self::CAPABILITY;
+ }
+
+ public function getCapabilityName() {
+ return pht('Default View Policy');
+ }
+
+ public function shouldAllowPublicPolicySetting() {
+ return true;
+ }
+
+}
Index: src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php
===================================================================
--- src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php
+++ src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php
@@ -32,9 +32,7 @@
}
$is_new = false;
} else {
- $poll = id(new PhabricatorSlowvotePoll())
- ->setAuthorPHID($user->getPHID())
- ->setViewPolicy(PhabricatorPolicies::POLICY_USER);
+ $poll = PhabricatorSlowvotePoll::initializeNewPoll($user);
$is_new = true;
}
@@ -53,6 +51,7 @@
$v_description = $request->getStr('description');
$v_responses = (int)$request->getInt('responses');
$v_shuffle = (int)$request->getBool('shuffle');
+ $v_view_policy = $request->getStr('viewPolicy');
if ($is_new) {
$poll->setMethod($request->getInt('method'));
@@ -94,6 +93,10 @@
->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_SHUFFLE)
->setNewValue($v_shuffle);
+ $xactions[] = id(clone $template)
+ ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
+ ->setNewValue($v_view_policy);
+
if (empty($errors)) {
$editor = id(new PhabricatorSlowvoteEditor())
->setActor($user)
@@ -115,6 +118,8 @@
return id(new AphrontRedirectResponse())
->setURI('/V'.$poll->getID());
+ } else {
+ $poll->setViewPolicy($v_view_policy);
}
}
@@ -206,6 +211,11 @@
$cancel_uri = '/V'.$poll->getID();
}
+ $policies = id(new PhabricatorPolicyQuery())
+ ->setViewer($user)
+ ->setObject($poll)
+ ->execute();
+
$form
->appendChild(
id(new AphrontFormSelectControl())
@@ -222,6 +232,13 @@
pht('Show choices in random order.'),
$v_shuffle))
->appendChild(
+ id(new AphrontFormPolicyControl())
+ ->setUser($user)
+ ->setName('viewPolicy')
+ ->setPolicyObject($poll)
+ ->setPolicies($policies)
+ ->setCapability(PhabricatorPolicyCapability::CAN_VIEW))
+ ->appendChild(
id(new AphrontFormSubmitControl())
->setValue($button)
->addCancelButton($cancel_uri));
Index: src/applications/slowvote/controller/PhabricatorSlowvotePollController.php
===================================================================
--- src/applications/slowvote/controller/PhabricatorSlowvotePollController.php
+++ src/applications/slowvote/controller/PhabricatorSlowvotePollController.php
@@ -47,7 +47,7 @@
->setPolicyObject($poll);
$actions = $this->buildActionView($poll);
- $properties = $this->buildPropertyView($poll);
+ $properties = $this->buildPropertyView($poll, $actions);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
@@ -59,8 +59,7 @@
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setActionList($actions)
- ->setPropertyList($properties);
+ ->addPropertyList($properties);
return $this->buildApplicationPage(
array(
@@ -105,12 +104,16 @@
return $view;
}
- private function buildPropertyView(PhabricatorSlowvotePoll $poll) {
+ private function buildPropertyView(
+ PhabricatorSlowvotePoll $poll,
+ PhabricatorActionListView $actions) {
+
$viewer = $this->getRequest()->getUser();
- $view = id(new PhabricatorPropertyListView())
+ $view = id(new PHUIPropertyListView())
->setUser($viewer)
- ->setObject($poll);
+ ->setObject($poll)
+ ->setActionList($actions);
$view->invokeWillRenderEvent();
Index: src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php
===================================================================
--- src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php
+++ src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php
@@ -29,11 +29,10 @@
AphrontFormView $form,
PhabricatorSavedQuery $saved_query) {
$phids = $saved_query->getParameter('authorPHIDs', array());
- $handles = id(new PhabricatorHandleQuery())
+ $author_handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireViewer())
->withPHIDs($phids)
->execute();
- $author_tokens = mpull($handles, 'getFullName', 'getPHID');
$voted = $saved_query->getParameter('voted', false);
@@ -43,7 +42,7 @@
->setDatasource('/typeahead/common/users/')
->setName('authors')
->setLabel(pht('Authors'))
- ->setValue($author_tokens))
+ ->setValue($author_handles))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
Index: src/applications/slowvote/storage/PhabricatorSlowvotePoll.php
===================================================================
--- src/applications/slowvote/storage/PhabricatorSlowvotePoll.php
+++ src/applications/slowvote/storage/PhabricatorSlowvotePoll.php
@@ -28,6 +28,20 @@
private $choices = self::ATTACHABLE;
private $viewerChoices = self::ATTACHABLE;
+ public static function initializeNewPoll(PhabricatorUser $actor) {
+ $app = id(new PhabricatorApplicationQuery())
+ ->setViewer($actor)
+ ->withClasses(array('PhabricatorApplicationSlowvote'))
+ ->executeOne();
+
+ $view_policy = $app->getPolicy(
+ PhabricatorSlowvoteCapabilityDefaultView::CAPABILITY);
+
+ return id(new PhabricatorSlowvotePoll())
+ ->setAuthorPHID($actor->getPHID())
+ ->setViewPolicy($view_policy);
+ }
+
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
Index: src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
===================================================================
--- src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
+++ src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
@@ -1575,13 +1575,20 @@
}
$blocks = array_mergev($blocks);
- if (!$blocks) {
- return array();
+
+ $phids = array();
+ if ($blocks) {
+ $phids[] = PhabricatorMarkupEngine::extractFilePHIDsFromEmbeddedFiles(
+ $blocks);
}
- $phids = PhabricatorMarkupEngine::extractFilePHIDsFromEmbeddedFiles(
- $blocks);
+ foreach ($xactions as $xaction) {
+ $phids[] = $this->extractFilePHIDsFromCustomTransaction(
+ $object,
+ $xaction);
+ }
+ $phids = array_unique(array_filter(array_mergev($phids)));
if (!$phids) {
return array();
}
@@ -1598,6 +1605,15 @@
return mpull($files, 'getPHID');
}
+ /**
+ * @task files
+ */
+ protected function extractFilePHIDsFromCustomTransaction(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+ return array();
+ }
+
/**
* @task files
Index: src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php
===================================================================
--- src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php
+++ src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php
@@ -85,6 +85,11 @@
$need_users = true;
$need_all_users = true;
break;
+ case 'accountsorprojects':
+ $need_users = true;
+ $need_all_users = true;
+ $need_projs = true;
+ break;
case 'arcanistprojects':
$need_arcanist_projects = true;
break;
@@ -218,6 +223,7 @@
$projs = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withStatus(PhabricatorProjectQuery::STATUS_OPEN)
+ ->needProfiles(true)
->execute();
foreach ($projs as $proj) {
$proj_result = id(new PhabricatorTypeaheadResult())
@@ -225,10 +231,10 @@
->setDisplayType("Project")
->setURI('/project/view/'.$proj->getID().'/')
->setPHID($proj->getPHID());
- $prof = $proj->loadProfile();
- if ($prof) {
- $proj_result->setImageURI($prof->loadProfileImageURI());
- }
+
+ $prof = $proj->getProfile();
+ $proj_result->setImageURI($prof->getProfileImageURI());
+
$results[] = $proj_result;
}
}
Index: src/applications/uiexample/examples/PHUIListExample.php
===================================================================
--- src/applications/uiexample/examples/PHUIListExample.php
+++ src/applications/uiexample/examples/PHUIListExample.php
@@ -150,6 +150,45 @@
->setHref('#')
->setType(PHUIListItemView::TYPE_LINK);
+ $item1 = id(new PHUIListItemView())
+ ->setName('Installation')
+ ->setHref('#')
+ ->setSelected(true)
+ ->setType(PHUIListItemView::TYPE_LINK);
+
+ $item2 = id(new PHUIListItemView())
+ ->setName('Webserver Config')
+ ->setHref('#')
+ ->setType(PHUIListItemView::TYPE_LINK);
+
+ $details1 = id(new PHUIListItemView())
+ ->setName('Details')
+ ->setHref('#')
+ ->setSelected(true)
+ ->setType(PHUIListItemView::TYPE_LINK);
+
+ $details2 = id(new PHUIListItemView())
+ ->setName('Lint (OK)')
+ ->setHref('#')
+ ->setType(PHUIListItemView::TYPE_LINK);
+
+ $details3 = id(new PHUIListItemView())
+ ->setName('Unit (5/5)')
+ ->setHref('#')
+ ->setType(PHUIListItemView::TYPE_LINK);
+
+ $details4 = id(new PHUIListItemView())
+ ->setName('Lint (Warn)')
+ ->setHref('#')
+ ->setStatusColor(PHUIListItemView::STATUS_WARN)
+ ->setType(PHUIListItemView::TYPE_LINK);
+
+ $details5 = id(new PHUIListItemView())
+ ->setName('Unit (3/5)')
+ ->setHref('#')
+ ->setStatusColor(PHUIListItemView::STATUS_FAIL)
+ ->setType(PHUIListItemView::TYPE_LINK);
+
$topnav = id(new PHUIListView())
->setType(PHUIListView::NAVBAR_LIST)
->addMenuItem($home)
@@ -158,6 +197,14 @@
->addMenuItem($item3)
->addMenuItem($item4);
+ $statustabs = id(new PHUIListView())
+ ->setType(PHUIListView::NAVBAR_LIST)
+ ->addMenuItem($details1)
+ ->addMenuItem($details2)
+ ->addMenuItem($details3)
+ ->addMenuItem($details4)
+ ->addMenuItem($details5);
+
$layout1 =
array(
id(new PHUIBoxView())
@@ -187,6 +234,13 @@
->addMargin(PHUI::MARGIN_MEDIUM)
->setShadow(true));
+ $layout5 =
+ array(
+ id(new PHUIBoxView())
+ ->appendChild($statustabs)
+ ->addMargin(PHUI::MARGIN_MEDIUM)
+ ->setShadow(true));
+
$head1 = id(new PHUIHeaderView())
->setHeader(pht('Unstyled'));
@@ -199,6 +253,9 @@
$head4 = id(new PHUIHeaderView())
->setHeader(pht('Action Menu'));
+ $head5 = id(new PHUIHeaderView())
+ ->setHeader(pht('Status Tabs'));
+
$wrap1 = id(new PHUIBoxView())
->appendChild($layout1)
->addMargin(PHUI::MARGIN_LARGE);
@@ -215,6 +272,10 @@
->appendChild($layout4)
->addMargin(PHUI::MARGIN_LARGE);
+ $wrap5 = id(new PHUIBoxView())
+ ->appendChild($layout5)
+ ->addMargin(PHUI::MARGIN_LARGE);
+
return phutil_tag(
'div',
array(
@@ -227,6 +288,8 @@
$wrap2,
$head3,
$wrap3,
+ $head5,
+ $wrap5,
$head4,
$wrap4
));
Index: src/applications/uiexample/examples/PHUIPropertyListExample.php
===================================================================
--- src/applications/uiexample/examples/PHUIPropertyListExample.php
+++ src/applications/uiexample/examples/PHUIPropertyListExample.php
@@ -1,6 +1,6 @@
<?php
-final class PhabricatorPropertyListExample extends PhabricatorUIExample {
+final class PHUIPropertyListExample extends PhabricatorUIExample {
public function getName() {
return 'Property List';
@@ -8,14 +8,39 @@
public function getDescription() {
return hsprintf(
- 'Use <tt>PhabricatorPropertyListView</tt> to render object properties.');
+ 'Use <tt>PHUIPropertyListView</tt> to render object properties.');
}
public function renderExample() {
$request = $this->getRequest();
$user = $request->getUser();
- $view = new PhabricatorPropertyListView();
+ $details1 = id(new PHUIListItemView())
+ ->setName('Details')
+ ->setHref('#')
+ ->setSelected(true)
+ ->setType(PHUIListItemView::TYPE_LINK);
+
+ $details2 = id(new PHUIListItemView())
+ ->setName('Lint (Warn)')
+ ->setHref('#')
+ ->setStatusColor(PHUIListItemView::STATUS_WARN)
+ ->setType(PHUIListItemView::TYPE_LINK);
+
+ $details3 = id(new PHUIListItemView())
+ ->setName('Unit (3/5)')
+ ->setHref('#')
+ ->setStatusColor(PHUIListItemView::STATUS_FAIL)
+ ->setType(PHUIListItemView::TYPE_LINK);
+
+ $statustabs = id(new PHUIListView())
+ ->setType(PHUIListView::NAVBAR_LIST)
+ ->addMenuItem($details1)
+ ->addMenuItem($details2)
+ ->addMenuItem($details3);
+
+ $view = new PHUIPropertyListView();
+ $view->setTabs($statustabs);
$view->addProperty(
pht('Color'),
@@ -36,28 +61,34 @@
'velit, aliquam et consequat quis, tincidunt id dolor.');
- $view->addSectionHeader('Colors of the Rainbow');
+ $view2 = new PHUIPropertyListView();
+ $view2->addSectionHeader('Colors of the Rainbow');
- $view->addProperty('R', 'Red');
- $view->addProperty('O', 'Orange');
- $view->addProperty('Y', 'Yellow');
- $view->addProperty('G', 'Green');
- $view->addProperty('B', 'Blue');
- $view->addProperty('I', 'Indigo');
- $view->addProperty('V', 'Violet');
+ $view2->addProperty('R', 'Red');
+ $view2->addProperty('O', 'Orange');
+ $view2->addProperty('Y', 'Yellow');
+ $view2->addProperty('G', 'Green');
+ $view2->addProperty('B', 'Blue');
+ $view2->addProperty('I', 'Indigo');
+ $view2->addProperty('V', 'Violet');
- $view->addSectionHeader('Haiku About Pasta');
- $view->addTextContent(
+ $view3 = new PHUIPropertyListView();
+ $view3->addSectionHeader('Haiku About Pasta');
+
+ $view3->addTextContent(
hsprintf(
'this is a pasta<br />'.
'haiku. it is very bad.<br />'.
'what did you expect?'));
- $edge_cases_header = id(new PHUIHeaderView())
- ->setHeader(pht('Edge Cases'));
+ $object_box1 = id(new PHUIObjectBoxView())
+ ->setHeaderText('PHUIPropertyListView Stackered')
+ ->addPropertyList($view)
+ ->addPropertyList($view2)
+ ->addPropertyList($view3);
- $edge_cases_view = new PhabricatorPropertyListView();
+ $edge_cases_view = new PHUIPropertyListView();
$edge_cases_view->addProperty(
pht('Description'),
@@ -97,29 +128,13 @@
pht('Joe'),
pht('Smith'));
- $edge_cases_view->addProperty(
- pht('Description'),
- pht('The next section shows adjacent section headers.'));
-
- $edge_cases_view->addSectionHeader('Several');
- $edge_cases_view->addSectionHeader('Adjacent');
- $edge_cases_view->addSectionHeader('Section');
- $edge_cases_view->addSectionHeader('Headers');
-
- $edge_cases_view->addProperty(
- pht('Description'),
- pht('The next section is several adjacent text blocks.'));
-
- $edge_cases_view->addTextContent('Lorem');
- $edge_cases_view->addTextContent('ipsum');
- $edge_cases_view->addTextContent('dolor');
- $edge_cases_view->addTextContent('sit');
- $edge_cases_view->addTextContent('amet...');
+ $object_box2 = id(new PHUIObjectBoxView())
+ ->setHeaderText('Some Bad Examples')
+ ->addPropertyList($edge_cases_view);
return array(
- $view,
- $edge_cases_header,
- $edge_cases_view,
+ $object_box1,
+ $object_box2,
);
}
}
Index: src/infrastructure/celerity/CeleritySpriteGenerator.php
===================================================================
--- src/infrastructure/celerity/CeleritySpriteGenerator.php
+++ src/infrastructure/celerity/CeleritySpriteGenerator.php
@@ -337,6 +337,38 @@
return $sheet;
}
+ public function buildProjectsSheet() {
+ $icons = $this->getDirectoryList('projects_1x');
+ $scales = array(
+ '1x' => 1,
+ '2x' => 2,
+ );
+ $template = id(new PhutilSprite())
+ ->setSourceSize(50, 50);
+
+ $sprites = array();
+ $prefix = 'projects_';
+ foreach ($icons as $icon) {
+ $sprite = id(clone $template)
+ ->setName($prefix.$icon)
+ ->setTargetCSS('.'.$prefix.$icon);
+
+ foreach ($scales as $scale_key => $scale) {
+ $path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png');
+ $sprite->setSourceFile($path, $scale);
+ }
+ $sprites[] = $sprite;
+ }
+
+ $sheet = $this->buildSheet('projects', true);
+ $sheet->setScales($scales);
+ foreach ($sprites as $sprite) {
+ $sheet->addSprite($sprite);
+ }
+
+ return $sheet;
+ }
+
public function buildPaymentsSheet() {
$icons = $this->getDirectoryList('payments_2x');
$scales = array(
Index: src/infrastructure/celerity/CelerityStaticResourceResponse.php
===================================================================
--- src/infrastructure/celerity/CelerityStaticResourceResponse.php
+++ src/infrastructure/celerity/CelerityStaticResourceResponse.php
@@ -193,6 +193,9 @@
throw new Exception(
'Literal </script> is not allowed inside inline script.');
}
+ if (strpos($data, '<!') !== false) {
+ throw new Exception('Literal <! is not allowed inside inline script.');
+ }
return hsprintf(
// We don't use <![CDATA[ ]]> because it is ignored by HTML parsers. We
// would need to send the document with XHTML content type.
Index: src/infrastructure/customfield/field/PhabricatorCustomFieldList.php
===================================================================
--- src/infrastructure/customfield/field/PhabricatorCustomFieldList.php
+++ src/infrastructure/customfield/field/PhabricatorCustomFieldList.php
@@ -85,7 +85,7 @@
public function appendFieldsToPropertyList(
PhabricatorCustomFieldInterface $object,
PhabricatorUser $viewer,
- PhabricatorPropertyListView $view) {
+ PHUIPropertyListView $view) {
$this->readFieldsFromStorage($object);
$fields = $this->fields;
Index: src/infrastructure/edges/query/PhabricatorEdgeQuery.php
===================================================================
--- src/infrastructure/edges/query/PhabricatorEdgeQuery.php
+++ src/infrastructure/edges/query/PhabricatorEdgeQuery.php
@@ -26,6 +26,10 @@
private $edgeTypes;
private $resultSet;
+ const ORDER_OLDEST_FIRST = 'order:oldest';
+ const ORDER_NEWEST_FIRST = 'order:newest';
+ private $order = self::ORDER_NEWEST_FIRST;
+
private $needEdgeData;
@@ -75,6 +79,20 @@
/**
+ * Configure the order edge results are returned in.
+ *
+ * @param const Order constant.
+ * @return this
+ *
+ * @task config
+ */
+ public function setOrder($order) {
+ $this->order = $order;
+ return $this;
+ }
+
+
+ /**
* When loading edges, also load edge data.
*
* @param bool True to load edge data.
@@ -303,7 +321,11 @@
* @task internal
*/
private function buildOrderClause($conn_r) {
- return 'ORDER BY edge.dateCreated DESC, edge.seq ASC';
+ if ($this->order == self::ORDER_NEWEST_FIRST) {
+ return 'ORDER BY edge.dateCreated DESC, edge.seq DESC';
+ } else {
+ return 'ORDER BY edge.dateCreated ASC, edge.seq ASC';
+ }
}
}
Index: src/infrastructure/javelin/Javelin.php
===================================================================
--- src/infrastructure/javelin/Javelin.php
+++ src/infrastructure/javelin/Javelin.php
@@ -19,6 +19,16 @@
'Configure Editor' => pht('Configure Editor'),
);
break;
+
+ case 'phabricator-remarkup-assist':
+ $config['pht'] = array(
+ 'bold text' => pht('bold text'),
+ 'italic text' => pht('italic text'),
+ 'monospaced text' => pht('monospaced text'),
+ 'List Item' => pht('List Item'),
+ 'data' => pht('data'),
+ );
+ break;
}
$response = CelerityAPI::getStaticResourceResponse();
Index: src/infrastructure/markup/PhabricatorMarkupEngine.php
===================================================================
--- src/infrastructure/markup/PhabricatorMarkupEngine.php
+++ src/infrastructure/markup/PhabricatorMarkupEngine.php
@@ -460,6 +460,7 @@
$blocks[] = new PhutilRemarkupEngineRemarkupNoteBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupTableBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupSimpleTableBlockRule();
+ $blocks[] = new PhutilRemarkupEngineRemarkupInterpreterRule();
$custom_block_rule_classes = $options['custom-block'];
if ($custom_block_rule_classes) {
Index: src/infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterCowsay.php
===================================================================
--- /dev/null
+++ src/infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterCowsay.php
@@ -0,0 +1,55 @@
+<?php
+
+final class PhabricatorRemarkupBlockInterpreterCowsay
+ extends PhutilRemarkupBlockInterpreter {
+
+ public function getInterpreterName() {
+ return 'cowsay';
+ }
+
+ public function markupContent($content, array $argv) {
+ if (!Filesystem::binaryExists('cowsay')) {
+ return $this->markupError(
+ pht('Unable to locate the `cowsay` binary. Install cowsay.'));
+ }
+
+ $bin = idx($argv, 'think') ? 'cowthink' : 'cowsay';
+ $eyes = idx($argv, 'eyes', 'oo');
+ $tongue = idx($argv, 'tongue', ' ');
+ $cow = idx($argv, 'cow', 'default');
+
+ // NOTE: Strip this aggressively to prevent nonsense like
+ // `cow=/etc/passwd`. We could build a whiltelist with `cowsay -l`.
+ $cow = preg_replace('/[^a-z.-]+/', '', $cow);
+
+ $future = new ExecFuture(
+ '%s -e %s -T %s -f %s ',
+ $bin,
+ $eyes,
+ $tongue,
+ $cow);
+
+ $future->write($content);
+
+ list($err, $stdout, $stderr) = $future->resolve();
+
+ if ($err) {
+ return $this->markupError(
+ pht(
+ 'Execution of `cowsay` failed:', $stderr));
+ }
+
+
+ if ($this->getEngine()->isTextMode()) {
+ return $stdout;
+ }
+
+ return phutil_tag(
+ 'div',
+ array(
+ 'class' => 'PhabricatorMonospaced remarkup-cowsay',
+ ),
+ $stdout);
+ }
+
+}
Index: src/infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterFiglet.php
===================================================================
--- /dev/null
+++ src/infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterFiglet.php
@@ -0,0 +1,40 @@
+<?php
+
+final class PhabricatorRemarkupBlockInterpreterFiglet
+ extends PhutilRemarkupBlockInterpreter {
+
+ public function getInterpreterName() {
+ return 'figlet';
+ }
+
+ public function markupContent($content, array $argv) {
+ if (!Filesystem::binaryExists('figlet')) {
+ return $this->markupError(
+ pht('Unable to locate the `figlet` binary. Install figlet.'));
+ }
+
+ $future = id(new ExecFuture('figlet'))
+ ->write(trim($content, "\n"));
+
+ list($err, $stdout, $stderr) = $future->resolve();
+
+ if ($err) {
+ return $this->markupError(
+ pht(
+ 'Execution of `figlet` failed:', $stderr));
+ }
+
+
+ if ($this->getEngine()->isTextMode()) {
+ return $stdout;
+ }
+
+ return phutil_tag(
+ 'div',
+ array(
+ 'class' => 'PhabricatorMonospaced remarkup-figlet',
+ ),
+ $stdout);
+ }
+
+}
Index: src/infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterGraphviz.php
===================================================================
--- /dev/null
+++ src/infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterGraphviz.php
@@ -0,0 +1,44 @@
+<?php
+
+final class PhabricatorRemarkupBlockInterpreterGraphviz
+ extends PhutilRemarkupBlockInterpreter {
+
+ public function getInterpreterName() {
+ return 'dot';
+ }
+
+ public function markupContent($content, array $argv) {
+ if (!Filesystem::binaryExists('dot')) {
+ return $this->markupError(
+ pht('Unable to locate the `dot` binary. Install Graphviz.'));
+ }
+
+ $future = id(new ExecFuture('dot -T%s', 'png'))
+ ->write(trim($content));
+
+ list($err, $stdout, $stderr) = $future->resolve();
+
+ if ($err) {
+ return $this->markupError(
+ pht(
+ 'Execution of `dot` failed, check your syntax: %s', $stderr));
+ }
+
+ $file = PhabricatorFile::buildFromFileDataOrHash(
+ $stdout,
+ array(
+ 'name' => 'graphviz.png',
+ ));
+
+ if ($this->getEngine()->isTextMode()) {
+ return '<'.$file->getBestURI().'>';
+ }
+
+ return phutil_tag(
+ 'img',
+ array(
+ 'src' => $file->getBestURI(),
+ ));
+ }
+
+}
Index: src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php
===================================================================
--- src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php
+++ src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php
@@ -33,6 +33,7 @@
private $parentQuery;
private $rawResultLimit;
private $capabilities;
+ private $workspace = array();
/* -( Query Configuration )------------------------------------------------ */
@@ -229,6 +230,11 @@
$visible = $filter->apply($maybe_visible);
}
+ if ($visible) {
+ $this->putObjectsInWorkspace($this->getWorkspaceMapForPage($visible));
+ $visible = $this->didFilterPage($visible);
+ }
+
$removed = array();
foreach ($maybe_visible as $key => $object) {
if (empty($visible[$key])) {
@@ -300,6 +306,108 @@
}
+/* -( Query Workspace )---------------------------------------------------- */
+
+
+ /**
+ * Put a map of objects into the query workspace. Many queries perform
+ * subqueries, which can eventually end up loading the same objects more than
+ * once (often to perform policy checks).
+ *
+ * For example, loading a user may load the user's profile image, which might
+ * load the user object again in order to verify that the viewer has
+ * permission to see the file.
+ *
+ * The "query workspace" allows queries to load objects from elsewhere in a
+ * query block instead of refetching them.
+ *
+ * When using the query workspace, it's important to obey two rules:
+ *
+ * **Never put objects into the workspace which the viewer may not be able
+ * to see**. You need to apply all policy filtering //before// putting
+ * objects in the workspace. Otherwise, subqueries may read the objects and
+ * use them to permit access to content the user shouldn't be able to view.
+ *
+ * **Fully enrich objects pulled from the workspace.** After pulling objects
+ * from the workspace, you still need to load and attach any additional
+ * content the query requests. Otherwise, a query might return objects without
+ * requested content.
+ *
+ * Generally, you do not need to update the workspace yourself: it is
+ * automatically populated as a side effect of objects surviving policy
+ * filtering.
+ *
+ * @param map<phid, PhabricatorPolicyInterface> Objects to add to the query
+ * workspace.
+ * @return this
+ * @task workspace
+ */
+ public function putObjectsInWorkspace(array $objects) {
+ assert_instances_of($objects, 'PhabricatorPolicyInterface');
+
+ $viewer_phid = $this->getViewer()->getPHID();
+
+ // The workspace is scoped per viewer to prevent accidental contamination.
+ if (empty($this->workspace[$viewer_phid])) {
+ $this->workspace[$viewer_phid] = array();
+ }
+
+ $this->workspace[$viewer_phid] += $objects;
+
+ return $this;
+ }
+
+
+ /**
+ * Retrieve objects from the query workspace. For more discussion about the
+ * workspace mechanism, see @{method:putObjectsInWorkspace}. This method
+ * searches both the current query's workspace and the workspaces of parent
+ * queries.
+ *
+ * @param list<phid> List of PHIDs to retreive.
+ * @return this
+ * @task workspace
+ */
+ public function getObjectsFromWorkspace(array $phids) {
+ $viewer_phid = $this->getViewer()->getPHID();
+
+ $results = array();
+ foreach ($phids as $key => $phid) {
+ if (isset($this->workspace[$viewer_phid][$phid])) {
+ $results[$phid] = $this->workspace[$viewer_phid][$phid];
+ unset($phids[$key]);
+ }
+ }
+
+ if ($phids && $this->getParentQuery()) {
+ $results += $this->getParentQuery()->getObjectsFromWorkspace($phids);
+ }
+
+ return $results;
+ }
+
+
+ /**
+ * Convert a result page to a `<phid, PhabricatorPolicyInterface>` map.
+ *
+ * @param list<PhabricatorPolicyInterface> Objects.
+ * @return map<phid, PhabricatorPolicyInterface> Map of objects which can
+ * be put into the workspace.
+ * @task workspace
+ */
+ protected function getWorkspaceMapForPage(array $results) {
+ $map = array();
+ foreach ($results as $result) {
+ $phid = $result->getPHID();
+ if ($phid !== null) {
+ $map[$phid] = $result;
+ }
+ }
+
+ return $map;
+ }
+
+
/* -( Policy Query Implementation )---------------------------------------- */
@@ -353,7 +461,13 @@
/**
* Hook for applying a page filter prior to the privacy filter. This allows
* you to drop some items from the result set without creating problems with
- * pagination or cursor updates.
+ * pagination or cursor updates. You can also load and attach data which is
+ * required to perform policy filtering.
+ *
+ * Generally, you should load non-policy data and perform non-policy filtering
+ * later, in @{method:didFilterPage}. Strictly fewer objects will make it that
+ * far (so the program will load less data) and subqueries from that context
+ * can use the query workspace to further reduce query load.
*
* This method will only be called if data is available. Implementations
* do not need to handle the case of no results specially.
@@ -366,6 +480,29 @@
return $page;
}
+ /**
+ * Hook for performing additional non-policy loading or filtering after an
+ * object has satisfied all policy checks. Generally, this means loading and
+ * attaching related data.
+ *
+ * Subqueries executed during this phase can use the query workspace, which
+ * may improve performance or make circular policies resolvable. Data which
+ * is not necessary for policy filtering should generally be loaded here.
+ *
+ * This callback can still filter objects (for example, if attachable data
+ * is discovered to not exist), but should not do so for policy reasons.
+ *
+ * This method will only be called if data is available. Implementations do
+ * not need to handle the case of no results specially.
+ *
+ * @param list<wild> Results from @{method:willFilterPage()}.
+ * @return list<PhabricatorPolicyInterface> Objects after additional
+ * non-policy processing.
+ */
+ protected function didFilterPage(array $page) {
+ return $page;
+ }
+
/**
* Hook for removing filtered results from alternate result sets. This
Index: src/infrastructure/storage/lisk/LiskDAO.php
===================================================================
--- src/infrastructure/storage/lisk/LiskDAO.php
+++ src/infrastructure/storage/lisk/LiskDAO.php
@@ -892,6 +892,11 @@
}
+ public function getPHID() {
+ return $this->phid;
+ }
+
+
/**
* Test if a property exists.
*
Index: src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
===================================================================
--- src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -204,6 +204,10 @@
'type' => 'db',
'name' => 'legalpad',
),
+ 'db.policy' => array(
+ 'type' => 'db',
+ 'name' => 'policy',
+ ),
'0000.legacy.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('0000.legacy.sql'),
@@ -1652,6 +1656,26 @@
'type' => 'sql',
'name' => $this->getPatchPath('20130929.filepolicy.sql'),
),
+ '20131004.dxedgekey.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20131004.dxedgekey.sql'),
+ ),
+ '20131004.dxreviewers.php' => array(
+ 'type' => 'php',
+ 'name' => $this->getPatchPath('20131004.dxreviewers.php'),
+ ),
+ '20131006.hdisable.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20131006.hdisable.sql'),
+ ),
+ '20131010.pstorage.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20131010.pstorage.sql'),
+ ),
+ '20131015.cpolicy.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20131015.cpolicy.sql'),
+ ),
);
}
}
Index: src/view/control/AphrontCursorPagerView.php
===================================================================
--- src/view/control/AphrontCursorPagerView.php
+++ src/view/control/AphrontCursorPagerView.php
@@ -86,6 +86,51 @@
($this->beforeID && $this->moreResults);
}
+ public function getFirstPageURI() {
+ if (!$this->uri) {
+ throw new Exception(
+ pht("You must call setURI() before you can call getFirstPageURI()."));
+ }
+
+ if (!$this->afterID && !($this->beforeID && $this->moreResults)) {
+ return null;
+ }
+
+ return $this->uri
+ ->alter('before', null)
+ ->alter('after', null);
+ }
+
+ public function getPrevPageURI() {
+ if (!$this->uri) {
+ throw new Exception(
+ pht("You must call setURI() before you can call getPrevPageURI()."));
+ }
+
+ if (!$this->prevPageID) {
+ return null;
+ }
+
+ return $this->uri
+ ->alter('after', null)
+ ->alter('before', $this->prevPageID);
+ }
+
+ public function getNextPageURI() {
+ if (!$this->uri) {
+ throw new Exception(
+ pht("You must call setURI() before you can call getNextPageURI()."));
+ }
+
+ if (!$this->nextPageID) {
+ return null;
+ }
+
+ return $this->uri
+ ->alter('after', $this->nextPageID)
+ ->alter('before', null);
+ }
+
public function render() {
if (!$this->uri) {
throw new Exception(
@@ -94,37 +139,34 @@
$links = array();
- if ($this->afterID || ($this->beforeID && $this->moreResults)) {
+ $first_uri = $this->getFirstPageURI();
+ if ($first_uri) {
$links[] = phutil_tag(
'a',
array(
- 'href' => $this->uri
- ->alter('before', null)
- ->alter('after', null),
+ 'href' => $first_uri,
),
"\xC2\xAB ". pht("First"));
}
- if ($this->prevPageID) {
+ $prev_uri = $this->getPrevPageURI();
+ if ($prev_uri) {
$links[] = phutil_tag(
'a',
array(
- 'href' => $this->uri
- ->alter('after', null)
- ->alter('before', $this->prevPageID),
+ 'href' => $prev_uri,
),
"\xE2\x80\xB9 " . pht("Prev"));
}
- if ($this->nextPageID) {
+ $next_uri = $this->getNextPageURI();
+ if ($next_uri) {
$links[] = phutil_tag(
'a',
array(
- 'href' => $this->uri
- ->alter('after', $this->nextPageID)
- ->alter('before', null),
+ 'href' => $next_uri,
),
- "Next \xE2\x80\xBA");
+ pht("Next") . " \xE2\x80\xBA");
}
return phutil_tag(
Index: src/view/form/control/AphrontFormPolicyControl.php
===================================================================
--- src/view/form/control/AphrontFormPolicyControl.php
+++ src/view/form/control/AphrontFormPolicyControl.php
@@ -36,17 +36,66 @@
}
protected function getOptions() {
+ $capability = $this->capability;
+
$options = array();
foreach ($this->policies as $policy) {
- if (($policy->getPHID() == PhabricatorPolicies::POLICY_PUBLIC) &&
- ($this->capability != PhabricatorPolicyCapability::CAN_VIEW)) {
- // Never expose "Public" for anything except "Can View".
- continue;
+ if ($policy->getPHID() == PhabricatorPolicies::POLICY_PUBLIC) {
+ // Never expose "Public" for capabilities which don't support it.
+ $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
+ if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {
+ continue;
+ }
}
- $type_name = PhabricatorPolicyType::getPolicyTypeName($policy->getType());
- $options[$type_name][$policy->getPHID()] = $policy->getFullName();
+ $options[$policy->getType()][$policy->getPHID()] = array(
+ 'name' => phutil_utf8_shorten($policy->getName(), 28),
+ 'full' => $policy->getName(),
+ 'icon' => $policy->getIcon(),
+ );
+ }
+
+ // If we were passed several custom policy options, throw away the ones
+ // which aren't the value for this capability. For example, an object might
+ // have a custom view pollicy and a custom edit policy. When we render
+ // the selector for "Can View", we don't want to show the "Can Edit"
+ // custom policy -- if we did, the menu would look like this:
+ //
+ // Custom
+ // Custom Policy
+ // Custom Policy
+ //
+ // ...where one is the "view" custom policy, and one is the "edit" custom
+ // policy.
+
+ $type_custom = PhabricatorPolicyType::TYPE_CUSTOM;
+ if (!empty($options[$type_custom])) {
+ $options[$type_custom] = array_select_keys(
+ $options[$type_custom],
+ array($this->getValue()));
+ }
+
+ // If there aren't any custom policies, add a placeholder policy so we
+ // render a menu item. This allows the user to switch to a custom policy.
+
+ if (empty($options[$type_custom])) {
+ $placeholder = new PhabricatorPolicy();
+ $placeholder->setName(pht('Custom Policy...'));
+ $options[$type_custom][$this->getCustomPolicyPlaceholder()] = array(
+ 'name' => $placeholder->getName(),
+ 'full' => $placeholder->getName(),
+ 'icon' => $placeholder->getIcon(),
+ );
}
+
+ $options = array_select_keys(
+ $options,
+ array(
+ PhabricatorPolicyType::TYPE_GLOBAL,
+ PhabricatorPolicyType::TYPE_CUSTOM,
+ PhabricatorPolicyType::TYPE_PROJECT,
+ ));
+
return $options;
}
@@ -65,6 +114,90 @@
}
$this->setValue($policy);
+
+ $control_id = celerity_generate_unique_node_id();
+ $input_id = celerity_generate_unique_node_id();
+
+ $caret = phutil_tag(
+ 'span',
+ array(
+ 'class' => 'caret',
+ ));
+
+ $input = phutil_tag(
+ 'input',
+ array(
+ 'type' => 'hidden',
+ 'id' => $input_id,
+ 'name' => $this->getName(),
+ 'value' => $this->getValue(),
+ ));
+
+ $options = $this->getOptions();
+
+ $order = array();
+ $labels = array();
+ foreach ($options as $key => $values) {
+ $order[$key] = array_keys($values);
+ $labels[$key] = PhabricatorPolicyType::getPolicyTypeName($key);
+ }
+
+ $flat_options = array_mergev($options);
+
+ $icons = array();
+ foreach (igroup($flat_options, 'icon') as $icon => $ignored) {
+ $icons[$icon] = id(new PHUIIconView())
+ ->setSpriteSheet(PHUIIconView::SPRITE_STATUS)
+ ->setSpriteIcon($icon);
+ }
+
+
+ Javelin::initBehavior(
+ 'policy-control',
+ array(
+ 'controlID' => $control_id,
+ 'inputID' => $input_id,
+ 'options' => $flat_options,
+ 'groups' => array_keys($options),
+ 'order' => $order,
+ 'icons' => $icons,
+ 'labels' => $labels,
+ 'value' => $this->getValue(),
+ 'customPlaceholder' => $this->getCustomPolicyPlaceholder(),
+ ));
+
+ $selected = $flat_options[$this->getValue()];
+
+ return phutil_tag(
+ 'div',
+ array(
+ ),
+ array(
+ javelin_tag(
+ 'a',
+ array(
+ 'class' => 'grey button dropdown has-icon policy-control',
+ 'href' => '#',
+ 'mustcapture' => true,
+ 'sigil' => 'policy-control',
+ 'id' => $control_id,
+ ),
+ array(
+ $caret,
+ javelin_tag(
+ 'span',
+ array(
+ 'sigil' => 'policy-label',
+ 'class' => 'phui-button-text',
+ ),
+ array(
+ $icons[$selected['icon']],
+ $selected['name'],
+ )),
+ )),
+ $input,
+ ));
+
return AphrontFormSelectControl::renderSelectTag(
$this->getValue(),
$this->getOptions(),
@@ -75,5 +208,8 @@
));
}
+ private function getCustomPolicyPlaceholder() {
+ return 'custom:placeholder';
+ }
}
Index: src/view/form/control/AphrontFormRadioButtonControl.php
===================================================================
--- src/view/form/control/AphrontFormRadioButtonControl.php
+++ src/view/form/control/AphrontFormRadioButtonControl.php
@@ -50,7 +50,7 @@
),
$button['label']);
- if (strlen($button['caption'])) {
+ if ($button['caption']) {
$label = hsprintf(
'%s<div class="aphront-form-radio-caption">%s</div>',
$label,
Index: src/view/form/control/AphrontFormTokenizerControl.php
===================================================================
--- src/view/form/control/AphrontFormTokenizerControl.php
+++ src/view/form/control/AphrontFormTokenizerControl.php
@@ -35,15 +35,8 @@
$name = $this->getName();
$values = nonempty($this->getValue(), array());
- // TODO: Convert tokenizers to always take raw handles. For now, we
- // accept either a list of handles or a `map<phid, string>`.
- try {
- assert_instances_of($values, 'PhabricatorObjectHandle');
- $values = mpull($values, 'getFullName', 'getPHID');
- } catch (InvalidArgumentException $ex) {
- // Ignore this, just use the values as provided.
- }
-
+ assert_instances_of($values, 'PhabricatorObjectHandle');
+ $values = mpull($values, 'getFullName', 'getPHID');
if ($this->getID()) {
$id = $this->getID();
@@ -103,6 +96,7 @@
'repositories' => pht('Type a repository name...'),
'packages' => pht('Type a package name...'),
'arcanistproject' => pht('Type an arc project name...'),
+ 'accountsorprojects' => pht('Type a user or project name...'),
);
return idx($map, $request);
Index: src/view/layout/PhabricatorCrumbsView.php
===================================================================
--- src/view/layout/PhabricatorCrumbsView.php
+++ src/view/layout/PhabricatorCrumbsView.php
@@ -35,10 +35,15 @@
foreach ($this->actions as $action) {
$icon = null;
if ($action->getIcon()) {
+ $icon_name = $action->getIcon();
+ if ($action->getDisabled()) {
+ $icon_name .= '-grey';
+ }
+
$icon = phutil_tag(
'span',
array(
- 'class' => 'sprite-icons icons-'.$action->getIcon(),
+ 'class' => 'sprite-icons icons-'.$icon_name,
),
'');
}
@@ -55,6 +60,11 @@
}
$action_classes = $action->getClasses();
$action_classes[] = 'phabricator-crumbs-action';
+
+ if ($action->getDisabled()) {
+ $action_classes[] = 'phabricator-crumbs-action-disabled';
+ }
+
$actions[] = javelin_tag(
'a',
array(
Index: src/view/page/PhabricatorStandardPageView.php
===================================================================
--- src/view/page/PhabricatorStandardPageView.php
+++ src/view/page/PhabricatorStandardPageView.php
@@ -202,8 +202,14 @@
require_celerity_resource('javelin-behavior-error-log');
}
+ if ($user) {
+ $viewer = $user;
+ } else {
+ $viewer = new PhabricatorUser();
+ }
+
$menu = id(new PhabricatorMainMenuView())
- ->setUser($request->getUser())
+ ->setUser($viewer)
->setDefaultSearchScope($this->getSearchDefaultScope());
if ($this->getController()) {
@@ -345,7 +351,7 @@
$user = $request->getUser();
$container = null;
- if ($user->isLoggedIn()) {
+ if ($user && $user->isLoggedIn()) {
$aphlict_object_id = celerity_generate_unique_node_id();
$aphlict_container_id = celerity_generate_unique_node_id();
Index: src/view/phui/PHUIHeaderView.php
===================================================================
--- src/view/phui/PHUIHeaderView.php
+++ src/view/phui/PHUIHeaderView.php
@@ -65,6 +65,31 @@
return $this;
}
+ public function setStatus($icon, $color, $name) {
+ $header_class = 'phui-header-status';
+
+ if ($color) {
+ $icon = $icon.'-'.$color;
+ $header_class = $header_class.'-'.$color;
+ }
+
+ $img = id(new PHUIIconView())
+ ->setSpriteSheet(PHUIIconView::SPRITE_STATUS)
+ ->setSpriteIcon($icon);
+
+ $tag = phutil_tag(
+ 'span',
+ array(
+ 'class' => "{$header_class} plr",
+ ),
+ array(
+ $img,
+ $name,
+ ));
+
+ return $this->addProperty(self::PROPERTY_STATUS, $tag);
+ }
+
public function render() {
require_celerity_resource('phui-header-view-css');
@@ -199,7 +224,7 @@
'href' => '/policy/explain/'.$phid.'/'.$view_capability.'/',
'sigil' => 'workflow',
),
- $policy->getName());
+ $policy->getShortName());
return array($icon, $link);
}
Index: src/view/phui/PHUIListItemView.php
===================================================================
--- src/view/phui/PHUIListItemView.php
+++ src/view/phui/PHUIListItemView.php
@@ -10,6 +10,9 @@
const TYPE_DIVIDER = 'type-divider';
const TYPE_ICON = 'type-icon';
+ const STATUS_WARN = 'phui-list-item-warn';
+ const STATUS_FAIL = 'phui-list-item-fail';
+
private $name;
private $href;
private $type = self::TYPE_LINK;
@@ -19,6 +22,7 @@
private $selected;
private $disabled;
private $renderNameAsTooltip;
+ private $statusColor;
public function setRenderNameAsTooltip($render_name_as_tooltip) {
$this->renderNameAsTooltip = $render_name_as_tooltip;
@@ -92,6 +96,11 @@
return $this->isExternal;
}
+ public function setStatusColor($color) {
+ $this->statusColor = $color;
+ return $this;
+ }
+
protected function getTagName() {
return 'li';
}
@@ -109,6 +118,10 @@
$classes[] = 'phui-list-item-selected';
}
+ if ($this->statusColor) {
+ $classes[] = $this->statusColor;
+ }
+
return array(
'class' => $classes,
);
Index: src/view/phui/PHUIObjectBoxView.php
===================================================================
--- src/view/phui/PHUIObjectBoxView.php
+++ src/view/phui/PHUIObjectBoxView.php
@@ -8,16 +8,12 @@
private $validationException;
private $header;
private $flush;
- private $propertyList;
- private $actionList;
+ private $propertyList = array();
- public function setActionList(PhabricatorActionListView $action_list) {
- $this->actionList = $action_list;
- return $this;
- }
-
- public function setPropertyList(PhabricatorPropertyListView $property_list) {
- $this->propertyList = $property_list;
+ // This is mostly a conveinence method to lessen code dupe
+ // when building objectboxes.
+ public function addPropertyList(PHUIPropertyListView $property_list) {
+ $this->propertyList[] = $property_list;
return $this;
}
@@ -78,6 +74,14 @@
}
}
+ $property_list = null;
+ if ($this->propertyList) {
+ $property_list = new PHUIPropertyGroupView();
+ foreach ($this->propertyList as $item) {
+ $property_list->addPropertyList($item);
+ }
+ }
+
$content = id(new PHUIBoxView())
->appendChild(
array(
@@ -85,8 +89,7 @@
$this->formError,
$exception_errors,
$this->form,
- $this->actionList,
- $this->propertyList,
+ $property_list,
$this->renderChildren(),
))
->setBorder(true)
Index: src/view/phui/PHUIPropertyGroupView.php
===================================================================
--- /dev/null
+++ src/view/phui/PHUIPropertyGroupView.php
@@ -0,0 +1,24 @@
+<?php
+
+final class PHUIPropertyGroupView extends AphrontTagView {
+
+ private $items;
+
+ public function addPropertyList(PHUIPropertyListView $item) {
+ $this->items[] = $item;
+ }
+
+ protected function canAppendChild() {
+ return false;
+ }
+
+ protected function getTagAttributes() {
+ return array(
+ 'class' => 'phui-property-list-view',
+ );
+ }
+
+ protected function getTagContent() {
+ return $this->items;
+ }
+}
Index: src/view/phui/PHUIPropertyListView.php
===================================================================
--- src/view/phui/PHUIPropertyListView.php
+++ src/view/phui/PHUIPropertyListView.php
@@ -1,11 +1,13 @@
<?php
-final class PhabricatorPropertyListView extends AphrontView {
+final class PHUIPropertyListView extends AphrontView {
private $parts = array();
private $hasKeyboardShortcuts;
private $object;
private $invokedWillRenderEvent;
+ private $actionList;
+ private $tabs;
protected function canAppendChild() {
return false;
@@ -16,6 +18,11 @@
return $this;
}
+ public function setActionList(PhabricatorActionListView $list) {
+ $this->actionList = $list;
+ return $this;
+ }
+
public function setHasKeyboardShortcuts($has_keyboard_shortcuts) {
$this->hasKeyboardShortcuts = $has_keyboard_shortcuts;
return $this;
@@ -43,6 +50,11 @@
return $this;
}
+ public function setTabs(PHUIListView $tabs) {
+ $this->tabs = $tabs;
+ return $tabs;
+ }
+
public function addSectionHeader($name) {
$this->parts[] = array(
'type' => 'section',
@@ -84,7 +96,7 @@
public function render() {
$this->invokeWillRenderEvent();
- require_celerity_resource('phabricator-property-list-view-css');
+ require_celerity_resource('phui-property-list-view-css');
$items = array();
foreach ($this->parts as $part) {
@@ -108,9 +120,12 @@
return phutil_tag(
'div',
array(
- 'class' => 'phabricator-property-list-view',
+ 'class' => 'phui-property-list-section',
),
- $items);
+ array(
+ $this->tabs,
+ $items,
+ ));
}
private function renderPropertyPart(array $part) {
@@ -126,14 +141,14 @@
$items[] = phutil_tag(
'dt',
array(
- 'class' => 'phabricator-property-list-key',
+ 'class' => 'phui-property-list-key',
),
array($key, ' '));
$items[] = phutil_tag(
'dd',
array(
- 'class' => 'phabricator-property-list-value',
+ 'class' => 'phui-property-list-value',
),
array($value, ' '));
}
@@ -141,7 +156,7 @@
$list = phutil_tag(
'dl',
array(
- 'class' => 'phabricator-property-list-properties',
+ 'class' => 'phui-property-list-properties',
),
$items);
@@ -150,36 +165,46 @@
$shortcuts = new AphrontKeyboardShortcutsAvailableView();
}
- return array(
- $shortcuts,
- phutil_tag(
+ $list = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'phui-property-list-properties-wrap',
+ ),
+ array($shortcuts, $list));
+
+ $action_list = null;
+ if ($this->actionList) {
+ $action_list = phutil_tag(
'div',
array(
- 'class' => 'phabricator-property-list-container',
+ 'class' => 'phui-property-list-actions',
),
+ $this->actionList);
+ $this->actionList = null;
+ }
+
+ return phutil_tag(
+ 'div',
array(
- $list,
- phutil_tag(
- 'div',
- array('class' => 'phabriator-property-list-view-end'),
- ''),
- )));
+ 'class' => 'phui-property-list-container grouped',
+ ),
+ array($action_list, $list));
}
private function renderSectionPart(array $part) {
return phutil_tag(
'div',
array(
- 'class' => 'phabricator-property-list-section-header',
+ 'class' => 'phui-property-list-section-header',
),
$part['name']);
}
private function renderTextPart(array $part) {
$classes = array();
- $classes[] = 'phabricator-property-list-text-content';
+ $classes[] = 'phui-property-list-text-content';
if ($part['type'] == 'image') {
- $classes[] = 'phabricator-property-list-image-content';
+ $classes[] = 'phui-property-list-image-content';
}
return phutil_tag(
'div',
Index: src/view/phui/PHUIStatusItemView.php
===================================================================
--- src/view/phui/PHUIStatusItemView.php
+++ src/view/phui/PHUIStatusItemView.php
@@ -62,6 +62,7 @@
$icon->setMetadata(
array(
'tip' => $this->iconLabel,
+ 'size' => 240,
));
}
}
Index: webroot/index.php
===================================================================
--- webroot/index.php
+++ webroot/index.php
@@ -83,9 +83,6 @@
$controller->willProcessRequest($uri_data);
$response = $controller->processRequest();
}
- } catch (AphrontRedirectException $ex) {
- $response = id(new AphrontRedirectResponse())
- ->setURI($ex->getURI());
} catch (Exception $ex) {
$original_exception = $ex;
$response = $application->handleException($ex);
Index: webroot/rsrc/css/aphront/dialog-view.css
===================================================================
--- webroot/rsrc/css/aphront/dialog-view.css
+++ webroot/rsrc/css/aphront/dialog-view.css
@@ -116,3 +116,11 @@
margin: 12px 24px;
list-style: circle;
}
+
+.aphront-policy-rejection {
+ font-weight: bold;
+}
+
+.aphront-capability-details {
+ margin: 20px 0 4px;
+}
Index: webroot/rsrc/css/application/chatlog/chatlog.css
===================================================================
--- webroot/rsrc/css/application/chatlog/chatlog.css
+++ webroot/rsrc/css/application/chatlog/chatlog.css
@@ -10,8 +10,30 @@
padding: 0;
}
+.phabricator-chat-log-pager-top {
+ padding: 16px 4px 8px;
+ font-weight: bold;
+ float: right;
+}
+
+.phabricator-chat-log-pager-bottom {
+ padding: 8px 4px 16px;
+ font-weight: bold;
+ float: right;
+}
+
+.phabricator-chat-log-pager-top a, .phabricator-chat-log-pager-bottom a {
+ padding: 2px 3px;
+}
+
+.phabricator-chat-log-jump {
+ padding: 16px 4px 8px;
+ font-weight: bold;
+ float: left;
+}
+
.phabricator-chat-log-panel {
- margin: 20px auto;
+ clear: both;
border-left: 1px solid #e7e7e7;
border-right: 1px solid #e7e7e7;
border-bottom: 1px solid #c0c5d1;
Index: webroot/rsrc/css/application/differential/changeset-view.css
===================================================================
--- webroot/rsrc/css/application/differential/changeset-view.css
+++ webroot/rsrc/css/application/differential/changeset-view.css
@@ -5,14 +5,14 @@
.differential-changeset {
position: relative;
margin: 0;
- padding: 16px 0;
+ padding-top: 32px;
}
.differential-diff {
background: transparent;
width: 100%;
- border-top: 1px solid #cca;
- border-bottom: 1px solid #cca;
+ border-top: 1px solid {$lightblueborder};
+ border-bottom: 1px solid {$lightblueborder};
}
.differential-diff td {
@@ -75,11 +75,11 @@
}
.differential-diff td.old {
- background: #ffd0d0;
+ background: #ffd0d0;
}
.differential-diff td.new {
- background: #d0ffd0;
+ background: #d0ffd0;
}
.differential-diff td.old-rebase {
@@ -157,14 +157,16 @@
background: #f3f6ff;
}
+
+
.differential-diff td.show-more,
.differential-diff th.show-context-line,
.differential-diff td.show-context,
.differential-diff td.differential-shield {
- background: #ffffee;
+ background: {$lightbluebackground};
padding: 12px 0;
- border-top: 1px solid #ccccaa;
- border-bottom: 1px solid #ccccaa;
+ border-top: 1px solid {$thinblueborder};
+ border-bottom: 1px solid {$thinblueborder};
}
.differential-diff td.show-more,
@@ -176,7 +178,7 @@
.differential-diff td.show-more {
text-align: center;
- color: #999966;
+ color: {$bluetext};
}
.differential-diff th.show-context-line {
@@ -204,32 +206,34 @@
}
.differential-meta-notice {
- border: 1px solid #ffdd99;
- background: #ffeeaa;
- font-family: "Helvetica Neue", "Arial", sans-serif;
- font-size: 12px;
- padding: 1em;
- margin: 0 0 6px 0;
+ border-top: 1px solid {$yellow};
+ border-bottom: 1px solid {$yellow};
+ background-color: {$lightyellow};
+ padding: 12px;
+}
+
+.differential-meta-notice + .differential-diff {
+ border-top: none;
}
.differential-changeset h1 {
- font-size: 14px;
+ font-size: 15px;
padding: 2px 0 12px 12px;
}
.differential-reticle {
- background: #ffeeaa;
- border: 1px solid #ffcc00;
- position: absolute;
- opacity: 0.5;
- top: 0px;
- left: 0px;
+ background: {$lightyellow};
+ border: 1px solid {$yellow};
+ position: absolute;
+ opacity: 0.5;
+ top: 0px;
+ left: 0px;
}
.differential-inline-comment,
.differential-inline-comment-edit {
- background: #f9f9f1;
- border: 1px solid #aaaa88;
+ background: #ffffee;
+ border: 1px solid #ccccaa;
font-family: "Helvetica Neue", "Arial", sans-serif;
font-size: 12px;
margin: 6px 0px;
@@ -249,9 +253,9 @@
.differential-inline-comment-head {
font-weight: bold;
color: #333333;
- border-bottom: 1px solid #ccccaa;
- padding-bottom: 6px;
- margin-bottom: 4px;
+ border-bottom: 1px solid rgba(204,204,170,0.37);
+ padding-bottom: 4px;
+ margin-bottom: 8px;
}
.differential-inline-comment-unsaved-draft .differential-inline-comment-head {
@@ -259,12 +263,12 @@
}
.differential-inline-comment-synthetic {
- background: #efffff;
- border: 1px solid #20dfdf;
+ background: {$lightblue};
+ border: 1px solid {$blue};
}
.differential-inline-comment-synthetic .differential-inline-comment-head {
- border-bottom: 1px solid #20dfdf;
+ border-bottom: 1px solid {$blueborder};
}
@@ -299,7 +303,7 @@
.differential-property-table {
width: auto;
- margin: .75em auto;
+ margin: 12px auto;
background: #e3e3e3;
}
@@ -363,8 +367,7 @@
}
.differential-inline-comment-edit-buttons {
- padding: 5px 0 0 0;
-
+ padding: 4px 0 0 0;
}
.differential-inline-comment-edit-buttons button {
@@ -447,3 +450,9 @@
.differential-collapse-undo a {
font-weight: bold;
}
+
+.differential-file-icon-header .phui-icon-view {
+ display: inline-block;
+ margin: 0 4px 2px 0;
+ vertical-align: middle;
+}
Index: webroot/rsrc/css/application/differential/results-table.css
===================================================================
--- webroot/rsrc/css/application/differential/results-table.css
+++ webroot/rsrc/css/application/differential/results-table.css
@@ -4,37 +4,33 @@
table.differential-results-table {
border-collapse: separate;
- border-spacing: 1px;
- width: 100%;
+ width: 96%;
font-size: 11px;
- background: #fcfcec;
}
.differential-results-table th {
- font-weight: bold;
text-align: center;
white-space: nowrap;
vertical-align: middle;
padding: 2px 4px;
- margin: 0;
- width: 70px;
+ width: 50px;
+ border-right: 1px solid #fff;
+ background: #f7f7f7;
}
.differential-results-table td {
- padding: 2px 8px;
+ padding: 0 8px;
margin: 0;
vertical-align: middle;
+ background: #f7f7f7;
}
.differential-results-table tr.differential-results-row-star th,
.differential-results-table tr.differential-results-row-star td {
- font-weight: bold;
- background: #dfe3ec;
+ background: {$greybackground};
}
-
.differential-results-table tr.differential-results-row-section th {
- font-weight: bold;
padding-top: 4px;
text-align: left;
}
@@ -44,9 +40,9 @@
}
.differential-results-table tr.differential-results-row-excuse td {
- padding-top: 1em;
- padding-right: 1em;
- padding-bottom: 1em;
+ padding-top: 8px;
+ padding-right: 8px;
+ padding-bottom: 8px;
}
.differential-results-table tr.differential-results-row-red th {
@@ -65,14 +61,14 @@
background: #88bbff;
}
-
.differential-results-table tr.differential-results-row-details td {
color: {$lightgreytext};
}
.differential-results-table tr.differential-results-row-show th {
- padding: 4px;
- color: {$lightgreytext};
- font-weight: bold;
- background: #dfe3ec;
+ border-top: 1px solid #fff;
+ border-right: none;
+ padding: 2px;
+ color: {$bluetext};
+ background: {$greybackground};
}
Index: webroot/rsrc/css/application/differential/revision-history.css
===================================================================
--- webroot/rsrc/css/application/differential/revision-history.css
+++ webroot/rsrc/css/application/differential/revision-history.css
@@ -8,13 +8,10 @@
border-spacing: 1px;
}
-.differential-revision-history-table th {
- color: {$greytext};
- padding: 4px 6px;
-}
-
+.differential-revision-history-table th,
.differential-revision-history-table td {
- padding: 4px 6px;
+ color: {$darkbluetext};
+ padding: 4px 8px;
}
.differential-revision-history-table td {
@@ -22,10 +19,9 @@
}
.differential-revision-history-table tr.alt {
- background: #dcdcdc;
+ background: {$greybackground};
}
-
.differential-revision-history-table td.revhistory-desc {
width: 100%;
white-space: normal;
@@ -41,7 +37,7 @@
.differential-revision-history-table td.revhistory-old,
.differential-revision-history-table td.revhistory-new {
- padding: 0em 1.5em;
+ padding: 0 16px;
text-align: center;
}
@@ -65,21 +61,18 @@
text-align: center;
}
-
-
-
.differential-revision-history-table td.diff-differ-submit {
text-align: right;
border-bottom: none;
- padding: 8px 0px 4px 0px;
+ padding: 8px 0 4px 0;
}
.differential-revision-history-table td.diff-differ-submit button {
- margin-left: 1em;
+ margin-left: 12px;
}
.differential-revision-history-table td.diff-differ-submit label {
font-weight: bold;
- padding-right: .25em;
+ padding-right: 4px;
color: {$darkgreytext};
}
Index: webroot/rsrc/css/application/diffusion/commit-view.css
===================================================================
--- webroot/rsrc/css/application/diffusion/commit-view.css
+++ webroot/rsrc/css/application/diffusion/commit-view.css
@@ -2,10 +2,6 @@
* @provides diffusion-commit-view-css
*/
-.diffusion-commit-message {
- padding: 8px;
-}
-
.diffusion-comment-list {
margin: 2em;
}
Index: webroot/rsrc/css/application/diffusion/diffusion-source.css
===================================================================
--- webroot/rsrc/css/application/diffusion/diffusion-source.css
+++ webroot/rsrc/css/application/diffusion/diffusion-source.css
@@ -3,7 +3,6 @@
*/
.diffusion-source {
- margin: 1em 0 2em;
width: 100%;
font-family: "Monaco", Consolas, monospace;
font-size: 10px;
@@ -15,14 +14,12 @@
}
.diffusion-source th {
- text-align: right;
- vertical-align: top;
- background: #eeeeee;
- color: {$lightgreytext};
- border-style: solid;
- border-width: 0px 1px;
- border-color: #eeeeee #999999 #eeeeee #dddddd;
- font-size: 11px;
+ text-align: right;
+ vertical-align: top;
+ background: {$lightgreybackground};
+ color: {$bluetext};
+ border-right: 1px solid {$thinblueborder};
+ font-size: 11px;
}
.diffusion-source td {
@@ -46,7 +43,7 @@
}
.diffusion-blame-link {
- min-width: 25px;
+ min-width: 28px;
}
.diffusion-rev-link {
@@ -61,20 +58,21 @@
.diffusion-rev-link a,
.diffusion-author-link a,
.diffusion-line-link a {
- font-weight: bold;
+ color: {$darkbluetext};
}
.diffusion-rev-link a,
.diffusion-author-link span,
.diffusion-author-link a {
- margin: 0 8px;
+ margin: 2px 8px 0;
+ display: block;
}
.diffusion-blame-link a,
.diffusion-line-link a {
/* Give the user a larger click target. */
- display: block;
- padding: 2px 8px;
+ display: block;
+ padding: 2px 8px;
}
.diffusion-line-link {
Index: webroot/rsrc/css/application/policy/policy-edit.css
===================================================================
--- /dev/null
+++ webroot/rsrc/css/application/policy/policy-edit.css
@@ -0,0 +1,32 @@
+/**
+ * @provides policy-edit-css
+ */
+
+.policy-rules-table {
+ width: 100%;
+}
+
+.policy-rules-table td {
+ padding: 4px;
+ width: 32px;
+ vertical-align: middle;
+}
+
+.policy-rules-table td.action-cell {
+ width: 120px;
+}
+
+.policy-rules-table td.rule-cell {
+ width: 180px;
+}
+
+.policy-rules-table td.value-cell {
+ width: auto;
+ padding-right: 12px;
+}
+
+.policy-rules-table td.action-cell select,
+.policy-rules-table td.rule-cell select,
+.policy-rules-table td input {
+ width: 100%;
+}
Index: webroot/rsrc/css/application/policy/policy.css
===================================================================
--- /dev/null
+++ webroot/rsrc/css/application/policy/policy.css
@@ -0,0 +1,18 @@
+/**
+ * @provides policy-css
+ */
+
+.policy-capability-explanation .phui-icon-view {
+ display: inline-block;
+ vertical-align: text-top;
+}
+
+.policy-capability-explanation {
+ margin: 8px 0 0;
+}
+
+.policy-capability-explanation a {
+ line-height: 14px;
+ margin-left: 4px;
+ color: {$bluetext};
+}
Index: webroot/rsrc/css/core/remarkup.css
===================================================================
--- webroot/rsrc/css/core/remarkup.css
+++ webroot/rsrc/css/core/remarkup.css
@@ -329,6 +329,20 @@
border-right: 1px solid #cccccc;
}
+.remarkup-interpreter-error {
+ padding: 8px;
+ border: 1px solid {$red};
+ background-color: {$lightred};
+}
+
+.remarkup-cowsay {
+ white-space: pre-wrap;
+}
+
+.remarkup-figlet {
+ white-space: pre-wrap;
+}
+
.remarkup-assist {
display: block;
width: 14px;
Index: webroot/rsrc/css/diviner/diviner-shared.css
===================================================================
--- webroot/rsrc/css/diviner/diviner-shared.css
+++ webroot/rsrc/css/diviner/diviner-shared.css
@@ -73,7 +73,7 @@
width: 100%;
}
-.phabricator-property-list-view + .diviner-document-section {
+.phui-property-list-view + .diviner-document-section {
margin-top: -1px;
}
Index: webroot/rsrc/css/layout/phabricator-action-list-view.css
===================================================================
--- webroot/rsrc/css/layout/phabricator-action-list-view.css
+++ webroot/rsrc/css/layout/phabricator-action-list-view.css
@@ -2,43 +2,12 @@
* @provides phabricator-action-list-view-css
*/
-.phabricator-action-list-view {
- background: #ffffff;
-}
-
.device-desktop .phabricator-action-list-view {
- border: 1px solid {$lightblueborder};
- border-bottom: 1px solid {$blueborder};
- padding: 4px 0;
- float: right;
- margin-top: 0px;
- width: 20%;
font-size: 12px;
}
-.device-desktop .phui-header-shell + .phabricator-action-list-view {
- margin-top: -33px;
-}
-
-.device-desktop .phui-header-shell.phui-header-tall + .phabricator-action-list-view {
- margin-top: -55px;
-}
-
-.device-desktop .phui-header-shell.phui-header-has-image +
- .phabricator-action-list-view {
- margin-top: -53px;
-}
-
-.device .phui-header-shell + .phabricator-action-list-view {
- margin-top: -1px;
-}
-
.device .phabricator-action-list-view {
- border-top: 1px solid #dcdcdc;
padding: 4px 0;
-}
-
-.device .phabricator-action-list-view {
display: none;
}
@@ -50,6 +19,8 @@
.device .phabricator-action-view-item {
font-size: 14px;
line-height: 22px;
+ padding-bottom: 4px;
+ border-bottom: 1px solid {$thinblueborder};
}
.phabricator-action-view {
@@ -78,10 +49,9 @@
.phabricator-action-view button.phabricator-action-view-item,
.phabricator-action-view-item {
- padding: 1px 0 1px 28px;
- line-height: 20px;
+ padding: 2px 4px 2px 28px;
+ line-height: 18px;
display: block;
- font-size: 12px;
text-decoration: none;
color: {$darkgreytext};
}
@@ -91,7 +61,7 @@
height: 14px;
position: absolute;
top: 5px;
- left: 8px;
+ left: 9px;
}
.device-desktop .phabricator-action-view:hover .phabricator-action-view-item {
Index: webroot/rsrc/css/layout/phabricator-crumbs-view.css
===================================================================
--- webroot/rsrc/css/layout/phabricator-crumbs-view.css
+++ webroot/rsrc/css/layout/phabricator-crumbs-view.css
@@ -25,6 +25,10 @@
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.9);
}
+.phabricator-crumbs-view a.phabricator-crumbs-action-disabled {
+ color: {$lightgreytext};
+}
+
.phabricator-crumbs-action.phabricator-crumbs-action-menu {
display: none;
}
Index: webroot/rsrc/css/layout/phabricator-property-list-view.css
===================================================================
--- webroot/rsrc/css/layout/phabricator-property-list-view.css
+++ /dev/null
@@ -1,127 +0,0 @@
-/**
- * @provides phabricator-property-list-view-css
- */
-
-.phabricator-property-list-view {
- background-color: #fff;
-}
-
-.phabricator-property-list-view .keyboard-shortcuts-available {
- float: right;
- height: 16px;
- margin: 12px 10px -28px 0px;
- padding: 0px 20px 0px 0px;
- vertical-align: middle;
- color: {$greytext};
- text-align: right;
- font-size: 11px;
- background:
- url('/rsrc/image/icon/fatcow/key_question.png') right center no-repeat;
-}
-
-.phabricator-property-list-container +
- .phabricator-property-list-section-header {
- border-color: {$lightgreyborder};
- border-style: solid;
- border-width: 1px 0 0;
-}
-
-.device-desktop .phabricator-property-list-container {
- padding: 12px 0 12px 0;
-}
-
-.device .phabricator-property-list-container {
- padding: 12px 0 4px 0;
-}
-
-.phabricator-property-list-key {
- color: {$bluetext};
- font-weight: bold;
- overflow: hidden;
- white-space: nowrap;
-}
-
-.device-desktop .phabricator-property-list-key {
- width: 15%;
- margin-left: 1%;
- text-align: right;
- float: left;
- clear: left;
- margin-bottom: 4px;
-}
-
-.device .phabricator-property-list-key {
- padding-left: 8px;
-}
-
-.phabricator-property-list-value {
- color: {$darkgreytext};
- overflow: hidden;
- line-height: 17px;
-}
-
-.device-desktop .phabricator-property-list-value {
- width: 50%;
- margin-left: 1%;
- float: left;
- margin-bottom: 4px;
-}
-
-.device .phabricator-property-list-value {
- padding-left: 16px;
- margin-bottom: 8px;
-}
-
-.phabriator-property-list-view-end {
- clear: both;
-}
-
-.phabricator-property-list-section-header {
- color: {$bluetext};
- padding: 12px 16px 0px;
- text-transform: uppercase;
- font-weight: 700;
-}
-
-.phabricator-property-list-section-header + .phabricator-property-list-text-content {
- border-top: none;
-}
-
-.phabricator-property-list-text-content {
- padding: 12px 16px;
- background: #fff;
- overflow: hidden;
- border-top: 1px solid {$lightblueborder};
-}
-
-/* In the common case where we immediately follow a header, move back up 30px
- so we snuggle next to the header. */
-.device-desktop .phui-header-view
- + .phabricator-action-list-view {
- margin-top: -30px;
-}
-
-.device-desktop .phui-header-view
- + .phabricator-action-list-view
- + .phabricator-property-list-view {
- margin-top: 0px;
-}
-
-
-.phabricator-property-list-image {
- margin: auto;
- max-width: 95%;
-}
-
-.phabricator-property-list-audio {
- display: block;
- margin: 16px auto;
- width: 50%;
- min-width: 240px;
-}
-
-/* When tags appear in property lists, give them a little more vertical
- spacing. */
-.phabricator-property-list-view .phabricator-tag-view {
- margin: 2px 0;
-}
Index: webroot/rsrc/css/layout/phabricator-timeline-view.css
===================================================================
--- webroot/rsrc/css/layout/phabricator-timeline-view.css
+++ webroot/rsrc/css/layout/phabricator-timeline-view.css
@@ -154,9 +154,9 @@
}
.device .phabricator-timeline-extra {
- display: block;
- text-align: right;
+ display: inline-block;
line-height: 16px;
+ margin-left: 8px;
}
.phabricator-timeline-red .phabricator-timeline-border {
Index: webroot/rsrc/css/phui/phui-button.css
===================================================================
--- webroot/rsrc/css/phui/phui-button.css
+++ webroot/rsrc/css/phui/phui-button.css
@@ -156,8 +156,7 @@
border-bottom-color: {$greyborder};
}
-.dropdown-menu-frame a,
-.dropdown-menu-frame span {
+.dropdown-menu-frame .dropdown-menu-item {
display: block;
padding: 2px 10px;
clear: both;
@@ -166,10 +165,36 @@
white-space: nowrap;
}
-.dropdown-menu-frame span {
+.dropdown-menu-frame .dropdown-menu-item-disabled {
color: {$lightgreytext};
}
+.dropdown-menu-frame .phui-icon-view {
+ display: inline-block;
+ padding: 0;
+ margin: 2px 8px -2px 4px;
+}
+
+a.policy-control {
+ width: 240px;
+ text-align: left;
+}
+
+a.policy-control .caret {
+ float: right;
+}
+
+a.policy-control span.phui-icon-view {
+ /* NOTE: Nudge these icons a little bit. Should this be for all
+ dropdown buttons? */
+ top: 4px;
+ left: 7px;
+}
+
+.dropdown-menu-frame .dropdown-menu-item-selected {
+ background: {$lightblue};
+}
+
.dropdown-menu-frame a:hover {
background: #005588;
background-image: linear-gradient(to bottom, #3b86c4, #2b628f);
Index: webroot/rsrc/css/phui/phui-document.css
===================================================================
--- webroot/rsrc/css/phui/phui-document.css
+++ webroot/rsrc/css/phui/phui-document.css
@@ -86,21 +86,17 @@
text-shadow: 0 1px 2px #fff;
}
-.phui-document-content .phabricator-property-list-container {
+.phui-document-content .phui-property-list-container {
border-color: #dde8ef;
}
-.phui-document-content .phabricator-property-list-view {
+.phui-document-content .phui-property-list-view {
border: none;
box-shadow: none;
margin: 0;
background-color: #f6f7f8;
}
-.phui-document-content .phabricator-property-list-value {
- width: auto;
-}
-
.phui-document-content {
min-height: 240px;
background: #fff;
@@ -158,6 +154,6 @@
padding-right: 160px;
}
-.phui-document-view .phabricator-property-list-view {
+.phui-document-view .phui-property-list-view {
border-bottom: 1px solid {$thinblueborder};
}
Index: webroot/rsrc/css/phui/phui-header-view.css
===================================================================
--- webroot/rsrc/css/phui/phui-header-view.css
+++ webroot/rsrc/css/phui/phui-header-view.css
@@ -33,7 +33,7 @@
border-top-width: 0;
}
-.phabricator-property-list-view + .diviner-document-section {
+.phui-property-list-view + .diviner-document-section {
margin-top: -1px;
}
@@ -43,6 +43,16 @@
color: {$darkbluetext};
}
+.phui-header-view a {
+ color: {$darkbluetext};
+}
+
+.phui-header-divider {
+ margin: 0 4px;
+ font-weight: normal;
+ color: {$lightbluetext};
+}
+
body.device-phone .phui-header-view {
padding: 12px 8px;
}
Index: webroot/rsrc/css/phui/phui-list.css
===================================================================
--- webroot/rsrc/css/phui/phui-list.css
+++ webroot/rsrc/css/phui/phui-list.css
@@ -127,3 +127,37 @@
float: none;
border: none;
}
+
+/* - Status Colors -------------------------------------------------------------
+
+ Colors for navbars
+
+*/
+
+.phui-list-item-warn .phui-list-item-href {
+ color: #bc7837;
+}
+
+.phui-list-item-fail .phui-list-item-href {
+ color: {$red};
+}
+
+.phui-list-item-warn.phui-list-item-selected .phui-list-item-href,
+.phui-list-item-warn .phui-list-item-href:hover {
+ background: {$lightyellow};
+ color: #bc7837;
+}
+
+.phui-list-item-fail.phui-list-item-selected .phui-list-item-href,
+.phui-list-item-fail .phui-list-item-href:hover {
+ background: {$lightred};
+ color: {$red};
+}
+
+.phui-list-item-warn.phui-list-item-selected .phui-list-item-href:hover {
+ background: #fcf0bd;
+}
+
+.phui-list-item-fail.phui-list-item-selected .phui-list-item-href:hover {
+ background: #f5d3d0;
+}
Index: webroot/rsrc/css/phui/phui-object-box.css
===================================================================
--- webroot/rsrc/css/phui/phui-object-box.css
+++ webroot/rsrc/css/phui/phui-object-box.css
@@ -26,13 +26,6 @@
margin-bottom: 0;
}
-.device-desktop .phui-object-box
- .phui-header-shell + .phabricator-action-list-view {
- margin-top: 0;
- margin-bottom: -12px;
- border-width: 0 0 0 1px;
-}
-
.device-phone .phui-object-box {
margin-top: 0;
}
Index: webroot/rsrc/css/phui/phui-object-item-list-view.css
===================================================================
--- webroot/rsrc/css/phui/phui-object-item-list-view.css
+++ webroot/rsrc/css/phui/phui-object-item-list-view.css
@@ -153,14 +153,17 @@
.phui-object-item-with-1-actions .phui-object-item-content-box {
margin-right: 24px;
+ overflow: hidden;
}
.phui-object-item-with-2-actions .phui-object-item-content-box {
margin-right: 48px;
+ overflow: hidden;
}
.phui-object-item-with-3-actions .phui-object-item-content-box {
margin-right: 72px;
+ overflow: hidden;
}
@@ -267,7 +270,8 @@
margin-right: 30px;
}
-.device .phui-object-item-icon-label {
+.device .phui-object-item-icon-label,
+.device .phui-object-item-icon-none {
display: none;
}
Index: webroot/rsrc/css/phui/phui-property-list-view.css
===================================================================
--- /dev/null
+++ webroot/rsrc/css/phui/phui-property-list-view.css
@@ -0,0 +1,164 @@
+/**
+ * @provides phui-property-list-view-css
+ */
+
+.phui-property-list-view {
+ background-color: #fff;
+}
+
+.phui-property-list-view .keyboard-shortcuts-available {
+ float: right;
+ height: 16px;
+ margin: 12px 10px -28px 0px;
+ padding: 0px 20px 0px 0px;
+ vertical-align: middle;
+ color: {$greytext};
+ text-align: right;
+ font-size: 11px;
+ background:
+ url('/rsrc/image/icon/fatcow/key_question.png') right center no-repeat;
+}
+
+.device .keyboard-shortcuts-available {
+ display: none;
+}
+
+.phui-property-list-section +
+ .phui-property-list-section {
+ border-color: {$thinblueborder};
+ border-style: solid;
+ border-width: 1px 0 0;
+}
+
+.device-desktop .phui-property-list-container {
+ padding: 12px 0 12px 0;
+ width: 100%;
+}
+
+.device .phui-property-list-container {
+ padding: 12px 0 4px 0;
+}
+
+.phui-property-list-key {
+ color: {$bluetext};
+ font-weight: bold;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.device-desktop .phui-property-list-key {
+ width: 18%;
+ margin-left: 1%;
+ text-align: right;
+ float: left;
+ clear: left;
+ margin-bottom: 4px;
+}
+
+.device .phui-property-list-key {
+ padding-left: 8px;
+}
+
+.phui-property-list-value {
+ color: {$darkgreytext};
+ overflow: hidden;
+ line-height: 17px;
+}
+
+.device-desktop .phui-property-list-value {
+ width: 78%;
+ margin-left: 1%;
+ float: left;
+ margin-bottom: 4px;
+}
+
+.device .phui-property-list-value {
+ padding-left: 16px;
+ margin-bottom: 8px;
+}
+
+.phui-property-list-section-header {
+ color: {$bluetext};
+ padding: 12px 16px 0px;
+ text-transform: uppercase;
+ font-weight: 700;
+}
+
+.device .phui-property-list-section-header {
+ padding-left: 8px;
+}
+
+.phui-property-list-text-content {
+ padding: 12px 16px;
+ background: #fff;
+ overflow: hidden;
+}
+
+.device .phui-property-list-text-content {
+ padding: 8px;
+}
+
+/* In the common case where we immediately follow a header, move back up 30px
+ so we snuggle next to the header. */
+.device-desktop .phui-header-view
+ + .phabricator-action-list-view {
+ margin-top: -30px;
+}
+
+.device-desktop .phui-header-view
+ + .phabricator-action-list-view
+ + .phui-property-list-view {
+ margin-top: 0px;
+}
+
+
+.phui-property-list-image {
+ margin: auto;
+ max-width: 95%;
+}
+
+.phui-property-list-audio {
+ display: block;
+ margin: 16px auto;
+ width: 50%;
+ min-width: 240px;
+}
+
+/* When tags appear in property lists, give them a little more vertical
+ spacing. */
+.phui-property-list-view .phabricator-tag-view {
+ margin: 2px 0;
+}
+
+.phui-property-list-properties-wrap {
+ float: left;
+ width: 78%;
+}
+
+.device .phui-property-list-properties-wrap {
+ width: auto;
+ border: none;
+}
+
+.phui-property-list-actions {
+ width: 20%;
+ float: right;
+ margin-right: 12px;
+ border-left: 1px solid {$thinblueborder};
+}
+
+.device .phui-property-list-actions {
+ float: none;
+ width: auto;
+ margin: -12px 0 12px 0;
+}
+
+.phui-property-list-image-content img {
+ margin: 20px auto;
+ background: url('/rsrc/image/checker_light.png');
+ border: 1px solid {$lightblueborder};
+}
+
+.device-desktop .phui-property-list-image-content img:hover {
+ background: url('/rsrc/image/checker_dark.png');
+}
Index: webroot/rsrc/css/phui/phui-status.css
===================================================================
--- webroot/rsrc/css/phui/phui-status.css
+++ webroot/rsrc/css/phui/phui-status.css
@@ -26,6 +26,11 @@
padding: 3px 0;
}
-.phui-status-item-highlighted {
+.phui-status-item-highlighted td {
background-color: {$lightyellow};
}
+
+.phui-status-item-highlighted td.phui-status-item-note {
+ background-color: transparent;
+ padding-left: 4px;
+}
Index: webroot/rsrc/css/sprite-icons.css
===================================================================
--- webroot/rsrc/css/sprite-icons.css
+++ webroot/rsrc/css/sprite-icons.css
@@ -13,7 +13,7 @@
only screen and (-webkit-min-device-pixel-ratio: 1.5) {
.sprite-icons {
background-image: url(/rsrc/image/sprite-icons-X2.png);
- background-size: 225px 225px;
+ background-size: 225px 240px;
}
}
@@ -38,842 +38,914 @@
background-position: -60px 0px;
}
-.icons-check {
+.icons-calendar {
background-position: -75px 0px;
}
-.icons-comment {
+.icons-check {
background-position: -90px 0px;
}
-.icons-computer {
+.icons-comment {
background-position: -105px 0px;
}
-.icons-create {
+.icons-computer {
background-position: -120px 0px;
}
-.icons-delete {
+.icons-create {
background-position: -135px 0px;
}
-.icons-disable {
+.icons-data {
background-position: -150px 0px;
}
-.icons-dislike {
+.icons-delete {
background-position: -165px 0px;
}
-.icons-download-alt {
+.icons-disable {
background-position: -180px 0px;
}
-.icons-download {
+.icons-dislike {
background-position: -195px 0px;
}
-.icons-edit {
+.icons-download-alt {
background-position: -210px 0px;
}
-.icons-enable {
+.icons-download {
background-position: 0px -15px;
}
-.icons-file {
+.icons-edit {
background-position: -15px -15px;
}
-.icons-flag-0 {
+.icons-enable {
background-position: -30px -15px;
}
-.icons-flag-1 {
+.icons-file {
background-position: -45px -15px;
}
-.icons-flag-2 {
+.icons-film {
background-position: -60px -15px;
}
-.icons-flag-3 {
+.icons-flag-0 {
background-position: -75px -15px;
}
-.icons-flag-4 {
+.icons-flag-1 {
background-position: -90px -15px;
}
-.icons-flag-5 {
+.icons-flag-2 {
background-position: -105px -15px;
}
-.icons-flag-6 {
+.icons-flag-3 {
background-position: -120px -15px;
}
-.icons-flag-7 {
+.icons-flag-4 {
background-position: -135px -15px;
}
-.icons-flag-ghost {
+.icons-flag-5 {
background-position: -150px -15px;
}
-.icons-flag {
+.icons-flag-6 {
background-position: -165px -15px;
}
-.icons-folder-open {
+.icons-flag-7 {
background-position: -180px -15px;
}
-.icons-fork {
+.icons-flag-ghost {
background-position: -195px -15px;
}
-.icons-herald {
+.icons-flag {
background-position: -210px -15px;
}
-.icons-highlight {
+.icons-folder-open {
background-position: 0px -30px;
}
-.icons-history {
+.icons-fork {
background-position: -15px -30px;
}
-.icons-home {
+.icons-herald {
background-position: -30px -30px;
}
-.icons-image {
+.icons-highlight {
background-position: -45px -30px;
}
-.icons-like {
+.icons-history {
background-position: -60px -30px;
}
-.icons-link {
+.icons-home {
background-position: -75px -30px;
}
-.icons-lint-info {
+.icons-image {
background-position: -90px -30px;
}
-.icons-lint-ok {
+.icons-like {
background-position: -105px -30px;
}
-.icons-lint-warning {
+.icons-link {
background-position: -120px -30px;
}
-.icons-lock {
+.icons-lint-info {
background-position: -135px -30px;
}
-.icons-love {
+.icons-lint-ok {
background-position: -150px -30px;
}
-.icons-lower-priority {
+.icons-lint-warning {
background-position: -165px -30px;
}
-.icons-merge {
+.icons-lock {
background-position: -180px -30px;
}
-.icons-message {
+.icons-love {
background-position: -195px -30px;
}
-.icons-meta-mta {
+.icons-lower-priority {
background-position: -210px -30px;
}
-.icons-move {
+.icons-merge {
background-position: 0px -45px;
}
-.icons-new {
+.icons-message {
background-position: -15px -45px;
}
-.icons-none {
+.icons-meta-mta {
background-position: -30px -45px;
}
-.icons-normal-priority {
+.icons-move {
background-position: -45px -45px;
}
-.icons-perflab {
+.icons-music {
background-position: -60px -45px;
}
-.icons-preview {
+.icons-new {
background-position: -75px -45px;
}
-.icons-project {
+.icons-none {
background-position: -90px -45px;
}
-.icons-raise-priority {
+.icons-normal-priority {
background-position: -105px -45px;
}
-.icons-refresh {
+.icons-perflab {
background-position: -120px -45px;
}
-.icons-remove {
+.icons-preview {
background-position: -135px -45px;
}
-.icons-search {
+.icons-project {
background-position: -150px -45px;
}
-.icons-start-sandcastle {
+.icons-raise-priority {
background-position: -165px -45px;
}
-.icons-tag {
+.icons-refresh {
background-position: -180px -45px;
}
-.icons-transcript {
+.icons-remove {
background-position: -195px -45px;
}
-.icons-undo {
+.icons-search {
background-position: -210px -45px;
}
-.icons-unlock {
+.icons-start-sandcastle {
background-position: 0px -60px;
}
-.icons-unmerge {
+.icons-tag {
background-position: -15px -60px;
}
-.icons-unpublish {
+.icons-transcript {
background-position: -30px -60px;
}
-.icons-upload {
+.icons-undo {
background-position: -45px -60px;
}
-.icons-user {
+.icons-unlock {
background-position: -60px -60px;
}
-.icons-warning {
+.icons-unmerge {
background-position: -75px -60px;
}
-.icons-world {
+.icons-unpublish {
background-position: -90px -60px;
}
-.icons-action-menu-grey {
+.icons-upload {
background-position: -105px -60px;
}
-.icons-arrow_left-grey {
+.icons-user {
background-position: -120px -60px;
}
-.icons-arrow_right-grey {
+.icons-warning {
background-position: -135px -60px;
}
-.icons-attach-grey {
+.icons-world {
background-position: -150px -60px;
}
-.icons-blame-grey {
+.icons-wrench {
background-position: -165px -60px;
}
-.icons-check-grey {
+.icons-zip {
background-position: -180px -60px;
}
-.icons-comment-grey {
+.icons-action-menu-grey {
background-position: -195px -60px;
}
-.icons-computer-grey {
+.icons-arrow_left-grey {
background-position: -210px -60px;
}
-.icons-create-grey {
+.icons-arrow_right-grey {
background-position: 0px -75px;
}
-.icons-delete-grey {
+.icons-attach-grey {
background-position: -15px -75px;
}
-.icons-disable-grey {
+.icons-blame-grey {
background-position: -30px -75px;
}
-.icons-dislike-grey {
+.icons-calendar-grey {
background-position: -45px -75px;
}
-.icons-download-alt-grey {
+.icons-check-grey {
background-position: -60px -75px;
}
-.icons-download-grey {
+.icons-comment-grey {
background-position: -75px -75px;
}
-.icons-edit-grey {
+.icons-computer-grey {
background-position: -90px -75px;
}
-.icons-enable-grey {
+.icons-create-grey {
background-position: -105px -75px;
}
-.icons-file-grey {
+.icons-data-grey {
background-position: -120px -75px;
}
-.icons-flag-0-grey {
+.icons-delete-grey {
background-position: -135px -75px;
}
-.icons-flag-1-grey {
+.icons-disable-grey {
background-position: -150px -75px;
}
-.icons-flag-2-grey {
+.icons-dislike-grey {
background-position: -165px -75px;
}
-.icons-flag-3-grey {
+.icons-download-alt-grey {
background-position: -180px -75px;
}
-.icons-flag-4-grey {
+.icons-download-grey {
background-position: -195px -75px;
}
-.icons-flag-5-grey {
+.icons-edit-grey {
+ background-position: -210px -75px;
+}
+
+.icons-enable-grey {
background-position: 0px -90px;
}
-.icons-flag-6-grey {
+.icons-file-grey {
background-position: -15px -90px;
}
-.icons-flag-7-grey {
+.icons-film-grey {
background-position: -30px -90px;
}
-.icons-flag-ghost-grey {
+.icons-flag-0-grey {
background-position: -45px -90px;
}
-.icons-flag-grey {
+.icons-flag-1-grey {
background-position: -60px -90px;
}
-.icons-folder-open-grey {
+.icons-flag-2-grey {
background-position: -75px -90px;
}
-.icons-fork-grey {
+.icons-flag-3-grey {
background-position: -90px -90px;
}
-.icons-herald-grey {
+.icons-flag-4-grey {
background-position: -105px -90px;
}
-.icons-highlight-grey {
+.icons-flag-5-grey {
background-position: -120px -90px;
}
-.icons-history-grey {
+.icons-flag-6-grey {
background-position: -135px -90px;
}
-.icons-home-grey {
+.icons-flag-7-grey {
background-position: -150px -90px;
}
-.icons-image-grey {
+.icons-flag-ghost-grey {
background-position: -165px -90px;
}
-.icons-like-grey {
+.icons-flag-grey {
background-position: -180px -90px;
}
-.icons-link-grey {
+.icons-folder-open-grey {
background-position: -195px -90px;
}
-.icons-lint-info-grey {
+.icons-fork-grey {
+ background-position: -210px -90px;
+}
+
+.icons-herald-grey {
background-position: 0px -105px;
}
-.icons-lint-ok-grey {
+.icons-highlight-grey {
background-position: -15px -105px;
}
-.icons-lint-warning-grey {
+.icons-history-grey {
background-position: -30px -105px;
}
-.icons-lock-grey {
+.icons-home-grey {
background-position: -45px -105px;
}
-.icons-love-grey {
+.icons-image-grey {
background-position: -60px -105px;
}
-.icons-lower-priority-grey {
+.icons-like-grey {
background-position: -75px -105px;
}
-.icons-merge-grey {
+.icons-link-grey {
background-position: -90px -105px;
}
-.icons-message-grey {
+.icons-lint-info-grey {
background-position: -105px -105px;
}
-.icons-meta-mta-grey {
+.icons-lint-ok-grey {
background-position: -120px -105px;
}
-.icons-move-grey {
+.icons-lint-warning-grey {
background-position: -135px -105px;
}
-.icons-new-grey {
+.icons-lock-grey {
background-position: -150px -105px;
}
-.icons-none-grey {
+.icons-love-grey {
background-position: -165px -105px;
}
-.icons-normal-priority-grey {
+.icons-lower-priority-grey {
background-position: -180px -105px;
}
-.icons-perflab-grey {
+.icons-merge-grey {
background-position: -195px -105px;
}
-.icons-preview-grey {
+.icons-message-grey {
+ background-position: -210px -105px;
+}
+
+.icons-meta-mta-grey {
background-position: 0px -120px;
}
-.icons-project-grey {
+.icons-move-grey {
background-position: -15px -120px;
}
-.icons-raise-priority-grey {
+.icons-music-grey {
background-position: -30px -120px;
}
-.icons-refresh-grey {
+.icons-new-grey {
background-position: -45px -120px;
}
-.icons-remove-grey {
+.icons-none-grey {
background-position: -60px -120px;
}
-.icons-search-grey {
+.icons-normal-priority-grey {
background-position: -75px -120px;
}
-.icons-start-sandcastle-grey {
+.icons-perflab-grey {
background-position: -90px -120px;
}
-.icons-tag-grey {
+.icons-preview-grey {
background-position: -105px -120px;
}
-.icons-transcript-grey {
+.icons-project-grey {
background-position: -120px -120px;
}
-.icons-undo-grey {
+.icons-raise-priority-grey {
background-position: -135px -120px;
}
-.icons-unlock-grey {
+.icons-refresh-grey {
background-position: -150px -120px;
}
-.icons-unmerge-grey {
+.icons-remove-grey {
background-position: -165px -120px;
}
-.icons-unpublish-grey {
+.icons-search-grey {
background-position: -180px -120px;
}
-.icons-upload-grey {
+.icons-start-sandcastle-grey {
background-position: -195px -120px;
}
-.icons-user-grey {
+.icons-tag-grey {
+ background-position: -210px -120px;
+}
+
+.icons-transcript-grey {
background-position: 0px -135px;
}
-.icons-warning-grey {
+.icons-undo-grey {
background-position: -15px -135px;
}
-.icons-world-grey {
+.icons-unlock-grey {
background-position: -30px -135px;
}
-.icons-action-menu-white, .device-desktop .phabricator-action-view:hover .icons-action-menu, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-action-menu {
+.icons-unmerge-grey {
background-position: -45px -135px;
}
-.icons-arrow_left-white, .device-desktop .phabricator-action-view:hover .icons-arrow_left, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-arrow_left {
+.icons-unpublish-grey {
background-position: -60px -135px;
}
-.icons-arrow_right-white, .device-desktop .phabricator-action-view:hover .icons-arrow_right, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-arrow_right {
+.icons-upload-grey {
background-position: -75px -135px;
}
-.icons-attach-white, .device-desktop .phabricator-action-view:hover .icons-attach, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-attach {
+.icons-user-grey {
background-position: -90px -135px;
}
-.icons-blame-white, .device-desktop .phabricator-action-view:hover .icons-blame, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-blame {
+.icons-warning-grey {
background-position: -105px -135px;
}
-.icons-check-white, .device-desktop .phabricator-action-view:hover .icons-check, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-check {
+.icons-world-grey {
background-position: -120px -135px;
}
-.icons-comment-white, .device-desktop .phabricator-action-view:hover .icons-comment, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-comment {
+.icons-wrench-grey {
background-position: -135px -135px;
}
-.icons-computer-white, .device-desktop .phabricator-action-view:hover .icons-computer, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-computer {
+.icons-zip-grey {
background-position: -150px -135px;
}
-.icons-create-white, .device-desktop .phabricator-action-view:hover .icons-create, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-create {
+.icons-action-menu-white, .device-desktop .phabricator-action-view:hover .icons-action-menu, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-action-menu {
background-position: -165px -135px;
}
-.icons-delete-white, .device-desktop .phabricator-action-view:hover .icons-delete, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-delete {
+.icons-arrow_left-white, .device-desktop .phabricator-action-view:hover .icons-arrow_left, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-arrow_left {
background-position: -180px -135px;
}
-.icons-disable-white, .device-desktop .phabricator-action-view:hover .icons-disable, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-disable {
+.icons-arrow_right-white, .device-desktop .phabricator-action-view:hover .icons-arrow_right, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-arrow_right {
background-position: -195px -135px;
}
-.icons-dislike-white, .device-desktop .phabricator-action-view:hover .icons-dislike, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-dislike {
+.icons-attach-white, .device-desktop .phabricator-action-view:hover .icons-attach, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-attach {
+ background-position: -210px -135px;
+}
+
+.icons-blame-white, .device-desktop .phabricator-action-view:hover .icons-blame, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-blame {
background-position: 0px -150px;
}
-.icons-download-alt-white, .device-desktop .phabricator-action-view:hover .icons-download-alt, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-download-alt {
+.icons-calendar-white, .device-desktop .phabricator-action-view:hover .icons-calendar, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-calendar {
background-position: -15px -150px;
}
-.icons-download-white, .device-desktop .phabricator-action-view:hover .icons-download, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-download {
+.icons-check-white, .device-desktop .phabricator-action-view:hover .icons-check, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-check {
background-position: -30px -150px;
}
-.icons-edit-white, .device-desktop .phabricator-action-view:hover .icons-edit, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-edit {
+.icons-comment-white, .device-desktop .phabricator-action-view:hover .icons-comment, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-comment {
background-position: -45px -150px;
}
-.icons-enable-white, .device-desktop .phabricator-action-view:hover .icons-enable, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-enable {
+.icons-computer-white, .device-desktop .phabricator-action-view:hover .icons-computer, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-computer {
background-position: -60px -150px;
}
-.icons-file-white, .device-desktop .phabricator-action-view:hover .icons-file, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-file {
+.icons-create-white, .device-desktop .phabricator-action-view:hover .icons-create, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-create {
background-position: -75px -150px;
}
-.icons-flag-0-white, .device-desktop .phabricator-action-view:hover .icons-flag-0, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-0 {
+.icons-data-white, .device-desktop .phabricator-action-view:hover .icons-data, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-data {
background-position: -90px -150px;
}
-.icons-flag-1-white, .device-desktop .phabricator-action-view:hover .icons-flag-1, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-1 {
+.icons-delete-white, .device-desktop .phabricator-action-view:hover .icons-delete, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-delete {
background-position: -105px -150px;
}
-.icons-flag-2-white, .device-desktop .phabricator-action-view:hover .icons-flag-2, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-2 {
+.icons-disable-white, .device-desktop .phabricator-action-view:hover .icons-disable, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-disable {
background-position: -120px -150px;
}
-.icons-flag-3-white, .device-desktop .phabricator-action-view:hover .icons-flag-3, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-3 {
+.icons-dislike-white, .device-desktop .phabricator-action-view:hover .icons-dislike, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-dislike {
background-position: -135px -150px;
}
-.icons-flag-4-white, .device-desktop .phabricator-action-view:hover .icons-flag-4, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-4 {
+.icons-download-alt-white, .device-desktop .phabricator-action-view:hover .icons-download-alt, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-download-alt {
background-position: -150px -150px;
}
-.icons-flag-5-white, .device-desktop .phabricator-action-view:hover .icons-flag-5, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-5 {
+.icons-download-white, .device-desktop .phabricator-action-view:hover .icons-download, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-download {
background-position: -165px -150px;
}
-.icons-flag-6-white, .device-desktop .phabricator-action-view:hover .icons-flag-6, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-6 {
+.icons-edit-white, .device-desktop .phabricator-action-view:hover .icons-edit, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-edit {
background-position: -180px -150px;
}
-.icons-flag-7-white, .device-desktop .phabricator-action-view:hover .icons-flag-7, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-7 {
+.icons-enable-white, .device-desktop .phabricator-action-view:hover .icons-enable, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-enable {
background-position: -195px -150px;
}
-.icons-flag-ghost-white, .device-desktop .phabricator-action-view:hover .icons-flag-ghost, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-ghost {
+.icons-file-white, .device-desktop .phabricator-action-view:hover .icons-file, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-file {
+ background-position: -210px -150px;
+}
+
+.icons-film-white, .device-desktop .phabricator-action-view:hover .icons-film, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-film {
background-position: 0px -165px;
}
-.icons-flag-white, .device-desktop .phabricator-action-view:hover .icons-flag, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag {
+.icons-flag-0-white, .device-desktop .phabricator-action-view:hover .icons-flag-0, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-0 {
background-position: -15px -165px;
}
-.icons-folder-open-white, .device-desktop .phabricator-action-view:hover .icons-folder-open, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-folder-open {
+.icons-flag-1-white, .device-desktop .phabricator-action-view:hover .icons-flag-1, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-1 {
background-position: -30px -165px;
}
-.icons-fork-white, .device-desktop .phabricator-action-view:hover .icons-fork, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-fork {
+.icons-flag-2-white, .device-desktop .phabricator-action-view:hover .icons-flag-2, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-2 {
background-position: -45px -165px;
}
-.icons-herald-white, .device-desktop .phabricator-action-view:hover .icons-herald, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-herald {
+.icons-flag-3-white, .device-desktop .phabricator-action-view:hover .icons-flag-3, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-3 {
background-position: -60px -165px;
}
-.icons-highlight-white, .device-desktop .phabricator-action-view:hover .icons-highlight, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-highlight {
+.icons-flag-4-white, .device-desktop .phabricator-action-view:hover .icons-flag-4, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-4 {
background-position: -75px -165px;
}
-.icons-history-white, .device-desktop .phabricator-action-view:hover .icons-history, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-history {
+.icons-flag-5-white, .device-desktop .phabricator-action-view:hover .icons-flag-5, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-5 {
background-position: -90px -165px;
}
-.icons-home-white, .device-desktop .phabricator-action-view:hover .icons-home, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-home {
+.icons-flag-6-white, .device-desktop .phabricator-action-view:hover .icons-flag-6, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-6 {
background-position: -105px -165px;
}
-.icons-image-white, .device-desktop .phabricator-action-view:hover .icons-image, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-image {
+.icons-flag-7-white, .device-desktop .phabricator-action-view:hover .icons-flag-7, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-7 {
background-position: -120px -165px;
}
-.icons-like-white, .device-desktop .phabricator-action-view:hover .icons-like, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-like {
+.icons-flag-ghost-white, .device-desktop .phabricator-action-view:hover .icons-flag-ghost, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag-ghost {
background-position: -135px -165px;
}
-.icons-link-white, .device-desktop .phabricator-action-view:hover .icons-link, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-link {
+.icons-flag-white, .device-desktop .phabricator-action-view:hover .icons-flag, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-flag {
background-position: -150px -165px;
}
-.icons-lint-info-white, .device-desktop .phabricator-action-view:hover .icons-lint-info, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-lint-info {
+.icons-folder-open-white, .device-desktop .phabricator-action-view:hover .icons-folder-open, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-folder-open {
background-position: -165px -165px;
}
-.icons-lint-ok-white, .device-desktop .phabricator-action-view:hover .icons-lint-ok, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-lint-ok {
+.icons-fork-white, .device-desktop .phabricator-action-view:hover .icons-fork, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-fork {
background-position: -180px -165px;
}
-.icons-lint-warning-white, .device-desktop .phabricator-action-view:hover .icons-lint-warning, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-lint-warning {
+.icons-herald-white, .device-desktop .phabricator-action-view:hover .icons-herald, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-herald {
background-position: -195px -165px;
}
-.icons-lock-white, .device-desktop .phabricator-action-view:hover .icons-lock, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-lock {
+.icons-highlight-white, .device-desktop .phabricator-action-view:hover .icons-highlight, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-highlight {
+ background-position: -210px -165px;
+}
+
+.icons-history-white, .device-desktop .phabricator-action-view:hover .icons-history, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-history {
background-position: 0px -180px;
}
-.icons-love-white, .device-desktop .phabricator-action-view:hover .icons-love, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-love {
+.icons-home-white, .device-desktop .phabricator-action-view:hover .icons-home, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-home {
background-position: -15px -180px;
}
-.icons-lower-priority-white, .device-desktop .phabricator-action-view:hover .icons-lower-priority, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-lower-priority {
+.icons-image-white, .device-desktop .phabricator-action-view:hover .icons-image, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-image {
background-position: -30px -180px;
}
-.icons-merge-white, .device-desktop .phabricator-action-view:hover .icons-merge, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-merge {
+.icons-like-white, .device-desktop .phabricator-action-view:hover .icons-like, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-like {
background-position: -45px -180px;
}
-.icons-message-white, .device-desktop .phabricator-action-view:hover .icons-message, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-message {
+.icons-link-white, .device-desktop .phabricator-action-view:hover .icons-link, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-link {
background-position: -60px -180px;
}
-.icons-meta-mta-white, .device-desktop .phabricator-action-view:hover .icons-meta-mta, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-meta-mta {
+.icons-lint-info-white, .device-desktop .phabricator-action-view:hover .icons-lint-info, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-lint-info {
background-position: -75px -180px;
}
-.icons-move-white, .device-desktop .phabricator-action-view:hover .icons-move, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-move {
+.icons-lint-ok-white, .device-desktop .phabricator-action-view:hover .icons-lint-ok, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-lint-ok {
background-position: -90px -180px;
}
-.icons-new-white, .device-desktop .phabricator-action-view:hover .icons-new, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-new {
+.icons-lint-warning-white, .device-desktop .phabricator-action-view:hover .icons-lint-warning, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-lint-warning {
background-position: -105px -180px;
}
-.icons-none-white, .device-desktop .phabricator-action-view:hover .icons-none, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-none {
+.icons-lock-white, .device-desktop .phabricator-action-view:hover .icons-lock, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-lock {
background-position: -120px -180px;
}
-.icons-normal-priority-white, .device-desktop .phabricator-action-view:hover .icons-normal-priority, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-normal-priority {
+.icons-love-white, .device-desktop .phabricator-action-view:hover .icons-love, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-love {
background-position: -135px -180px;
}
-.icons-perflab-white, .device-desktop .phabricator-action-view:hover .icons-perflab, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-perflab {
+.icons-lower-priority-white, .device-desktop .phabricator-action-view:hover .icons-lower-priority, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-lower-priority {
background-position: -150px -180px;
}
-.icons-preview-white, .device-desktop .phabricator-action-view:hover .icons-preview, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-preview {
+.icons-merge-white, .device-desktop .phabricator-action-view:hover .icons-merge, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-merge {
background-position: -165px -180px;
}
-.icons-project-white, .device-desktop .phabricator-action-view:hover .icons-project, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-project {
+.icons-message-white, .device-desktop .phabricator-action-view:hover .icons-message, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-message {
background-position: -180px -180px;
}
-.icons-raise-priority-white, .device-desktop .phabricator-action-view:hover .icons-raise-priority, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-raise-priority {
+.icons-meta-mta-white, .device-desktop .phabricator-action-view:hover .icons-meta-mta, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-meta-mta {
background-position: -195px -180px;
}
-.icons-refresh-white, .device-desktop .phabricator-action-view:hover .icons-refresh, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-refresh {
+.icons-move-white, .device-desktop .phabricator-action-view:hover .icons-move, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-move {
+ background-position: -210px -180px;
+}
+
+.icons-music-white, .device-desktop .phabricator-action-view:hover .icons-music, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-music {
background-position: 0px -195px;
}
-.icons-remove-white, .device-desktop .phabricator-action-view:hover .icons-remove, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-remove {
+.icons-new-white, .device-desktop .phabricator-action-view:hover .icons-new, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-new {
background-position: -15px -195px;
}
-.icons-search-white, .device-desktop .phabricator-action-view:hover .icons-search, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-search {
+.icons-none-white, .device-desktop .phabricator-action-view:hover .icons-none, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-none {
background-position: -30px -195px;
}
-.icons-start-sandcastle-white, .device-desktop .phabricator-action-view:hover .icons-start-sandcastle, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-start-sandcastle {
+.icons-normal-priority-white, .device-desktop .phabricator-action-view:hover .icons-normal-priority, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-normal-priority {
background-position: -45px -195px;
}
-.icons-tag-white, .device-desktop .phabricator-action-view:hover .icons-tag, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-tag {
+.icons-perflab-white, .device-desktop .phabricator-action-view:hover .icons-perflab, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-perflab {
background-position: -60px -195px;
}
-.icons-transcript-white, .device-desktop .phabricator-action-view:hover .icons-transcript, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-transcript {
+.icons-preview-white, .device-desktop .phabricator-action-view:hover .icons-preview, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-preview {
background-position: -75px -195px;
}
-.icons-undo-white, .device-desktop .phabricator-action-view:hover .icons-undo, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-undo {
+.icons-project-white, .device-desktop .phabricator-action-view:hover .icons-project, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-project {
background-position: -90px -195px;
}
-.icons-unlock-white, .device-desktop .phabricator-action-view:hover .icons-unlock, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-unlock {
+.icons-raise-priority-white, .device-desktop .phabricator-action-view:hover .icons-raise-priority, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-raise-priority {
background-position: -105px -195px;
}
-.icons-unmerge-white, .device-desktop .phabricator-action-view:hover .icons-unmerge, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-unmerge {
+.icons-refresh-white, .device-desktop .phabricator-action-view:hover .icons-refresh, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-refresh {
background-position: -120px -195px;
}
-.icons-unpublish-white, .device-desktop .phabricator-action-view:hover .icons-unpublish, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-unpublish {
+.icons-remove-white, .device-desktop .phabricator-action-view:hover .icons-remove, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-remove {
background-position: -135px -195px;
}
-.icons-upload-white, .device-desktop .phabricator-action-view:hover .icons-upload, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-upload {
+.icons-search-white, .device-desktop .phabricator-action-view:hover .icons-search, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-search {
background-position: -150px -195px;
}
-.icons-user-white, .device-desktop .phabricator-action-view:hover .icons-user, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-user {
+.icons-start-sandcastle-white, .device-desktop .phabricator-action-view:hover .icons-start-sandcastle, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-start-sandcastle {
background-position: -165px -195px;
}
-.icons-warning-white, .device-desktop .phabricator-action-view:hover .icons-warning, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-warning {
+.icons-tag-white, .device-desktop .phabricator-action-view:hover .icons-tag, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-tag {
background-position: -180px -195px;
}
-.icons-world-white, .device-desktop .phabricator-action-view:hover .icons-world, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-world {
+.icons-transcript-white, .device-desktop .phabricator-action-view:hover .icons-transcript, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-transcript {
background-position: -195px -195px;
}
-.remarkup-assist-b {
+.icons-undo-white, .device-desktop .phabricator-action-view:hover .icons-undo, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-undo {
+ background-position: -210px -195px;
+}
+
+.icons-unlock-white, .device-desktop .phabricator-action-view:hover .icons-unlock, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-unlock {
background-position: 0px -210px;
}
-.remarkup-assist-code {
+.icons-unmerge-white, .device-desktop .phabricator-action-view:hover .icons-unmerge, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-unmerge {
background-position: -15px -210px;
}
-.remarkup-assist-fullscreen {
+.icons-unpublish-white, .device-desktop .phabricator-action-view:hover .icons-unpublish, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-unpublish {
background-position: -30px -210px;
}
-.remarkup-control-fullscreen-mode .remarkup-assist-fullscreen {
+.icons-upload-white, .device-desktop .phabricator-action-view:hover .icons-upload, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-upload {
background-position: -45px -210px;
}
-.remarkup-assist-help {
+.icons-user-white, .device-desktop .phabricator-action-view:hover .icons-user, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-user {
background-position: -60px -210px;
}
-.remarkup-assist-i {
+.icons-warning-white, .device-desktop .phabricator-action-view:hover .icons-warning, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-warning {
background-position: -75px -210px;
}
-.remarkup-assist-image {
+.icons-world-white, .device-desktop .phabricator-action-view:hover .icons-world, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-world {
background-position: -90px -210px;
}
-.remarkup-assist-larger {
+.icons-wrench-white, .device-desktop .phabricator-action-view:hover .icons-wrench, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-wrench {
background-position: -105px -210px;
}
-.remarkup-assist-meme {
+.icons-zip-white, .device-desktop .phabricator-action-view:hover .icons-zip, .device-desktop .phui-list-sidenav .phui-list-item-href:hover .icons-zip {
background-position: -120px -210px;
}
-.remarkup-assist-ol {
+.remarkup-assist-b {
background-position: -135px -210px;
}
-.remarkup-assist-table {
+.remarkup-assist-code {
background-position: -150px -210px;
}
-.remarkup-assist-tag {
+.remarkup-assist-fullscreen {
background-position: -165px -210px;
}
-.remarkup-assist-tt {
+.remarkup-control-fullscreen-mode .remarkup-assist-fullscreen {
background-position: -180px -210px;
}
-.remarkup-assist-ul {
+.remarkup-assist-help {
background-position: -195px -210px;
}
+
+.remarkup-assist-i {
+ background-position: -210px -210px;
+}
+
+.remarkup-assist-image {
+ background-position: 0px -225px;
+}
+
+.remarkup-assist-larger {
+ background-position: -15px -225px;
+}
+
+.remarkup-assist-meme {
+ background-position: -30px -225px;
+}
+
+.remarkup-assist-ol {
+ background-position: -45px -225px;
+}
+
+.remarkup-assist-table {
+ background-position: -60px -225px;
+}
+
+.remarkup-assist-tag {
+ background-position: -75px -225px;
+}
+
+.remarkup-assist-tt {
+ background-position: -90px -225px;
+}
+
+.remarkup-assist-ul {
+ background-position: -105px -225px;
+}
Index: webroot/rsrc/css/sprite-login.css
===================================================================
--- webroot/rsrc/css/sprite-login.css
+++ webroot/rsrc/css/sprite-login.css
@@ -70,18 +70,22 @@
background-position: 0px -105px;
}
-.login-Phabricator {
+.login-Persona {
background-position: -35px -105px;
}
-.login-TwitchTV {
+.login-Phabricator {
background-position: -70px -105px;
}
-.login-Twitter {
+.login-TwitchTV {
background-position: -105px -105px;
}
-.login-Yahoo {
+.login-Twitter {
background-position: 0px -140px;
}
+
+.login-Yahoo {
+ background-position: -35px -140px;
+}
Index: webroot/rsrc/css/sprite-projects.css
===================================================================
--- /dev/null
+++ webroot/rsrc/css/sprite-projects.css
@@ -0,0 +1,243 @@
+/**
+ * @provides sprite-projects-css
+ * @generated
+ */
+
+.sprite-projects {
+ background-image: url(/rsrc/image/sprite-projects.png);
+ background-repeat: no-repeat;
+}
+
+@media
+only screen and (min-device-pixel-ratio: 1.5),
+only screen and (-webkit-min-device-pixel-ratio: 1.5) {
+ .sprite-projects {
+ background-image: url(/rsrc/image/sprite-projects-X2.png);
+ background-size: 357px 408px;
+ }
+}
+
+
+.projects_8ball {
+ background-position: 0px 0px;
+}
+
+.projects_alien {
+ background-position: -51px 0px;
+}
+
+.projects_annouce {
+ background-position: -102px 0px;
+}
+
+.projects_art {
+ background-position: -153px 0px;
+}
+
+.projects_award {
+ background-position: -204px 0px;
+}
+
+.projects_bacon {
+ background-position: -255px 0px;
+}
+
+.projects_bandaid {
+ background-position: -306px 0px;
+}
+
+.projects_beer {
+ background-position: 0px -51px;
+}
+
+.projects_bomb {
+ background-position: -51px -51px;
+}
+
+.projects_briefcase {
+ background-position: -102px -51px;
+}
+
+.projects_bug {
+ background-position: -153px -51px;
+}
+
+.projects_calendar {
+ background-position: -204px -51px;
+}
+
+.projects_cloud {
+ background-position: -255px -51px;
+}
+
+.projects_coffee {
+ background-position: -306px -51px;
+}
+
+.projects_creditcard {
+ background-position: 0px -102px;
+}
+
+.projects_death {
+ background-position: -51px -102px;
+}
+
+.projects_desktop {
+ background-position: -102px -102px;
+}
+
+.projects_dropbox {
+ background-position: -153px -102px;
+}
+
+.projects_education {
+ background-position: -204px -102px;
+}
+
+.projects_experimental {
+ background-position: -255px -102px;
+}
+
+.projects_facebook {
+ background-position: -306px -102px;
+}
+
+.projects_facility {
+ background-position: 0px -153px;
+}
+
+.projects_film {
+ background-position: -51px -153px;
+}
+
+.projects_forked {
+ background-position: -102px -153px;
+}
+
+.projects_games {
+ background-position: -153px -153px;
+}
+
+.projects_ghost {
+ background-position: -204px -153px;
+}
+
+.projects_gift {
+ background-position: -255px -153px;
+}
+
+.projects_globe {
+ background-position: -306px -153px;
+}
+
+.projects_golf {
+ background-position: 0px -204px;
+}
+
+.projects_heart {
+ background-position: -51px -204px;
+}
+
+.projects_intergalactic {
+ background-position: -102px -204px;
+}
+
+.projects_lock {
+ background-position: -153px -204px;
+}
+
+.projects_mail {
+ background-position: -204px -204px;
+}
+
+.projects_martini {
+ background-position: -255px -204px;
+}
+
+.projects_medical {
+ background-position: -306px -204px;
+}
+
+.projects_mobile {
+ background-position: 0px -255px;
+}
+
+.projects_music {
+ background-position: -51px -255px;
+}
+
+.projects_news {
+ background-position: -102px -255px;
+}
+
+.projects_orgchart {
+ background-position: -153px -255px;
+}
+
+.projects_peoples {
+ background-position: -204px -255px;
+}
+
+.projects_piechart {
+ background-position: -255px -255px;
+}
+
+.projects_poison {
+ background-position: -306px -255px;
+}
+
+.projects_putabirdonit {
+ background-position: 0px -306px;
+}
+
+.projects_radiate {
+ background-position: -51px -306px;
+}
+
+.projects_savings {
+ background-position: -102px -306px;
+}
+
+.projects_search {
+ background-position: -153px -306px;
+}
+
+.projects_shield {
+ background-position: -204px -306px;
+}
+
+.projects_speed {
+ background-position: -255px -306px;
+}
+
+.projects_sprint {
+ background-position: -306px -306px;
+}
+
+.projects_star {
+ background-position: 0px -357px;
+}
+
+.projects_storage {
+ background-position: -51px -357px;
+}
+
+.projects_tablet {
+ background-position: -102px -357px;
+}
+
+.projects_travel {
+ background-position: -153px -357px;
+}
+
+.projects_twitter {
+ background-position: -204px -357px;
+}
+
+.projects_warning {
+ background-position: -255px -357px;
+}
+
+.projects_whale {
+ background-position: -306px -357px;
+}
Index: webroot/rsrc/externals/javelin/core/init.js
===================================================================
--- webroot/rsrc/externals/javelin/core/init.js
+++ webroot/rsrc/externals/javelin/core/init.js
@@ -67,13 +67,15 @@
for (var ii = 0; ii < local_queue.length; ++ii) {
var evt = local_queue[ii];
- // Sometimes IE gives us events which throw when ".type" is accessed;
- // just ignore them since we can't meaningfully dispatch them. TODO:
- // figure out where these are coming from.
+ // Sometimes IE gives us events which throw when ".type" is accessed;
+ // just ignore them since we can't meaningfully dispatch them. TODO:
+ // figure out where these are coming from.
try { var test = evt.type; } catch (x) { continue; }
if (!loaded && evt.type == 'domready') {
- document.body && (document.body.id = null);
+ // NOTE: Firefox interprets "document.body.id = null" as the string
+ // literal "null".
+ document.body && (document.body.id = '');
loaded = true;
for (var jj = 0; jj < onload.length; jj++) {
onload[jj]();
Index: webroot/rsrc/externals/javelin/lib/Workflow.js
===================================================================
--- webroot/rsrc/externals/javelin/lib/Workflow.js
+++ webroot/rsrc/externals/javelin/lib/Workflow.js
@@ -88,6 +88,8 @@
return;
}
+ event.prevent();
+
// Get the button (which is sometimes actually another tag, like an <a />)
// which triggered the event. In particular, this makes sure we get the
// right node if there is a <button> with an <img /> inside it or
@@ -98,6 +100,13 @@
JX.Workflow._pop();
} else {
var form = event.getNode('jx-dialog');
+
+ // Issue a DOM event first, so form-oriented handlers can act.
+ var dom_event = JX.DOM.invoke(form, 'didWorkflowSubmit');
+ if (dom_event.getPrevented()) {
+ return;
+ }
+
var data = JX.DOM.convertFormToListOfPairs(form);
data.push([t.name, t.value || true]);
@@ -112,7 +121,6 @@
.start();
}
}
- event.prevent();
},
_getActiveWorkflow : function() {
var stack = JX.Workflow._stack;
Index: webroot/rsrc/js/application/auth/behavior-persona-login.js
===================================================================
--- /dev/null
+++ webroot/rsrc/js/application/auth/behavior-persona-login.js
@@ -0,0 +1,41 @@
+/**
+ * @provides javelin-behavior-persona-login
+ * @requires javelin-behavior
+ * javelin-resource
+ * javelin-stratcom
+ * javelin-workflow
+ * javelin-util
+ */
+
+JX.behavior('persona-login', function(config) {
+
+ JX.Stratcom.listen(
+ 'submit',
+ 'persona-login-form',
+ function(e) {
+ e.kill();
+ navigator.id.request();
+ });
+
+ var onloaded = function() {
+ // Before installing watch(), log the user out, because we know they don't
+ // have a valid session if they're hitting this page. If we don't do this,
+ // Persona may immediately trigger a login event, which prevents the user
+ // from selecting another authentication mechanism.
+ navigator.id.logout();
+
+ navigator.id.watch({
+ loggedInUser: null,
+ onlogin: onlogin,
+ onlogout: JX.bag
+ });
+ };
+
+ var onlogin = function(assertion) {
+ new JX.Workflow(config.loginURI, {assertion: assertion})
+ .start();
+ };
+
+ var persona_library = 'https://login.persona.org/include.js';
+ JX.Resource.load(persona_library, onloaded);
+});
Index: webroot/rsrc/js/application/herald/HeraldRuleEditor.js
===================================================================
--- webroot/rsrc/js/application/herald/HeraldRuleEditor.js
+++ webroot/rsrc/js/application/herald/HeraldRuleEditor.js
@@ -220,6 +220,7 @@
case 'tag':
case 'package':
case 'project':
+ case 'userorproject':
var tokenizer = this._newTokenizer(type);
input = tokenizer[0];
get_fn = tokenizer[1];
Index: webroot/rsrc/js/application/policy/behavior-policy-control.js
===================================================================
--- /dev/null
+++ webroot/rsrc/js/application/policy/behavior-policy-control.js
@@ -0,0 +1,121 @@
+/**
+ * @provides javelin-behavior-policy-control
+ * @requires javelin-behavior
+ * javelin-dom
+ * javelin-util
+ * phabricator-dropdown-menu
+ * phabricator-menu-item
+ * javelin-workflow
+ * @javelin
+ */
+JX.behavior('policy-control', function(config) {
+ var control = JX.$(config.controlID);
+ var input = JX.$(config.inputID);
+ var value = config.value;
+
+ var menu = new JX.PhabricatorDropdownMenu(control)
+ .setWidth(260);
+
+ menu.toggleAlignDropdownRight(false);
+
+ menu.listen('open', function() {
+ menu.clear();
+
+ for (var ii = 0; ii < config.groups.length; ii++) {
+ var group = config.groups[ii];
+
+ var header = new JX.PhabricatorMenuItem(config.labels[group], JX.bag);
+ header.setDisabled(true);
+ menu.addItem(header);
+
+ for (var jj = 0; jj < config.order[group].length; jj++) {
+ var phid = config.order[group][jj];
+
+ var onselect;
+ if (group == 'custom') {
+ onselect = JX.bind(null, function(phid) {
+ var uri = get_custom_uri(phid);
+
+ new JX.Workflow(uri)
+ .setHandler(function(response) {
+ if (!response.phid) {
+ return;
+ }
+
+ replace_policy(phid, response.phid, response.info);
+ select_policy(response.phid);
+ })
+ .start();
+
+ }, phid);
+ } else {
+ onselect = JX.bind(null, select_policy, phid);
+ }
+
+ var item = new JX.PhabricatorMenuItem(
+ render_option(phid, true),
+ onselect);
+
+ if (phid == value) {
+ item.setSelected(true);
+ }
+
+ menu.addItem(item);
+ }
+ }
+
+ });
+
+
+ var select_policy = function(phid) {
+ JX.DOM.setContent(
+ JX.DOM.find(control, 'span', 'policy-label'),
+ render_option(phid));
+
+ input.value = phid;
+ value = phid;
+ };
+
+
+ var render_option = function(phid, with_title) {
+ var option = config.options[phid];
+
+ var name = option.name;
+ if (with_title && (option.full != option.name)) {
+ name = JX.$N('span', {title: option.full}, name);
+ }
+
+ return [JX.$H(config.icons[option.icon]), name];
+ };
+
+
+ /**
+ * Get the workflow URI to create or edit a policy with a given PHID.
+ */
+ var get_custom_uri = function(phid) {
+ var uri = '/policy/edit/';
+ if (phid != config.customPlaceholder) {
+ uri += phid + '/';
+ }
+ return uri;
+ };
+
+
+ /**
+ * Replace an existing policy option with a new one. Used to swap out custom
+ * policies after the user edits them.
+ */
+ var replace_policy = function(old_phid, new_phid, info) {
+ config.options[new_phid] = info;
+ for (var k in config.order) {
+ for (var ii = 0; ii < config.order[k].length; ii++) {
+ if (config.order[k][ii] == old_phid) {
+ config.order[k][ii] = new_phid;
+ return;
+ }
+ }
+ }
+ };
+
+
+});
Index: webroot/rsrc/js/application/policy/behavior-policy-rule-editor.js
===================================================================
--- /dev/null
+++ webroot/rsrc/js/application/policy/behavior-policy-rule-editor.js
@@ -0,0 +1,179 @@
+/**
+ * @provides javelin-behavior-policy-rule-editor
+ * @requires javelin-behavior
+ * multirow-row-manager
+ * javelin-dom
+ * javelin-util
+ * phabricator-prefab
+ * javelin-tokenizer
+ * javelin-typeahead
+ * javelin-typeahead-preloaded-source
+ * javelin-json
+ */
+JX.behavior('policy-rule-editor', function(config) {
+ var root = JX.$(config.rootID);
+ var rows = [];
+ var data = {};
+
+ JX.DOM.listen(
+ root,
+ 'click',
+ 'create-rule',
+ function(e) {
+ e.kill();
+ new_rule(config.defaultRule);
+ });
+
+ JX.DOM.listen(
+ root,
+ 'change',
+ 'rule-select',
+ function(e) {
+ e.kill();
+
+ var row = e.getNode(JX.MultirowRowManager.getRowSigil());
+ var row_id = rules_manager.getRowID(row);
+
+ data[row_id].rule = data[row_id].ruleNode.value;
+ data[row_id].value = null;
+
+ redraw(row_id);
+ });
+
+ JX.DOM.listen(
+ JX.DOM.findAbove(root, 'form'),
+ ['submit', 'didWorkflowSubmit'],
+ null,
+ function(e) {
+ var rules = JX.DOM.find(e.getNode('tag:form'), 'input', 'rules');
+
+ var value = [];
+ for (var ii = 0; ii < rows.length; ii++) {
+ var row_data = data[rows[ii]];
+
+ var row_dict = {
+ action: row_data.actionNode.value,
+ rule: row_data.rule,
+ value: row_data.getValue()
+ };
+
+ value.push(row_dict);
+ }
+
+ rules.value = JX.JSON.stringify(value);
+ });
+
+
+ var rules_table = JX.DOM.find(root, 'table', 'rules');
+ var rules_manager = new JX.MultirowRowManager(rules_table);
+ rules_manager.listen(
+ 'row-removed',
+ function(row_id) {
+ delete data[row_id];
+ for (var ii = 0; ii < rows.length; ii++) {
+ if (rows[ii] == row_id) {
+ rows.splice(ii, 1);
+ break;
+ }
+ }
+ });
+
+
+ function new_rule(spec) {
+ var row = rules_manager.addRow([]);
+ var row_id = rules_manager.getRowID(row);
+
+ rows.push(row_id);
+ data[row_id] = JX.copy({}, spec);
+
+ redraw(row_id);
+ }
+
+ function redraw(row_id) {
+ var action_content = JX.Prefab.renderSelect(
+ config.actions,
+ data[row_id].action);
+ data[row_id].actionNode = action_content;
+ var action_cell = JX.$N('td', {className: "action-cell"}, action_content);
+
+ var rule_content = JX.Prefab.renderSelect(
+ config.rules,
+ data[row_id].rule,
+ {sigil: 'rule-select'});
+ data[row_id].ruleNode = rule_content;
+ var rule_cell = JX.$N('td', {className: "rule-cell"}, rule_content);
+
+ var input = render_input(data[row_id].rule, null);
+
+ var value_content = input.node;
+ data[row_id].getValue = input.get;
+ input.set(data[row_id].value);
+
+ var value_cell = JX.$N('td', {className: "value-cell"}, value_content);
+
+ rules_manager.updateRow(row_id, [action_cell, rule_cell, value_cell]);
+ }
+
+ function render_input(rule, value) {
+ var node, get_fn, set_fn;
+ var type = config.types[rule];
+ var template = config.templates[rule];
+
+ switch (type) {
+ case 'tokenizer':
+ node = JX.$H(template.markup).getNode();
+ node.id = '';
+
+ var datasource = new JX.TypeaheadPreloadedSource(template.uri);
+
+ var typeahead = new JX.Typeahead(node);
+ typeahead.setDatasource(datasource);
+
+ var tokenizer = new JX.Tokenizer(node);
+ tokenizer.setLimit(template.limit);
+ tokenizer.setTypeahead(typeahead);
+ tokenizer.setPlaceholder(template.placeholder);
+ tokenizer.start();
+
+ get_fn = function() { return JX.keys(tokenizer.getTokens()); };
+ set_fn = function(map) {
+ if (!map) {
+ return;
+ }
+ for (var k in map) {
+ tokenizer.addToken(k, map[k]);
+ }
+ };
+ break;
+ case 'none':
+ node = null;
+ get_fn = JX.bag;
+ set_fn = JX.bag;
+ break;
+ case 'select':
+ node = JX.Prefab.renderSelect(
+ config.templates[rule].options,
+ value);
+ get_fn = function() { return node.value; };
+ set_fn = function(v) { node.value = v; };
+ break;
+ default:
+ case 'text':
+ node = JX.$N('input', {type: 'text'});
+ get_fn = function() { return node.value; };
+ set_fn = function(v) { node.value = v; };
+ break;
+ }
+
+ return {
+ node: node,
+ get: get_fn,
+ set: set_fn
+ };
+ }
+
+ for (var ii = 0; ii < config.data.length; ii++) {
+ new_rule(config.data[ii]);
+ }
+
+});
Index: webroot/rsrc/js/core/DropdownMenu.js
===================================================================
--- webroot/rsrc/js/core/DropdownMenu.js
+++ webroot/rsrc/js/core/DropdownMenu.js
@@ -33,6 +33,11 @@
null,
JX.bind(this, this._onclickglobal));
+ JX.Stratcom.listen(
+ 'resize',
+ null,
+ JX.bind(this, this._onresize));
+
JX.PhabricatorDropdownMenu.listen(
'open',
JX.bind(this, this.close));
@@ -40,6 +45,10 @@
events : ['open'],
+ properties : {
+ width : null
+ },
+
members : {
_node : null,
_menu : null,
@@ -78,6 +87,11 @@
this._hide();
},
+ clear : function() {
+ this._items = [];
+ return this;
+ },
+
addItem : function(item) {
if (__DEV__) {
if (!(item instanceof JX.PhabricatorMenuItem)) {
@@ -104,6 +118,12 @@
if (!item) {
return;
}
+
+ if (item.getDisabled()) {
+ e.prevent();
+ return;
+ }
+
item.select();
e.prevent();
this.close();
@@ -132,6 +152,20 @@
_show : function() {
document.body.appendChild(this._menu);
+ if (this.getWidth()) {
+ new JX.Vector(this.getWidth(), null).setDim(this._menu);
+ }
+
+ this._onresize();
+
+ JX.DOM.alterClass(this._node, 'dropdown-open', true);
+ },
+
+ _onresize : function() {
+ if (!this._open) {
+ return;
+ }
+
var m = JX.Vector.getDim(this._menu);
var v = JX.$V(this._node);
@@ -143,8 +177,6 @@
v = v.add(0, d.y);
}
v.setPos(this._menu);
-
- JX.DOM.alterClass(this._node, 'dropdown-open', true);
},
_hide : function() {
Index: webroot/rsrc/js/core/DropdownMenuItem.js
===================================================================
--- webroot/rsrc/js/core/DropdownMenuItem.js
+++ webroot/rsrc/js/core/DropdownMenuItem.js
@@ -17,10 +17,26 @@
_action : null,
render : function() {
+ var classes = [];
+ classes.push('dropdown-menu-item');
+
+ if (this.getSelected()) {
+ classes.push('dropdown-menu-item-selected');
+ }
+
+ if (this.getDisabled()) {
+ classes.push('dropdown-menu-item-disabled');
+ }
+
+ var attrs = {
+ href: this.getHref(),
+ meta: { item: this },
+ className: classes.join(' ')
+ };
+
if (this.getDisabled()) {
- return JX.$N('span', this.getName());
+ return JX.$N('span', attrs, this.getName());
} else {
- var attrs = { href : this.getHref(), meta : { item : this } };
return JX.$N('a', attrs, this.getName());
}
},
@@ -31,9 +47,10 @@
},
properties : {
- name : '',
- href : '',
- disabled : false
+ name: '',
+ href: '',
+ disabled: false,
+ selected: false
}
});
Index: webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js
===================================================================
--- webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js
+++ webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js
@@ -3,12 +3,14 @@
* @requires javelin-behavior
* javelin-stratcom
* javelin-dom
+ * phabricator-phtize
* phabricator-textareautils
* javelin-workflow
* javelin-vector
*/
JX.behavior('phabricator-remarkup-assist', function(config) {
+ var pht = JX.phtize(config.pht);
var edit_mode = 'normal';
var edit_root = null;
@@ -100,13 +102,13 @@
switch (action) {
case 'b':
- update(area, '**', sel || 'bold text', '**');
+ update(area, '**', sel || pht('bold text'), '**');
break;
case 'i':
- update(area, '//', sel || 'italic text', '//');
+ update(area, '//', sel || pht('italic text'), '//');
break;
case 'tt':
- update(area, '`', sel || 'monospaced text', '`');
+ update(area, '`', sel || pht('monospaced text'), '`');
break;
case 'ul':
case 'ol':
@@ -114,7 +116,7 @@
if (sel) {
sel = sel.split("\n");
} else {
- sel = ["List Item"];
+ sel = [pht('List Item')];
}
sel = sel.join("\n" + ch);
update(area, ((r.start === 0) ? "" : "\n\n") + ch, sel, "\n\n");
@@ -126,7 +128,8 @@
update(area, ((r.start === 0) ? "" : "\n\n"), sel, "\n\n");
break;
case 'table':
- update(area, (r.start === 0 ? '' : '\n\n') + '| ', sel || 'data', ' |');
+ var prefix = (r.start === 0 ? '' : '\n\n');
+ update(area, prefix + '| ', sel || pht('data'), ' |');
break;
case 'meme':
new JX.Workflow('/macro/meme/create/')
Index: webroot/rsrc/js/core/behavior-workflow.js
===================================================================
--- webroot/rsrc/js/core/behavior-workflow.js
+++ webroot/rsrc/js/core/behavior-workflow.js
@@ -8,7 +8,7 @@
JX.behavior('workflow', function() {
- // Listen for both real
+ // Listen for both real and synthetic submit events.
JX.Stratcom.listen(
['submit', 'didSyntheticSubmit'],
['workflow', 'tag:form'],

File Metadata

Mime Type
text/plain
Expires
Mon, Jul 21, 8:34 AM (17 h, 26 m)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/gg/i3/hy5kgvswikde4ocl
Default Alt Text
D7339.largetrue.diff (575 KB)

Event Timeline