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 @@ +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 @@ -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 @@ +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 @@ 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 @@ +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 @@ 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 @@ +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 @@ +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 @@ +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 @@ 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[1-9]\d*)/' => 'HeraldRuleViewController', 'edit/(?:(?P[1-9]\d*)/)?' => 'HeraldRuleController', + 'disable/(?P[1-9]\d*)/(?P\w+)/' => + 'HeraldDisableController', 'history/(?:(?P[1-9]\d*)/)?' => 'HeraldRuleEditHistoryController', - 'delete/(?P[1-9]\d*)/' => 'HeraldDeleteController', 'test/' => 'HeraldTestConsoleController', 'transcript/' => 'HeraldTranscriptListController', 'transcript/(?P[1-9]\d*)/(?:(?P\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 @@ +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 @@ -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 @@ +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 @@ +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 @@ +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 @@ 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 @@ +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 @@ +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 ."; } 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 @@ 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 @@ 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 @@ + $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 @@ $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 @@ 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 @@ 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[^/]+)/(?P[^/]+)/' => 'PhabricatorPolicyExplainController', + 'edit/(?:(?P[^/]+)/)?' => 'PhabricatorPolicyEditController', ), ); } Index: src/applications/policy/capability/PhabricatorPolicyCapability.php =================================================================== --- /dev/null +++ src/applications/policy/capability/PhabricatorPolicyCapability.php @@ -0,0 +1,72 @@ +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 @@ + 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 @@ +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 @@ -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 @@ 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 @@ 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 @@ +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 @@ +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 @@ +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')->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 @@ +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')->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 @@ + 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 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 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 @@ +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 @@ +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 Other permitted PHID types. * @return list 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 @@ +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 @@ PhabricatorPropertyListView to render object properties.'); + 'Use PHUIPropertyListView 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
'. 'haiku. it is very bad.
'. '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 is not allowed inside inline script.'); } + if (strpos($data, ' 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 @@ +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 @@ +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 @@ +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 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 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 `` map. + * + * @param list Objects. + * @return map 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 Results from @{method:willFilterPage()}. + * @return list 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
%s
', $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`. - 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 @@ +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 @@ 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 ) // which triggered the event. In particular, this makes sure we get the // right node if there is a