diff --git a/resources/sql/patches/20131112.userverified.1.col.sql b/resources/sql/patches/20131112.userverified.1.col.sql new file mode 100644 index 000000000..0612e7982 --- /dev/null +++ b/resources/sql/patches/20131112.userverified.1.col.sql @@ -0,0 +1,10 @@ +ALTER TABLE {$NAMESPACE}_user.user + ADD isEmailVerified INT UNSIGNED NOT NULL; + +ALTER TABLE {$NAMESPACE}_user.user + ADD isApproved INT UNSIGNED NOT NULL; + +ALTER TABLE {$NAMESPACE}_user.user + ADD KEY `key_approved` (isApproved); + +UPDATE {$NAMESPACE}_user.user SET isApproved = 1; diff --git a/resources/sql/patches/20131112.userverified.2.mig.php b/resources/sql/patches/20131112.userverified.2.mig.php new file mode 100644 index 000000000..381fc966a --- /dev/null +++ b/resources/sql/patches/20131112.userverified.2.mig.php @@ -0,0 +1,33 @@ +establishConnection('w'); + +foreach (new LiskMigrationIterator($table) as $user) { + $username = $user->getUsername(); + echo "Migrating {$username}...\n"; + if ($user->getIsEmailVerified()) { + // Email already verified. + continue; + } + + $primary = $user->loadPrimaryEmail(); + if (!$primary) { + // No primary email. + continue; + } + + if (!$primary->getIsVerified()) { + // Primary email not verified. + continue; + } + + // Primary email is verified, so mark the account as verified. + queryfx( + $conn_w, + 'UPDATE %T SET isEmailVerified = 1 WHERE id = %d', + $table->getTableName(), + $user->getID()); +} + +echo "Done.\n"; diff --git a/scripts/ssh/ssh-exec.php b/scripts/ssh/ssh-exec.php index 5b518e01b..6d8763b90 100755 --- a/scripts/ssh/ssh-exec.php +++ b/scripts/ssh/ssh-exec.php @@ -1,108 +1,108 @@ #!/usr/bin/env php setTagline('receive SSH requests'); $args->setSynopsis(<<parse( array( array( 'name' => 'phabricator-ssh-user', 'param' => 'username', ), array( 'name' => 'ssh-command', 'param' => 'command', ), )); try { $user_name = $args->getArg('phabricator-ssh-user'); if (!strlen($user_name)) { throw new Exception("No username."); } $user = id(new PhabricatorUser())->loadOneWhere( 'userName = %s', $user_name); if (!$user) { throw new Exception("Invalid username."); } - if ($user->getIsDisabled()) { - throw new Exception("You have been exiled."); + if (!$user->isUserActivated()) { + throw new Exception(pht("Your account is not activated.")); } if ($args->getArg('ssh-command')) { $original_command = $args->getArg('ssh-command'); } else { $original_command = getenv('SSH_ORIGINAL_COMMAND'); } // Now, rebuild the original command. $original_argv = id(new PhutilShellLexer()) ->splitArguments($original_command); if (!$original_argv) { throw new Exception("No interactive logins."); } $command = head($original_argv); array_unshift($original_argv, 'phabricator-ssh-exec'); $original_args = new PhutilArgumentParser($original_argv); $workflows = array( new ConduitSSHWorkflow(), new DiffusionSSHSubversionServeWorkflow(), new DiffusionSSHMercurialServeWorkflow(), new DiffusionSSHGitUploadPackWorkflow(), new DiffusionSSHGitReceivePackWorkflow(), ); $workflow_names = mpull($workflows, 'getName', 'getName'); if (empty($workflow_names[$command])) { throw new Exception("Invalid command."); } $workflow = $original_args->parseWorkflows($workflows); $workflow->setUser($user); $sock_stdin = fopen('php://stdin', 'r'); if (!$sock_stdin) { throw new Exception("Unable to open stdin."); } $sock_stdout = fopen('php://stdout', 'w'); if (!$sock_stdout) { throw new Exception("Unable to open stdout."); } $sock_stderr = fopen('php://stderr', 'w'); if (!$sock_stderr) { throw new Exception("Unable to open stderr."); } $socket_channel = new PhutilSocketChannel( $sock_stdin, $sock_stdout); $error_channel = new PhutilSocketChannel(null, $sock_stderr); $metrics_channel = new PhutilMetricsChannel($socket_channel); $workflow->setIOChannel($metrics_channel); $workflow->setErrorChannel($error_channel); $err = $workflow->execute($original_args); $metrics_channel->flush(); $error_channel->flush(); } catch (Exception $ex) { fwrite(STDERR, "phabricator-ssh-exec: ".$ex->getMessage()."\n"); exit(1); } diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 0746bd8bb..012818dfc 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -1,4721 +1,4721 @@ array( 'hash' => 'ae90914d120ac3838ddc633b480343f3', 'uri' => '/res/ae90914d/rsrc/image/actions/edit.png', 'disk' => '/rsrc/image/actions/edit.png', 'type' => 'png', ), '/rsrc/image/apple-touch-icon.png' => array( 'hash' => '3380adf2dd4a5efa0885618bc5943640', 'uri' => '/res/3380adf2/rsrc/image/apple-touch-icon.png', 'disk' => '/rsrc/image/apple-touch-icon.png', 'type' => 'png', ), '/rsrc/image/avatar.png' => array( 'hash' => '1c5f255071537f05406adee86717ff27', 'uri' => '/res/1c5f2550/rsrc/image/avatar.png', 'disk' => '/rsrc/image/avatar.png', 'type' => 'png', ), '/rsrc/image/checker_dark.png' => array( 'hash' => '640f795343df76ebe5409aae6187e57f', 'uri' => '/res/640f7953/rsrc/image/checker_dark.png', 'disk' => '/rsrc/image/checker_dark.png', 'type' => 'png', ), '/rsrc/image/checker_light.png' => array( 'hash' => '7f8f3ef8beb0f2cc4cc69efb9e1c3308', 'uri' => '/res/7f8f3ef8/rsrc/image/checker_light.png', 'disk' => '/rsrc/image/checker_light.png', 'type' => 'png', ), '/rsrc/image/credit_cards.png' => array( 'hash' => '681448de424ea159b6ea68af04c046ae', 'uri' => '/res/681448de/rsrc/image/credit_cards.png', 'disk' => '/rsrc/image/credit_cards.png', 'type' => 'png', ), '/rsrc/image/darkload.gif' => array( 'hash' => '3a52cb7145d6e70f461fed21273117f2', 'uri' => '/res/3a52cb71/rsrc/image/darkload.gif', 'disk' => '/rsrc/image/darkload.gif', 'type' => 'gif', ), '/rsrc/image/divot.png' => array( 'hash' => '3be267bd11ea375bf68e808893718e0e', 'uri' => '/res/3be267bd/rsrc/image/divot.png', 'disk' => '/rsrc/image/divot.png', 'type' => 'png', ), '/rsrc/image/grippy_texture.png' => array( 'hash' => 'a8945e12ceeaddd5b491a8d81cfa19c1', 'uri' => '/res/a8945e12/rsrc/image/grippy_texture.png', 'disk' => '/rsrc/image/grippy_texture.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/arrow_branch.png' => array( 'hash' => 'f27b67520766e3d971722bcff703f3a8', 'uri' => '/res/f27b6752/rsrc/image/icon/fatcow/arrow_branch.png', 'disk' => '/rsrc/image/icon/fatcow/arrow_branch.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/arrow_merge.png' => array( 'hash' => 'c4bd97f3b1257439e2123ef69d2194d0', 'uri' => '/res/c4bd97f3/rsrc/image/icon/fatcow/arrow_merge.png', 'disk' => '/rsrc/image/icon/fatcow/arrow_merge.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/bullet_black.png' => array( 'hash' => 'c148284c84aa02ba1190dcf7e31c8985', 'uri' => '/res/c148284c/rsrc/image/icon/fatcow/bullet_black.png', 'disk' => '/rsrc/image/icon/fatcow/bullet_black.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/bullet_orange.png' => array( 'hash' => '397bd1c948d9aaac5e440a9270c3697a', 'uri' => '/res/397bd1c9/rsrc/image/icon/fatcow/bullet_orange.png', 'disk' => '/rsrc/image/icon/fatcow/bullet_orange.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/bullet_red.png' => array( 'hash' => '470e3b2c2ca84ebdd476271b681f421b', 'uri' => '/res/470e3b2c/rsrc/image/icon/fatcow/bullet_red.png', 'disk' => '/rsrc/image/icon/fatcow/bullet_red.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/calendar_edit.png' => array( 'hash' => 'de249c0f4f37bf5b2c69ff39ec5573fb', 'uri' => '/res/de249c0f/rsrc/image/icon/fatcow/calendar_edit.png', 'disk' => '/rsrc/image/icon/fatcow/calendar_edit.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/document_black.png' => array( 'hash' => '44d65a7f05a9c921719deedc160d68f7', 'uri' => '/res/44d65a7f/rsrc/image/icon/fatcow/document_black.png', 'disk' => '/rsrc/image/icon/fatcow/document_black.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/flag_blue.png' => array( 'hash' => '75a080492f900fbe489e4b27e403962b', 'uri' => '/res/75a08049/rsrc/image/icon/fatcow/flag_blue.png', 'disk' => '/rsrc/image/icon/fatcow/flag_blue.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/flag_finish.png' => array( 'hash' => '4af11fc7fab8e4610cbc3c88a02d4f78', 'uri' => '/res/4af11fc7/rsrc/image/icon/fatcow/flag_finish.png', 'disk' => '/rsrc/image/icon/fatcow/flag_finish.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/flag_ghost.png' => array( 'hash' => '14c9f30a37b43f276f27a27a924bf02d', 'uri' => '/res/14c9f30a/rsrc/image/icon/fatcow/flag_ghost.png', 'disk' => '/rsrc/image/icon/fatcow/flag_ghost.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/flag_green.png' => array( 'hash' => 'fed01374cd396cb774872762dcc447e1', 'uri' => '/res/fed01374/rsrc/image/icon/fatcow/flag_green.png', 'disk' => '/rsrc/image/icon/fatcow/flag_green.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/flag_orange.png' => array( 'hash' => '88008cb8bb99761a37e5a743e2455aeb', 'uri' => '/res/88008cb8/rsrc/image/icon/fatcow/flag_orange.png', 'disk' => '/rsrc/image/icon/fatcow/flag_orange.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/flag_pink.png' => array( 'hash' => '2f199f06ffc3dfc81b7561a057e0bc33', 'uri' => '/res/2f199f06/rsrc/image/icon/fatcow/flag_pink.png', 'disk' => '/rsrc/image/icon/fatcow/flag_pink.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/flag_purple.png' => array( 'hash' => '16358629dc86c39550b575586eb5df80', 'uri' => '/res/16358629/rsrc/image/icon/fatcow/flag_purple.png', 'disk' => '/rsrc/image/icon/fatcow/flag_purple.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/flag_red.png' => array( 'hash' => '210c28b4d93c439a499f5814f5e05772', 'uri' => '/res/210c28b4/rsrc/image/icon/fatcow/flag_red.png', 'disk' => '/rsrc/image/icon/fatcow/flag_red.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/flag_yellow.png' => array( 'hash' => 'bdfd73744a80bb80329ae50bc8a5f962', 'uri' => '/res/bdfd7374/rsrc/image/icon/fatcow/flag_yellow.png', 'disk' => '/rsrc/image/icon/fatcow/flag_yellow.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/folder.png' => array( 'hash' => '25e46cf9d210dde2242332296f79938c', 'uri' => '/res/25e46cf9/rsrc/image/icon/fatcow/folder.png', 'disk' => '/rsrc/image/icon/fatcow/folder.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/folder_go.png' => array( 'hash' => 'ba922ff7959309f51a14cb7ed5124d8b', 'uri' => '/res/ba922ff7/rsrc/image/icon/fatcow/folder_go.png', 'disk' => '/rsrc/image/icon/fatcow/folder_go.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/key_question.png' => array( 'hash' => '530a6448a4b91edec091a9292ccfd3d9', 'uri' => '/res/530a6448/rsrc/image/icon/fatcow/key_question.png', 'disk' => '/rsrc/image/icon/fatcow/key_question.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/link.png' => array( 'hash' => 'be1bea49b216548433014f3324902928', 'uri' => '/res/be1bea49/rsrc/image/icon/fatcow/link.png', 'disk' => '/rsrc/image/icon/fatcow/link.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/page_white_edit.png' => array( 'hash' => 'e7b7e7f2d9730bc80bc5c9eac1f3e36d', 'uri' => '/res/e7b7e7f2/rsrc/image/icon/fatcow/page_white_edit.png', 'disk' => '/rsrc/image/icon/fatcow/page_white_edit.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/page_white_link.png' => array( 'hash' => '1cfbad14412bda6c6f132dcc7c8725fd', 'uri' => '/res/1cfbad14/rsrc/image/icon/fatcow/page_white_link.png', 'disk' => '/rsrc/image/icon/fatcow/page_white_link.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/page_white_put.png' => array( 'hash' => 'bb7308aa5ac40137a8262da395a267fd', 'uri' => '/res/bb7308aa/rsrc/image/icon/fatcow/page_white_put.png', 'disk' => '/rsrc/image/icon/fatcow/page_white_put.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/page_white_text.png' => array( 'hash' => 'e47d590b626f617fb7d1d44e96e8fd11', 'uri' => '/res/e47d590b/rsrc/image/icon/fatcow/page_white_text.png', 'disk' => '/rsrc/image/icon/fatcow/page_white_text.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/source/conduit.png' => array( 'hash' => '1cae0656580aa3cd0b54b9d98306b1b9', 'uri' => '/res/1cae0656/rsrc/image/icon/fatcow/source/conduit.png', 'disk' => '/rsrc/image/icon/fatcow/source/conduit.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/source/email.png' => array( 'hash' => '93bdb3e168da1ed68f50c42125729d4e', 'uri' => '/res/93bdb3e1/rsrc/image/icon/fatcow/source/email.png', 'disk' => '/rsrc/image/icon/fatcow/source/email.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/source/fax.png' => array( 'hash' => 'd7dedf229841f2d041b347afd881596f', 'uri' => '/res/d7dedf22/rsrc/image/icon/fatcow/source/fax.png', 'disk' => '/rsrc/image/icon/fatcow/source/fax.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/source/mobile.png' => array( 'hash' => '786e7146d1e7d7318baf76c9d2baad97', 'uri' => '/res/786e7146/rsrc/image/icon/fatcow/source/mobile.png', 'disk' => '/rsrc/image/icon/fatcow/source/mobile.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/source/tablet.png' => array( 'hash' => '374cd40e4965be6b2fbdef4059d0ca05', 'uri' => '/res/374cd40e/rsrc/image/icon/fatcow/source/tablet.png', 'disk' => '/rsrc/image/icon/fatcow/source/tablet.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/source/web.png' => array( 'hash' => 'f4882a8f5619ba505ca033f72a340635', 'uri' => '/res/f4882a8f/rsrc/image/icon/fatcow/source/web.png', 'disk' => '/rsrc/image/icon/fatcow/source/web.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/thumbnails/default160x120.png' => array( 'hash' => '1b52ebd1fe0eee3ed0abfc382991b265', 'uri' => '/res/1b52ebd1/rsrc/image/icon/fatcow/thumbnails/default160x120.png', 'disk' => '/rsrc/image/icon/fatcow/thumbnails/default160x120.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/thumbnails/default60x45.png' => array( 'hash' => '048d851d8d1daad4754e891e734c1899', 'uri' => '/res/048d851d/rsrc/image/icon/fatcow/thumbnails/default60x45.png', 'disk' => '/rsrc/image/icon/fatcow/thumbnails/default60x45.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/thumbnails/image160x120.png' => array( 'hash' => '434acbd8dbbc2da9f09f6205a396eba1', 'uri' => '/res/434acbd8/rsrc/image/icon/fatcow/thumbnails/image160x120.png', 'disk' => '/rsrc/image/icon/fatcow/thumbnails/image160x120.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/thumbnails/image60x45.png' => array( 'hash' => '29f7872dc53588fe0b8f0b330c7ee23a', 'uri' => '/res/29f7872d/rsrc/image/icon/fatcow/thumbnails/image60x45.png', 'disk' => '/rsrc/image/icon/fatcow/thumbnails/image60x45.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/thumbnails/pdf160x120.png' => array( 'hash' => '39d2e22541658a3472ba41ae2fa548e5', 'uri' => '/res/39d2e225/rsrc/image/icon/fatcow/thumbnails/pdf160x120.png', 'disk' => '/rsrc/image/icon/fatcow/thumbnails/pdf160x120.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/thumbnails/pdf60x45.png' => array( 'hash' => 'b3572e9317cbed5184d12bdfabed2727', 'uri' => '/res/b3572e93/rsrc/image/icon/fatcow/thumbnails/pdf60x45.png', 'disk' => '/rsrc/image/icon/fatcow/thumbnails/pdf60x45.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/thumbnails/zip160x120.png' => array( 'hash' => 'e505108688a903b5cfb674707a289bcc', 'uri' => '/res/e5051086/rsrc/image/icon/fatcow/thumbnails/zip160x120.png', 'disk' => '/rsrc/image/icon/fatcow/thumbnails/zip160x120.png', 'type' => 'png', ), '/rsrc/image/icon/fatcow/thumbnails/zip60x45.png' => array( 'hash' => 'f00716f4e8f7a95e70d43504f06be0a6', 'uri' => '/res/f00716f4/rsrc/image/icon/fatcow/thumbnails/zip60x45.png', 'disk' => '/rsrc/image/icon/fatcow/thumbnails/zip60x45.png', 'type' => 'png', ), '/rsrc/image/icon/lightbox/close-2.png' => array( 'hash' => '72ff3ddcc1ed5d19a715ed6242114b53', 'uri' => '/res/72ff3ddc/rsrc/image/icon/lightbox/close-2.png', 'disk' => '/rsrc/image/icon/lightbox/close-2.png', 'type' => 'png', ), '/rsrc/image/icon/lightbox/close-hover-2.png' => array( 'hash' => '6ad4bd4a7820547a1d9041752546ba16', 'uri' => '/res/6ad4bd4a/rsrc/image/icon/lightbox/close-hover-2.png', 'disk' => '/rsrc/image/icon/lightbox/close-hover-2.png', 'type' => 'png', ), '/rsrc/image/icon/lightbox/left-arrow-2.png' => array( 'hash' => 'd84cbb0d42739f87b8f25b2f1d2f1153', 'uri' => '/res/d84cbb0d/rsrc/image/icon/lightbox/left-arrow-2.png', 'disk' => '/rsrc/image/icon/lightbox/left-arrow-2.png', 'type' => 'png', ), '/rsrc/image/icon/lightbox/left-arrow-hover-2.png' => array( 'hash' => 'cdf05f98fff3f390cd8df0c89894a3e1', 'uri' => '/res/cdf05f98/rsrc/image/icon/lightbox/left-arrow-hover-2.png', 'disk' => '/rsrc/image/icon/lightbox/left-arrow-hover-2.png', 'type' => 'png', ), '/rsrc/image/icon/lightbox/right-arrow-2.png' => array( 'hash' => '52021038cb6995c71f62a804bc2d420d', 'uri' => '/res/52021038/rsrc/image/icon/lightbox/right-arrow-2.png', 'disk' => '/rsrc/image/icon/lightbox/right-arrow-2.png', 'type' => 'png', ), '/rsrc/image/icon/lightbox/right-arrow-hover-2.png' => array( 'hash' => '65d5756b7b9cfcdeb2eb197a9aa6bbd2', 'uri' => '/res/65d5756b/rsrc/image/icon/lightbox/right-arrow-hover-2.png', 'disk' => '/rsrc/image/icon/lightbox/right-arrow-hover-2.png', 'type' => 'png', ), '/rsrc/image/icon/subscribe.png' => array( 'hash' => '5f47a4b17de245af39a4e7a097e40623', 'uri' => '/res/5f47a4b1/rsrc/image/icon/subscribe.png', 'disk' => '/rsrc/image/icon/subscribe.png', 'type' => 'png', ), '/rsrc/image/icon/tango/attachment.png' => array( 'hash' => '776fed2de89803fd8a0ba4b9deede230', 'uri' => '/res/776fed2d/rsrc/image/icon/tango/attachment.png', 'disk' => '/rsrc/image/icon/tango/attachment.png', 'type' => 'png', ), '/rsrc/image/icon/tango/edit.png' => array( 'hash' => 'c0028d99dcf4e9559bbf3c88ce2d8a8d', 'uri' => '/res/c0028d99/rsrc/image/icon/tango/edit.png', 'disk' => '/rsrc/image/icon/tango/edit.png', 'type' => 'png', ), '/rsrc/image/icon/tango/go-down.png' => array( 'hash' => '96862812cbb0445573c264dc057b8300', 'uri' => '/res/96862812/rsrc/image/icon/tango/go-down.png', 'disk' => '/rsrc/image/icon/tango/go-down.png', 'type' => 'png', ), '/rsrc/image/icon/tango/log.png' => array( 'hash' => 'a6f72499bef279ff6807a7dbc5148f1e', 'uri' => '/res/a6f72499/rsrc/image/icon/tango/log.png', 'disk' => '/rsrc/image/icon/tango/log.png', 'type' => 'png', ), '/rsrc/image/icon/tango/upload.png' => array( 'hash' => '8c11b63d6d99db3d7159c5d9a94e3062', 'uri' => '/res/8c11b63d/rsrc/image/icon/tango/upload.png', 'disk' => '/rsrc/image/icon/tango/upload.png', 'type' => 'png', ), '/rsrc/image/icon/unsubscribe.png' => array( 'hash' => '29429ad65aa3af50b072b32087057361', 'uri' => '/res/29429ad6/rsrc/image/icon/unsubscribe.png', 'disk' => '/rsrc/image/icon/unsubscribe.png', 'type' => 'png', ), '/rsrc/image/loading.gif' => array( 'hash' => '664297671941142f37d8c89e717ff2ce', 'uri' => '/res/66429767/rsrc/image/loading.gif', 'disk' => '/rsrc/image/loading.gif', 'type' => 'gif', ), '/rsrc/image/loading/boating_24.gif' => array( 'hash' => '2cd349ded48d698ebe886ba97b2db0f7', 'uri' => '/res/2cd349de/rsrc/image/loading/boating_24.gif', 'disk' => '/rsrc/image/loading/boating_24.gif', 'type' => 'gif', ), '/rsrc/image/loading/compass_24.gif' => array( 'hash' => '726c1ed4bf23446e044d6b9d28250a07', 'uri' => '/res/726c1ed4/rsrc/image/loading/compass_24.gif', 'disk' => '/rsrc/image/loading/compass_24.gif', 'type' => 'gif', ), '/rsrc/image/loading/loading_24.gif' => array( 'hash' => 'd6dcc5e6111a44fb9a160fc27b19d85c', 'uri' => '/res/d6dcc5e6/rsrc/image/loading/loading_24.gif', 'disk' => '/rsrc/image/loading/loading_24.gif', 'type' => 'gif', ), '/rsrc/image/loading/loading_48.gif' => array( 'hash' => 'cb6fc6eb9c0a0efaf589978029080c58', 'uri' => '/res/cb6fc6eb/rsrc/image/loading/loading_48.gif', 'disk' => '/rsrc/image/loading/loading_48.gif', 'type' => 'gif', ), '/rsrc/image/loading/loading_d48.gif' => array( 'hash' => 'c5181f5e0ac8125ad9beda73fdf18e91', 'uri' => '/res/c5181f5e/rsrc/image/loading/loading_d48.gif', 'disk' => '/rsrc/image/loading/loading_d48.gif', 'type' => 'gif', ), '/rsrc/image/loading/loading_w24.gif' => array( 'hash' => '231857d68736e9bdda6bdbaaf924b8da', 'uri' => '/res/231857d6/rsrc/image/loading/loading_w24.gif', 'disk' => '/rsrc/image/loading/loading_w24.gif', 'type' => 'gif', ), '/rsrc/image/main_texture.png' => array( 'hash' => 'e34d8143384721be73ec9b7532a977ab', 'uri' => '/res/e34d8143/rsrc/image/main_texture.png', 'disk' => '/rsrc/image/main_texture.png', 'type' => 'png', ), '/rsrc/image/menu_texture.png' => array( 'hash' => 'ad020b1529b3a3b3480ca9de1d5f1e40', 'uri' => '/res/ad020b15/rsrc/image/menu_texture.png', 'disk' => '/rsrc/image/menu_texture.png', 'type' => 'png', ), '/rsrc/image/people/harding.png' => array( 'hash' => '818b035ace2c480aa8df7b7f11cef58b', 'uri' => '/res/818b035a/rsrc/image/people/harding.png', 'disk' => '/rsrc/image/people/harding.png', 'type' => 'png', ), '/rsrc/image/people/jefferson.png' => array( 'hash' => '55fe807ff02f9320e595fb59442e2038', 'uri' => '/res/55fe807f/rsrc/image/people/jefferson.png', 'disk' => '/rsrc/image/people/jefferson.png', 'type' => 'png', ), '/rsrc/image/people/lincoln.png' => array( 'hash' => '2363337947ab52fd5fda79e4a004e930', 'uri' => '/res/23633379/rsrc/image/people/lincoln.png', 'disk' => '/rsrc/image/people/lincoln.png', 'type' => 'png', ), '/rsrc/image/people/mckinley.png' => array( 'hash' => '0b7b05dd47c49a0874670e5e8200bba8', 'uri' => '/res/0b7b05dd/rsrc/image/people/mckinley.png', 'disk' => '/rsrc/image/people/mckinley.png', 'type' => 'png', ), '/rsrc/image/people/taft.png' => array( 'hash' => 'f3e47d45b59b0b009fd536dabae9a151', 'uri' => '/res/f3e47d45/rsrc/image/people/taft.png', 'disk' => '/rsrc/image/people/taft.png', 'type' => 'png', ), '/rsrc/image/people/washington.png' => array( 'hash' => '01412761cab769f7993d69eba986d949', 'uri' => '/res/01412761/rsrc/image/people/washington.png', 'disk' => '/rsrc/image/people/washington.png', 'type' => 'png', ), '/rsrc/image/phrequent_active.png' => array( 'hash' => '716cddc08630eaa33934b2008723cac0', 'uri' => '/res/716cddc0/rsrc/image/phrequent_active.png', 'disk' => '/rsrc/image/phrequent_active.png', 'type' => 'png', ), '/rsrc/image/phrequent_inactive.png' => array( 'hash' => 'f9099683873c01c5de1dc6650bd668fe', 'uri' => '/res/f9099683/rsrc/image/phrequent_inactive.png', 'disk' => '/rsrc/image/phrequent_inactive.png', 'type' => 'png', ), '/rsrc/image/search.png' => array( 'hash' => 'ff7da044e6f923b8f569dec11f97e5e5', 'uri' => '/res/ff7da044/rsrc/image/search.png', 'disk' => '/rsrc/image/search.png', 'type' => 'png', ), '/rsrc/image/sprite-actions-X2.png' => array( 'hash' => '06962a5e8bea98ba7418d1d6cabcd7dc', 'uri' => '/res/06962a5e/rsrc/image/sprite-actions-X2.png', 'disk' => '/rsrc/image/sprite-actions-X2.png', 'type' => 'png', ), '/rsrc/image/sprite-actions.png' => array( 'hash' => 'd5dda5fab1e61b00538c9a4fa1ee94c8', 'uri' => '/res/d5dda5fa/rsrc/image/sprite-actions.png', 'disk' => '/rsrc/image/sprite-actions.png', 'type' => 'png', ), '/rsrc/image/sprite-apps-X2.png' => array( 'hash' => '67e8a6bf2d7fbb0b7961f1a0dcf8592b', 'uri' => '/res/67e8a6bf/rsrc/image/sprite-apps-X2.png', 'disk' => '/rsrc/image/sprite-apps-X2.png', 'type' => 'png', ), '/rsrc/image/sprite-apps-large-X2.png' => array( 'hash' => '9bed1778022e2bd25d658842be54844d', 'uri' => '/res/9bed1778/rsrc/image/sprite-apps-large-X2.png', 'disk' => '/rsrc/image/sprite-apps-large-X2.png', 'type' => 'png', ), '/rsrc/image/sprite-apps-large.png' => array( 'hash' => '518d6e5487c8d3758921ad85c1bb7d60', 'uri' => '/res/518d6e54/rsrc/image/sprite-apps-large.png', 'disk' => '/rsrc/image/sprite-apps-large.png', 'type' => 'png', ), '/rsrc/image/sprite-apps-xlarge.png' => array( 'hash' => '992d2c278b6a22c0fa874d457a252fbd', 'uri' => '/res/992d2c27/rsrc/image/sprite-apps-xlarge.png', 'disk' => '/rsrc/image/sprite-apps-xlarge.png', 'type' => 'png', ), '/rsrc/image/sprite-apps.png' => array( 'hash' => '9024ab95247f936f41c0b51c75e2e228', 'uri' => '/res/9024ab95/rsrc/image/sprite-apps.png', 'disk' => '/rsrc/image/sprite-apps.png', 'type' => 'png', ), '/rsrc/image/sprite-conpherence-X2.png' => array( 'hash' => '5e47868b00933a9afb6c844e464e6b23', 'uri' => '/res/5e47868b/rsrc/image/sprite-conpherence-X2.png', 'disk' => '/rsrc/image/sprite-conpherence-X2.png', 'type' => 'png', ), '/rsrc/image/sprite-conpherence.png' => array( 'hash' => 'ca51f1be25213262d68e626e4cab7f0f', 'uri' => '/res/ca51f1be/rsrc/image/sprite-conpherence.png', 'disk' => '/rsrc/image/sprite-conpherence.png', 'type' => 'png', ), '/rsrc/image/sprite-docs-X2.png' => array( 'hash' => '57d3286ce88133f3ec9240e35f6bb897', 'uri' => '/res/57d3286c/rsrc/image/sprite-docs-X2.png', 'disk' => '/rsrc/image/sprite-docs-X2.png', 'type' => 'png', ), '/rsrc/image/sprite-docs.png' => array( 'hash' => 'b2b089072d6eddd831402a77c02b5736', 'uri' => '/res/b2b08907/rsrc/image/sprite-docs.png', 'disk' => '/rsrc/image/sprite-docs.png', 'type' => 'png', ), '/rsrc/image/sprite-gradient.png' => array( 'hash' => '1f0306b0ca281b1e5b96de0096269f1d', 'uri' => '/res/1f0306b0/rsrc/image/sprite-gradient.png', 'disk' => '/rsrc/image/sprite-gradient.png', 'type' => 'png', ), '/rsrc/image/sprite-icons-X2.png' => array( 'hash' => '4fcceb49691b148ad5dc6671295ff378', 'uri' => '/res/4fcceb49/rsrc/image/sprite-icons-X2.png', 'disk' => '/rsrc/image/sprite-icons-X2.png', 'type' => 'png', ), '/rsrc/image/sprite-icons.png' => array( 'hash' => '736b067d369ff89f1ab2756c619ba129', 'uri' => '/res/736b067d/rsrc/image/sprite-icons.png', 'disk' => '/rsrc/image/sprite-icons.png', 'type' => 'png', ), '/rsrc/image/sprite-login-X2.png' => array( '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' => '7d3eee260ee0beb90c12e26fbc48fd9c', 'uri' => '/res/7d3eee26/rsrc/image/sprite-login.png', 'disk' => '/rsrc/image/sprite-login.png', 'type' => 'png', ), '/rsrc/image/sprite-menu-X2.png' => array( 'hash' => '63b649a6ccba7bf76bc9456dc5dfb12b', 'uri' => '/res/63b649a6/rsrc/image/sprite-menu-X2.png', 'disk' => '/rsrc/image/sprite-menu-X2.png', 'type' => 'png', ), '/rsrc/image/sprite-menu.png' => array( 'hash' => 'e0e16618691d2cffe64e9c57843828ff', 'uri' => '/res/e0e16618/rsrc/image/sprite-menu.png', 'disk' => '/rsrc/image/sprite-menu.png', 'type' => 'png', ), '/rsrc/image/sprite-minicons-X2.png' => array( 'hash' => 'c420c6462f7e50ca9941ccc5dd9e3dec', 'uri' => '/res/c420c646/rsrc/image/sprite-minicons-X2.png', 'disk' => '/rsrc/image/sprite-minicons-X2.png', 'type' => 'png', ), '/rsrc/image/sprite-minicons.png' => array( 'hash' => '168bb875933624b3080a1cc134e5b4ed', 'uri' => '/res/168bb875/rsrc/image/sprite-minicons.png', 'disk' => '/rsrc/image/sprite-minicons.png', 'type' => 'png', ), '/rsrc/image/sprite-payments.png' => array( 'hash' => '5ce73fb580609e7cda16832e3577b147', 'uri' => '/res/5ce73fb5/rsrc/image/sprite-payments.png', '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' => 'bb0d9cc2fec8e852c69790cbb626c6b1', 'uri' => '/res/bb0d9cc2/rsrc/image/sprite-status-X2.png', 'disk' => '/rsrc/image/sprite-status-X2.png', 'type' => 'png', ), '/rsrc/image/sprite-status.png' => array( 'hash' => 'b78e998cb34964052b17a8777651ecbd', 'uri' => '/res/b78e998c/rsrc/image/sprite-status.png', 'disk' => '/rsrc/image/sprite-status.png', 'type' => 'png', ), '/rsrc/image/sprite-tokens-X2.png' => array( 'hash' => '8b822687e6b1088cbb5ea89cf6d351a4', 'uri' => '/res/8b822687/rsrc/image/sprite-tokens-X2.png', 'disk' => '/rsrc/image/sprite-tokens-X2.png', 'type' => 'png', ), '/rsrc/image/sprite-tokens.png' => array( 'hash' => '67c46fd75c885b76ecbfe46e71a476cc', 'uri' => '/res/67c46fd7/rsrc/image/sprite-tokens.png', 'disk' => '/rsrc/image/sprite-tokens.png', 'type' => 'png', ), '/rsrc/image/texture/card-gradient.png' => array( 'hash' => '268b7fdd758d4bf99db8de6770aae8af', 'uri' => '/res/268b7fdd/rsrc/image/texture/card-gradient.png', 'disk' => '/rsrc/image/texture/card-gradient.png', 'type' => 'png', ), '/rsrc/image/texture/dark-menu-hover.png' => array( 'hash' => 'a214a732644be34872e895b338b5d639', 'uri' => '/res/a214a732/rsrc/image/texture/dark-menu-hover.png', 'disk' => '/rsrc/image/texture/dark-menu-hover.png', 'type' => 'png', ), '/rsrc/image/texture/dark-menu.png' => array( 'hash' => '41ee673a762cec48a154b456ad5ac204', 'uri' => '/res/41ee673a/rsrc/image/texture/dark-menu.png', 'disk' => '/rsrc/image/texture/dark-menu.png', 'type' => 'png', ), '/rsrc/image/texture/grip.png' => array( 'hash' => 'f11bc231d241f1335cfca2933ad234e0', 'uri' => '/res/f11bc231/rsrc/image/texture/grip.png', 'disk' => '/rsrc/image/texture/grip.png', 'type' => 'png', ), '/rsrc/image/texture/panel-header-gradient.png' => array( 'hash' => 'ad9204dd3ef5b12b645d80677d8ccead', 'uri' => '/res/ad9204dd/rsrc/image/texture/panel-header-gradient.png', 'disk' => '/rsrc/image/texture/panel-header-gradient.png', 'type' => 'png', ), '/rsrc/image/texture/phlnx-bg.png' => array( 'hash' => 'a55a694da8b3874ca7a3105b7818f3a0', 'uri' => '/res/a55a694d/rsrc/image/texture/phlnx-bg.png', 'disk' => '/rsrc/image/texture/phlnx-bg.png', 'type' => 'png', ), '/rsrc/image/texture/pholio-background.gif' => array( 'hash' => 'cf4561af116edf393dc583e5119fb412', 'uri' => '/res/cf4561af/rsrc/image/texture/pholio-background.gif', 'disk' => '/rsrc/image/texture/pholio-background.gif', 'type' => 'gif', ), '/rsrc/image/texture/table_header.png' => array( 'hash' => '4ed3f56a30d3749e8f62052b9735a316', 'uri' => '/res/4ed3f56a/rsrc/image/texture/table_header.png', 'disk' => '/rsrc/image/texture/table_header.png', 'type' => 'png', ), '/rsrc/image/texture/table_header_hover.png' => array( 'hash' => 'ea1f71a604e9b4859de1e25751540437', 'uri' => '/res/ea1f71a6/rsrc/image/texture/table_header_hover.png', 'disk' => '/rsrc/image/texture/table_header_hover.png', 'type' => 'png', ), '/rsrc/image/texture/table_header_tall.png' => array( 'hash' => 'b05525601f78d759f1c5e47fd9c1a8aa', 'uri' => '/res/b0552560/rsrc/image/texture/table_header_tall.png', 'disk' => '/rsrc/image/texture/table_header_tall.png', 'type' => 'png', ), '/rsrc/swf/aphlict.swf' => array( 'hash' => '4b9a9d83bebaf254f3790e87b45c1f92', 'uri' => '/res/4b9a9d83/rsrc/swf/aphlict.swf', 'disk' => '/rsrc/swf/aphlict.swf', 'type' => 'swf', ), 'aphront-bars' => array( 'uri' => '/res/dc8fd846/rsrc/css/aphront/aphront-bars.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/aphront-bars.css', ), 'aphront-calendar-view-css' => array( 'uri' => '/res/d5a33deb/rsrc/css/aphront/calendar-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/calendar-view.css', ), 'aphront-contextbar-view-css' => array( 'uri' => '/res/46c6248f/rsrc/css/aphront/context-bar.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/context-bar.css', ), 'aphront-dark-console-css' => array( 'uri' => '/res/5c341863/rsrc/css/aphront/dark-console.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/dark-console.css', ), 'aphront-dialog-view-css' => array( - 'uri' => '/res/830fa2de/rsrc/css/aphront/dialog-view.css', + 'uri' => '/res/6b6a41c6/rsrc/css/aphront/dialog-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/dialog-view.css', ), 'aphront-error-view-css' => array( 'uri' => '/res/cb571901/rsrc/css/aphront/error-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/error-view.css', ), 'aphront-list-filter-view-css' => array( 'uri' => '/res/b770e0da/rsrc/css/aphront/list-filter-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/list-filter-view.css', ), 'aphront-multi-column-view-css' => array( 'uri' => '/res/9d2b2374/rsrc/css/aphront/multi-column.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/multi-column.css', ), 'aphront-notes' => array( 'uri' => '/res/ac115367/rsrc/css/aphront/aphront-notes.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/aphront-notes.css', ), 'aphront-pager-view-css' => array( 'uri' => '/res/ea81aec0/rsrc/css/aphront/pager-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/pager-view.css', ), 'aphront-panel-view-css' => array( 'uri' => '/res/70d7011b/rsrc/css/aphront/panel-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/panel-view.css', ), 'aphront-request-failure-view-css' => array( 'uri' => '/res/c9a43002/rsrc/css/aphront/request-failure-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/request-failure-view.css', ), 'aphront-table-view-css' => array( 'uri' => '/res/24f51f0b/rsrc/css/aphront/table-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/table-view.css', ), 'aphront-tokenizer-control-css' => array( 'uri' => '/res/36192cf2/rsrc/css/aphront/tokenizer.css', 'type' => 'css', 'requires' => array( 0 => 'aphront-typeahead-control-css', ), 'disk' => '/rsrc/css/aphront/tokenizer.css', ), 'aphront-tooltip-css' => array( 'uri' => '/res/3a7d8e07/rsrc/css/aphront/tooltip.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/tooltip.css', ), 'aphront-two-column-view-css' => array( 'uri' => '/res/4263aa98/rsrc/css/aphront/two-column.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/two-column.css', ), 'aphront-typeahead-control-css' => array( 'uri' => '/res/c6ad64bb/rsrc/css/aphront/typeahead.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/typeahead.css', ), 'auth-css' => array( 'uri' => '/res/9e544d3c/rsrc/css/application/auth/auth.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/auth/auth.css', ), 'config-options-css' => array( 'uri' => '/res/4b5b6779/rsrc/css/application/config/config-options.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/config/config-options.css', ), 'conpherence-menu-css' => array( 'uri' => '/res/cae40b18/rsrc/css/application/conpherence/menu.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/conpherence/menu.css', ), 'conpherence-message-pane-css' => array( 'uri' => '/res/150f96d4/rsrc/css/application/conpherence/message-pane.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/conpherence/message-pane.css', ), 'conpherence-notification-css' => array( 'uri' => '/res/232c8cdb/rsrc/css/application/conpherence/notification.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/conpherence/notification.css', ), 'conpherence-update-css' => array( 'uri' => '/res/92094ed7/rsrc/css/application/conpherence/update.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/conpherence/update.css', ), 'conpherence-widget-pane-css' => array( 'uri' => '/res/13478b94/rsrc/css/application/conpherence/widget-pane.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/conpherence/widget-pane.css', ), 'differential-changeset-view-css' => array( 'uri' => '/res/37f702ae/rsrc/css/application/differential/changeset-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/changeset-view.css', ), 'differential-core-view-css' => array( 'uri' => '/res/18563185/rsrc/css/application/differential/core.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/core.css', ), 'differential-inline-comment-editor' => array( 'uri' => '/res/e952d210/rsrc/js/application/differential/DifferentialInlineCommentEditor.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-dom', 1 => 'javelin-util', 2 => 'javelin-stratcom', 3 => 'javelin-install', 4 => 'javelin-request', 5 => 'javelin-workflow', ), 'disk' => '/rsrc/js/application/differential/DifferentialInlineCommentEditor.js', ), 'differential-local-commits-view-css' => array( 'uri' => '/res/c6e9db42/rsrc/css/application/differential/local-commits-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/local-commits-view.css', ), 'differential-results-table-css' => array( 'uri' => '/res/5e37cf75/rsrc/css/application/differential/results-table.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/results-table.css', ), 'differential-revision-add-comment-css' => array( 'uri' => '/res/849748d3/rsrc/css/application/differential/add-comment.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/add-comment.css', ), 'differential-revision-comment-css' => array( 'uri' => '/res/e2dda8b5/rsrc/css/application/differential/revision-comment.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/revision-comment.css', ), 'differential-revision-comment-list-css' => array( 'uri' => '/res/6cc4ca9b/rsrc/css/application/differential/revision-comment-list.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/revision-comment-list.css', ), 'differential-revision-history-css' => array( 'uri' => '/res/13b4c17b/rsrc/css/application/differential/revision-history.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/revision-history.css', ), 'differential-revision-list-css' => array( 'uri' => '/res/fe6c4721/rsrc/css/application/differential/revision-list.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/revision-list.css', ), 'differential-table-of-contents-css' => array( 'uri' => '/res/3bb8c01f/rsrc/css/application/differential/table-of-contents.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/table-of-contents.css', ), 'diffusion-commit-view-css' => array( 'uri' => '/res/a48ea65a/rsrc/css/application/diffusion/commit-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/diffusion/commit-view.css', ), 'diffusion-icons-css' => array( 'uri' => '/res/82e77537/rsrc/css/application/diffusion/diffusion-icons.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/diffusion/diffusion-icons.css', ), 'diffusion-source-css' => array( 'uri' => '/res/5076c269/rsrc/css/application/diffusion/diffusion-source.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/diffusion/diffusion-source.css', ), 'diviner-shared-css' => array( 'uri' => '/res/2e831eea/rsrc/css/diviner/diviner-shared.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/diviner/diviner-shared.css', ), 'global-drag-and-drop-css' => array( 'uri' => '/res/4e24cb65/rsrc/css/application/files/global-drag-and-drop.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/files/global-drag-and-drop.css', ), 'herald-css' => array( 'uri' => '/res/2150a55d/rsrc/css/application/herald/herald.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/herald/herald.css', ), 'herald-rule-editor' => array( 'uri' => '/res/928275b4/rsrc/js/application/herald/HeraldRuleEditor.js', 'type' => 'js', 'requires' => array( 0 => 'multirow-row-manager', 1 => 'javelin-install', 2 => 'javelin-typeahead', 3 => 'javelin-util', 4 => 'javelin-dom', 5 => 'javelin-tokenizer', 6 => 'javelin-typeahead-preloaded-source', 7 => 'javelin-stratcom', 8 => 'javelin-json', 9 => 'phabricator-prefab', ), 'disk' => '/rsrc/js/application/herald/HeraldRuleEditor.js', ), 'herald-test-css' => array( 'uri' => '/res/51199954/rsrc/css/application/herald/herald-test.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/herald/herald-test.css', ), 'inline-comment-summary-css' => array( 'uri' => '/res/3cf1f7a7/rsrc/css/application/diff/inline-comment-summary.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/diff/inline-comment-summary.css', ), 'javelin-aphlict' => array( 'uri' => '/res/c0b9e53f/rsrc/js/application/aphlict/Aphlict.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', ), 'disk' => '/rsrc/js/application/aphlict/Aphlict.js', ), 'javelin-behavior' => array( 'uri' => '/res/15482715/rsrc/externals/javelin/lib/behavior.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-magical-init', 1 => 'javelin-util', ), 'disk' => '/rsrc/externals/javelin/lib/behavior.js', ), 'javelin-behavior-aphlict-dropdown' => array( 'uri' => '/res/3ff0c90a/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-request', 2 => 'javelin-stratcom', 3 => 'javelin-vector', 4 => 'javelin-dom', 5 => 'javelin-uri', ), 'disk' => '/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js', ), 'javelin-behavior-aphlict-listen' => array( 'uri' => '/res/7487f207/rsrc/js/application/aphlict/behavior-aphlict-listen.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-aphlict', 2 => 'javelin-stratcom', 3 => 'javelin-request', 4 => 'javelin-uri', 5 => 'javelin-dom', 6 => 'javelin-json', 7 => 'phabricator-notification', ), 'disk' => '/rsrc/js/application/aphlict/behavior-aphlict-listen.js', ), 'javelin-behavior-aphront-basic-tokenizer' => array( 'uri' => '/res/c7fd9a7b/rsrc/js/core/behavior-tokenizer.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'phabricator-prefab', ), 'disk' => '/rsrc/js/core/behavior-tokenizer.js', ), 'javelin-behavior-aphront-crop' => array( 'uri' => '/res/8c800f36/rsrc/js/core/behavior-crop.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-vector', 3 => 'javelin-magical-init', ), 'disk' => '/rsrc/js/core/behavior-crop.js', ), 'javelin-behavior-aphront-drag-and-drop-textarea' => array( 'uri' => '/res/a261f6e6/rsrc/js/core/behavior-drag-and-drop-textarea.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'phabricator-drag-and-drop-file-upload', 3 => 'phabricator-textareautils', ), 'disk' => '/rsrc/js/core/behavior-drag-and-drop-textarea.js', ), 'javelin-behavior-aphront-form-disable-on-submit' => array( 'uri' => '/res/a4a4ff07/rsrc/js/core/behavior-form.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), 'disk' => '/rsrc/js/core/behavior-form.js', ), 'javelin-behavior-aphront-more' => array( 'uri' => '/res/fae13324/rsrc/js/core/behavior-more.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), 'disk' => '/rsrc/js/core/behavior-more.js', ), 'javelin-behavior-audio-source' => array( 'uri' => '/res/21831141/rsrc/js/core/behavior-audio-source.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-vector', 3 => 'javelin-dom', ), 'disk' => '/rsrc/js/core/behavior-audio-source.js', ), 'javelin-behavior-audit-preview' => array( 'uri' => '/res/d8f31e46/rsrc/js/application/diffusion/behavior-audit-preview.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'phabricator-shaped-request', ), 'disk' => '/rsrc/js/application/diffusion/behavior-audit-preview.js', ), 'javelin-behavior-balanced-payment-form' => array( 'uri' => '/res/6876492d/rsrc/js/application/phortune/behavior-balanced-payment-form.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'phortune-credit-card-form', ), 'disk' => '/rsrc/js/application/phortune/behavior-balanced-payment-form.js', ), 'javelin-behavior-config-reorder-fields' => array( 'uri' => '/res/691c5c8c/rsrc/js/application/config/behavior-reorder-fields.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-json', 4 => 'phabricator-draggable-list', ), 'disk' => '/rsrc/js/application/config/behavior-reorder-fields.js', ), 'javelin-behavior-conpherence-menu' => array( 'uri' => '/res/f27205d4/rsrc/js/application/conpherence/behavior-menu.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-stratcom', 4 => 'javelin-workflow', 5 => 'javelin-behavior-device', 6 => 'javelin-history', 7 => 'javelin-vector', ), 'disk' => '/rsrc/js/application/conpherence/behavior-menu.js', ), 'javelin-behavior-conpherence-pontificate' => array( 'uri' => '/res/19cb581b/rsrc/js/application/conpherence/behavior-pontificate.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-workflow', 4 => 'javelin-stratcom', ), 'disk' => '/rsrc/js/application/conpherence/behavior-pontificate.js', ), 'javelin-behavior-conpherence-widget-pane' => array( 'uri' => '/res/562ca20e/rsrc/js/application/conpherence/behavior-widget-pane.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-workflow', 4 => 'javelin-util', 5 => 'phabricator-notification', 6 => 'javelin-behavior-device', 7 => 'phabricator-dropdown-menu', 8 => 'phabricator-menu-item', ), 'disk' => '/rsrc/js/application/conpherence/behavior-widget-pane.js', ), 'javelin-behavior-countdown-timer' => array( 'uri' => '/res/13d40efa/rsrc/js/application/countdown/timer.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/countdown/timer.js', ), 'javelin-behavior-dark-console' => array( 'uri' => '/res/1e2c7a5e/rsrc/js/core/behavior-dark-console.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-util', 3 => 'javelin-dom', 4 => 'javelin-request', 5 => 'phabricator-keyboard-shortcut', ), 'disk' => '/rsrc/js/core/behavior-dark-console.js', ), 'javelin-behavior-device' => array( 'uri' => '/res/12e43f5a/rsrc/js/core/behavior-device.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-vector', 4 => 'javelin-install', ), 'disk' => '/rsrc/js/core/behavior-device.js', ), 'javelin-behavior-differential-accept-with-errors' => array( 'uri' => '/res/8fea67b3/rsrc/js/application/differential/behavior-accept-with-errors.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/differential/behavior-accept-with-errors.js', ), 'javelin-behavior-differential-add-reviewers-and-ccs' => array( 'uri' => '/res/fd9f2c1c/rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'phabricator-prefab', ), 'disk' => '/rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js', ), 'javelin-behavior-differential-comment-jump' => array( 'uri' => '/res/8ffb4222/rsrc/js/application/differential/behavior-comment-jump.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/differential/behavior-comment-jump.js', ), 'javelin-behavior-differential-diff-radios' => array( 'uri' => '/res/004cb66f/rsrc/js/application/differential/behavior-diff-radios.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/differential/behavior-diff-radios.js', ), 'javelin-behavior-differential-dropdown-menus' => array( 'uri' => '/res/722c679c/rsrc/js/application/differential/behavior-dropdown-menus.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-stratcom', 4 => 'phabricator-dropdown-menu', 5 => 'phabricator-menu-item', 6 => 'phabricator-phtize', ), 'disk' => '/rsrc/js/application/differential/behavior-dropdown-menus.js', ), 'javelin-behavior-differential-edit-inline-comments' => array( 'uri' => '/res/935d4012/rsrc/js/application/differential/behavior-edit-inline-comments.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-util', 4 => 'javelin-vector', 5 => 'differential-inline-comment-editor', ), 'disk' => '/rsrc/js/application/differential/behavior-edit-inline-comments.js', ), 'javelin-behavior-differential-feedback-preview' => array( 'uri' => '/res/4421fac6/rsrc/js/application/differential/behavior-comment-preview.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-request', 4 => 'javelin-util', 5 => 'phabricator-shaped-request', ), 'disk' => '/rsrc/js/application/differential/behavior-comment-preview.js', ), 'javelin-behavior-differential-keyboard-navigation' => array( 'uri' => '/res/22ed93ba/rsrc/js/application/differential/behavior-keyboard-nav.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'phabricator-keyboard-shortcut', ), 'disk' => '/rsrc/js/application/differential/behavior-keyboard-nav.js', ), 'javelin-behavior-differential-populate' => array( 'uri' => '/res/bb9a29f4/rsrc/js/application/differential/behavior-populate.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-workflow', 2 => 'javelin-util', 3 => 'javelin-dom', 4 => 'javelin-stratcom', 5 => 'javelin-behavior-device', 6 => 'javelin-vector', 7 => 'phabricator-tooltip', ), 'disk' => '/rsrc/js/application/differential/behavior-populate.js', ), 'javelin-behavior-differential-show-all-comments' => array( 'uri' => '/res/8801848d/rsrc/js/application/differential/behavior-show-all-comments.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/differential/behavior-show-all-comments.js', ), 'javelin-behavior-differential-show-field-details' => array( 'uri' => '/res/8d57f459/rsrc/js/application/differential/behavior-show-field-details.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/differential/behavior-show-field-details.js', ), 'javelin-behavior-differential-show-more' => array( 'uri' => '/res/03b7bc9e/rsrc/js/application/differential/behavior-show-more.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-workflow', 3 => 'javelin-util', 4 => 'javelin-stratcom', ), 'disk' => '/rsrc/js/application/differential/behavior-show-more.js', ), 'javelin-behavior-differential-toggle-files' => array( 'uri' => '/res/beb89813/rsrc/js/application/differential/behavior-toggle-files.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'phabricator-phtize', ), 'disk' => '/rsrc/js/application/differential/behavior-toggle-files.js', ), 'javelin-behavior-differential-user-select' => array( 'uri' => '/res/23c51a5d/rsrc/js/application/differential/behavior-user-select.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', ), 'disk' => '/rsrc/js/application/differential/behavior-user-select.js', ), 'javelin-behavior-diffusion-commit-branches' => array( 'uri' => '/res/1ede335a/rsrc/js/application/diffusion/behavior-commit-branches.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-request', ), 'disk' => '/rsrc/js/application/diffusion/behavior-commit-branches.js', ), 'javelin-behavior-diffusion-commit-graph' => array( 'uri' => '/res/536b8483/rsrc/js/application/diffusion/behavior-commit-graph.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', ), 'disk' => '/rsrc/js/application/diffusion/behavior-commit-graph.js', ), 'javelin-behavior-diffusion-jump-to' => array( 'uri' => '/res/bade44bd/rsrc/js/application/diffusion/behavior-jump-to.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-vector', 2 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/diffusion/behavior-jump-to.js', ), 'javelin-behavior-diffusion-pull-lastmodified' => array( 'uri' => '/res/29fe2790/rsrc/js/application/diffusion/behavior-pull-lastmodified.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-request', ), 'disk' => '/rsrc/js/application/diffusion/behavior-pull-lastmodified.js', ), 'javelin-behavior-doorkeeper-tag' => array( 'uri' => '/res/59480572/rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-json', 3 => 'javelin-workflow', 4 => 'javelin-magical-init', ), 'disk' => '/rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js', ), 'javelin-behavior-error-log' => array( 'uri' => '/res/acefdea7/rsrc/js/core/behavior-error-log.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-dom', ), 'disk' => '/rsrc/js/core/behavior-error-log.js', ), 'javelin-behavior-fancy-datepicker' => array( 'uri' => '/res/dcd7c2ca/rsrc/js/core/behavior-fancy-datepicker.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-util', 2 => 'javelin-dom', 3 => 'javelin-stratcom', 4 => 'javelin-vector', ), 'disk' => '/rsrc/js/core/behavior-fancy-datepicker.js', ), 'javelin-behavior-global-drag-and-drop' => array( 'uri' => '/res/ee8e9c39/rsrc/js/core/behavior-global-drag-and-drop.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-uri', 3 => 'javelin-mask', 4 => 'phabricator-drag-and-drop-file-upload', ), 'disk' => '/rsrc/js/core/behavior-global-drag-and-drop.js', ), 'javelin-behavior-herald-rule-editor' => array( 'uri' => '/res/77a0c945/rsrc/js/application/herald/herald-rule-editor.js', 'type' => 'js', 'requires' => array( 0 => 'herald-rule-editor', 1 => 'javelin-behavior', ), 'disk' => '/rsrc/js/application/herald/herald-rule-editor.js', ), 'javelin-behavior-history-install' => array( 'uri' => '/res/9099a161/rsrc/js/core/behavior-history-install.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-history', ), 'disk' => '/rsrc/js/core/behavior-history-install.js', ), 'javelin-behavior-icon-composer' => array( 'uri' => '/res/0be5c462/rsrc/js/application/files/behavior-icon-composer.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', ), 'disk' => '/rsrc/js/application/files/behavior-icon-composer.js', ), 'javelin-behavior-konami' => array( 'uri' => '/res/b7bb7c24/rsrc/js/core/behavior-konami.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', ), 'disk' => '/rsrc/js/core/behavior-konami.js', ), 'javelin-behavior-launch-icon-composer' => array( 'uri' => '/res/202488ac/rsrc/js/application/files/behavior-launch-icon-composer.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-workflow', ), 'disk' => '/rsrc/js/application/files/behavior-launch-icon-composer.js', ), 'javelin-behavior-lightbox-attachments' => array( 'uri' => '/res/72b4d3a8/rsrc/js/core/behavior-lightbox-attachments.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-mask', 4 => 'javelin-util', 5 => 'phabricator-busy', ), 'disk' => '/rsrc/js/core/behavior-lightbox-attachments.js', ), 'javelin-behavior-line-chart' => array( 'uri' => '/res/1aa5ac88/rsrc/js/application/maniphest/behavior-line-chart.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-vector', ), 'disk' => '/rsrc/js/application/maniphest/behavior-line-chart.js', ), 'javelin-behavior-load-blame' => array( 'uri' => '/res/138e2961/rsrc/js/application/diffusion/behavior-load-blame.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-request', ), 'disk' => '/rsrc/js/application/diffusion/behavior-load-blame.js', ), 'javelin-behavior-maniphest-batch-editor' => array( 'uri' => '/res/81b2b86f/rsrc/js/application/maniphest/behavior-batch-editor.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'phabricator-prefab', 4 => 'multirow-row-manager', 5 => 'javelin-json', ), 'disk' => '/rsrc/js/application/maniphest/behavior-batch-editor.js', ), 'javelin-behavior-maniphest-batch-selector' => array( - 'uri' => '/res/423c5f1b/rsrc/js/application/maniphest/behavior-batch-selector.js', + 'uri' => '/res/a82658b3/rsrc/js/application/maniphest/behavior-batch-selector.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-util', ), 'disk' => '/rsrc/js/application/maniphest/behavior-batch-selector.js', ), 'javelin-behavior-maniphest-list-editor' => array( 'uri' => '/res/a251e72f/rsrc/js/application/maniphest/behavior-list-edit.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-workflow', 4 => 'javelin-fx', 5 => 'javelin-util', ), 'disk' => '/rsrc/js/application/maniphest/behavior-list-edit.js', ), 'javelin-behavior-maniphest-subpriority-editor' => array( - 'uri' => '/res/1fa4961f/rsrc/js/application/maniphest/behavior-subpriorityeditor.js', + 'uri' => '/res/95f3d4a6/rsrc/js/application/maniphest/behavior-subpriorityeditor.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-workflow', 4 => 'phabricator-draggable-list', ), 'disk' => '/rsrc/js/application/maniphest/behavior-subpriorityeditor.js', ), 'javelin-behavior-maniphest-transaction-controls' => array( 'uri' => '/res/e8498688/rsrc/js/application/maniphest/behavior-transaction-controls.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'phabricator-prefab', ), 'disk' => '/rsrc/js/application/maniphest/behavior-transaction-controls.js', ), 'javelin-behavior-maniphest-transaction-expand' => array( 'uri' => '/res/966410de/rsrc/js/application/maniphest/behavior-transaction-expand.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-workflow', 3 => 'javelin-stratcom', ), 'disk' => '/rsrc/js/application/maniphest/behavior-transaction-expand.js', ), 'javelin-behavior-maniphest-transaction-preview' => array( 'uri' => '/res/9447a3f9/rsrc/js/application/maniphest/behavior-transaction-preview.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-json', 4 => 'javelin-stratcom', 5 => 'phabricator-shaped-request', ), 'disk' => '/rsrc/js/application/maniphest/behavior-transaction-preview.js', ), 'javelin-behavior-owners-path-editor' => array( 'uri' => '/res/9cf78ffc/rsrc/js/application/owners/owners-path-editor.js', 'type' => 'js', 'requires' => array( 0 => 'owners-path-editor', 1 => 'javelin-behavior', ), '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', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-vector', 3 => 'javelin-dom', 4 => 'javelin-uri', ), 'disk' => '/rsrc/js/core/behavior-active-nav.js', ), 'javelin-behavior-phabricator-autofocus' => array( 'uri' => '/res/bf92b8d6/rsrc/js/core/behavior-autofocus.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', ), 'disk' => '/rsrc/js/core/behavior-autofocus.js', ), 'javelin-behavior-phabricator-busy-example' => array( 'uri' => '/res/dbe12f2f/rsrc/js/application/uiexample/busy-example.js', 'type' => 'js', 'requires' => array( 0 => 'phabricator-busy', 1 => 'javelin-behavior', ), 'disk' => '/rsrc/js/application/uiexample/busy-example.js', ), 'javelin-behavior-phabricator-file-tree' => array( 'uri' => '/res/e5bf93df/rsrc/js/core/behavior-file-tree.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'phabricator-keyboard-shortcut', 2 => 'javelin-stratcom', ), 'disk' => '/rsrc/js/core/behavior-file-tree.js', ), 'javelin-behavior-phabricator-gesture' => array( 'uri' => '/res/16e1e77c/rsrc/js/core/behavior-gesture.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-behavior-device', 2 => 'javelin-stratcom', 3 => 'javelin-vector', 4 => 'javelin-dom', 5 => 'javelin-magical-init', ), 'disk' => '/rsrc/js/core/behavior-gesture.js', ), 'javelin-behavior-phabricator-gesture-example' => array( 'uri' => '/res/91d1e7f2/rsrc/js/application/uiexample/gesture-example.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-stratcom', 1 => 'javelin-behavior', 2 => 'javelin-vector', 3 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/uiexample/gesture-example.js', ), 'javelin-behavior-phabricator-hovercards' => array( 'uri' => '/res/4fe6b436/rsrc/js/core/behavior-hovercard.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-behavior-device', 2 => 'javelin-stratcom', 3 => 'javelin-vector', 4 => 'phabricator-hovercard', ), 'disk' => '/rsrc/js/core/behavior-hovercard.js', ), 'javelin-behavior-phabricator-keyboard-pager' => array( 'uri' => '/res/6a5445b8/rsrc/js/core/behavior-keyboard-pager.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-uri', 2 => 'phabricator-keyboard-shortcut', ), 'disk' => '/rsrc/js/core/behavior-keyboard-pager.js', ), 'javelin-behavior-phabricator-keyboard-shortcuts' => array( 'uri' => '/res/b971e713/rsrc/js/core/behavior-keyboard-shortcuts.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-workflow', 2 => 'javelin-json', 3 => 'javelin-dom', 4 => 'phabricator-keyboard-shortcut', ), 'disk' => '/rsrc/js/core/behavior-keyboard-shortcuts.js', ), 'javelin-behavior-phabricator-line-linker' => array( 'uri' => '/res/1cefdb6a/rsrc/js/core/behavior-line-linker.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-history', ), 'disk' => '/rsrc/js/core/behavior-line-linker.js', ), 'javelin-behavior-phabricator-nav' => array( 'uri' => '/res/afabcf16/rsrc/js/core/behavior-phabricator-nav.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-behavior-device', 2 => 'javelin-stratcom', 3 => 'javelin-dom', 4 => 'javelin-magical-init', 5 => 'javelin-vector', 6 => 'javelin-request', 7 => 'javelin-util', ), 'disk' => '/rsrc/js/core/behavior-phabricator-nav.js', ), 'javelin-behavior-phabricator-notification-example' => array( 'uri' => '/res/7c50cefd/rsrc/js/application/uiexample/notification-example.js', 'type' => 'js', 'requires' => array( 0 => 'phabricator-notification', 1 => 'javelin-stratcom', 2 => 'javelin-behavior', ), 'disk' => '/rsrc/js/application/uiexample/notification-example.js', ), 'javelin-behavior-phabricator-object-selector' => array( 'uri' => '/res/461f95f7/rsrc/js/core/behavior-object-selector.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-request', 3 => 'javelin-util', ), 'disk' => '/rsrc/js/core/behavior-object-selector.js', ), 'javelin-behavior-phabricator-oncopy' => array( 'uri' => '/res/cd3a9345/rsrc/js/core/behavior-oncopy.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', ), 'disk' => '/rsrc/js/core/behavior-oncopy.js', ), 'javelin-behavior-phabricator-remarkup-assist' => array( 'uri' => '/res/4153e95f/rsrc/js/core/behavior-phabricator-remarkup-assist.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'phabricator-phtize', 4 => 'phabricator-textareautils', 5 => 'javelin-workflow', 6 => 'javelin-vector', ), 'disk' => '/rsrc/js/core/behavior-phabricator-remarkup-assist.js', ), 'javelin-behavior-phabricator-reveal-content' => array( 'uri' => '/res/fef525ef/rsrc/js/core/behavior-reveal-content.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), 'disk' => '/rsrc/js/core/behavior-reveal-content.js', ), 'javelin-behavior-phabricator-search-typeahead' => array( 'uri' => '/res/409d9567/rsrc/js/core/behavior-search-typeahead.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-typeahead-ondemand-source', 2 => 'javelin-typeahead', 3 => 'javelin-dom', 4 => 'javelin-uri', 5 => 'javelin-util', 6 => 'javelin-stratcom', ), 'disk' => '/rsrc/js/core/behavior-search-typeahead.js', ), 'javelin-behavior-phabricator-tooltips' => array( 'uri' => '/res/a0ac5320/rsrc/js/core/behavior-tooltip.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-behavior-device', 2 => 'javelin-stratcom', 3 => 'phabricator-tooltip', ), 'disk' => '/rsrc/js/core/behavior-tooltip.js', ), 'javelin-behavior-phabricator-transaction-comment-form' => array( 'uri' => '/res/3c8d3c10/rsrc/js/application/transactions/behavior-transaction-comment-form.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-fx', 4 => 'javelin-request', 5 => 'phabricator-shaped-request', ), 'disk' => '/rsrc/js/application/transactions/behavior-transaction-comment-form.js', ), 'javelin-behavior-phabricator-transaction-list' => array( 'uri' => '/res/f05b3c6b/rsrc/js/application/transactions/behavior-transaction-list.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-workflow', 3 => 'javelin-dom', 4 => 'javelin-fx', ), 'disk' => '/rsrc/js/application/transactions/behavior-transaction-list.js', ), 'javelin-behavior-phabricator-watch-anchor' => array( 'uri' => '/res/69a90817/rsrc/js/core/behavior-watch-anchor.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-vector', ), 'disk' => '/rsrc/js/core/behavior-watch-anchor.js', ), 'javelin-behavior-phame-post-preview' => array( 'uri' => '/res/181d1cbe/rsrc/js/application/phame/phame-post-preview.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'phabricator-shaped-request', ), 'disk' => '/rsrc/js/application/phame/phame-post-preview.js', ), 'javelin-behavior-pholio-mock-edit' => array( 'uri' => '/res/1fd14497/rsrc/js/application/pholio/behavior-pholio-mock-edit.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-workflow', 4 => 'phabricator-phtize', 5 => 'phabricator-drag-and-drop-file-upload', 6 => 'phabricator-draggable-list', ), 'disk' => '/rsrc/js/application/pholio/behavior-pholio-mock-edit.js', ), 'javelin-behavior-pholio-mock-view' => array( 'uri' => '/res/f9588dcf/rsrc/js/application/pholio/behavior-pholio-mock-view.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-util', 2 => 'javelin-stratcom', 3 => 'javelin-dom', 4 => 'javelin-vector', 5 => 'javelin-magical-init', 6 => 'javelin-request', 7 => 'javelin-history', 8 => 'javelin-workflow', 9 => 'javelin-mask', 10 => 'javelin-behavior-device', 11 => 'phabricator-keyboard-shortcut', ), 'disk' => '/rsrc/js/application/pholio/behavior-pholio-mock-view.js', ), 'javelin-behavior-phui-object-box-tabs' => array( 'uri' => '/res/c2318be8/rsrc/js/phui/behavior-phui-object-box-tabs.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), 'disk' => '/rsrc/js/phui/behavior-phui-object-box-tabs.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', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-stratcom', 4 => 'javelin-request', ), 'disk' => '/rsrc/js/application/ponder/behavior-votebox.js', ), 'javelin-behavior-project-create' => array( 'uri' => '/res/e91f3f8f/rsrc/js/application/projects/behavior-project-create.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-workflow', ), 'disk' => '/rsrc/js/application/projects/behavior-project-create.js', ), 'javelin-behavior-refresh-csrf' => array( 'uri' => '/res/6c54100f/rsrc/js/core/behavior-refresh-csrf.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-request', 1 => 'javelin-behavior', 2 => 'javelin-dom', 3 => 'phabricator-busy', ), 'disk' => '/rsrc/js/core/behavior-refresh-csrf.js', ), 'javelin-behavior-releeph-preview-branch' => array( 'uri' => '/res/f694854d/rsrc/js/application/releeph/releeph-preview-branch.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-uri', 3 => 'javelin-request', ), 'disk' => '/rsrc/js/application/releeph/releeph-preview-branch.js', ), 'javelin-behavior-releeph-request-state-change' => array( 'uri' => '/res/07ecde0c/rsrc/js/application/releeph/releeph-request-state-change.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-request', 4 => 'phabricator-keyboard-shortcut', 5 => 'phabricator-notification', ), 'disk' => '/rsrc/js/application/releeph/releeph-request-state-change.js', ), 'javelin-behavior-releeph-request-typeahead' => array( 'uri' => '/res/2c2350a0/rsrc/js/application/releeph/releeph-request-typeahead.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-typeahead', 3 => 'javelin-typeahead-ondemand-source', 4 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/releeph/releeph-request-typeahead.js', ), 'javelin-behavior-remarkup-preview' => array( 'uri' => '/res/6ec98508/rsrc/js/core/behavior-remarkup-preview.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'phabricator-shaped-request', ), 'disk' => '/rsrc/js/core/behavior-remarkup-preview.js', ), 'javelin-behavior-repository-crossreference' => array( 'uri' => '/res/d3f9d50b/rsrc/js/application/repository/repository-crossreference.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-uri', ), 'disk' => '/rsrc/js/application/repository/repository-crossreference.js', ), 'javelin-behavior-search-reorder-queries' => array( 'uri' => '/res/9864b481/rsrc/js/application/search/behavior-reorder-queries.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-workflow', 3 => 'javelin-dom', 4 => 'phabricator-draggable-list', ), 'disk' => '/rsrc/js/application/search/behavior-reorder-queries.js', ), 'javelin-behavior-select-on-click' => array( 'uri' => '/res/f021b754/rsrc/js/core/behavior-select-on-click.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), 'disk' => '/rsrc/js/core/behavior-select-on-click.js', ), 'javelin-behavior-slowvote-embed' => array( 'uri' => '/res/8e85e20d/rsrc/js/application/slowvote/behavior-slowvote-embed.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-request', 2 => 'javelin-stratcom', 3 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/slowvote/behavior-slowvote-embed.js', ), 'javelin-behavior-stripe-payment-form' => array( 'uri' => '/res/c1a12d77/rsrc/js/application/phortune/behavior-stripe-payment-form.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'phortune-credit-card-form', ), 'disk' => '/rsrc/js/application/phortune/behavior-stripe-payment-form.js', ), 'javelin-behavior-test-payment-form' => array( 'uri' => '/res/a8fe8616/rsrc/js/application/phortune/behavior-test-payment-form.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'phortune-credit-card-form', ), 'disk' => '/rsrc/js/application/phortune/behavior-test-payment-form.js', ), 'javelin-behavior-toggle-class' => array( 'uri' => '/res/79921b7f/rsrc/js/core/behavior-toggle-class.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), 'disk' => '/rsrc/js/core/behavior-toggle-class.js', ), 'javelin-behavior-view-placeholder' => array( 'uri' => '/res/6abdb85b/rsrc/externals/javelin/ext/view/ViewPlaceholder.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-view-renderer', 3 => 'javelin-install', ), 'disk' => '/rsrc/externals/javelin/ext/view/ViewPlaceholder.js', ), 'javelin-behavior-workflow' => array( 'uri' => '/res/144d3196/rsrc/js/core/behavior-workflow.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-stratcom', 2 => 'javelin-workflow', 3 => 'javelin-dom', ), 'disk' => '/rsrc/js/core/behavior-workflow.js', ), 'javelin-color' => array( 'uri' => '/res/f17034de/rsrc/externals/javelin/ext/fx/Color.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', ), 'disk' => '/rsrc/externals/javelin/ext/fx/Color.js', ), 'javelin-cookie' => array( 'uri' => '/res/ee0d399f/rsrc/externals/javelin/lib/Cookie.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', ), 'disk' => '/rsrc/externals/javelin/lib/Cookie.js', ), 'javelin-dom' => array( 'uri' => '/res/580c0aeb/rsrc/externals/javelin/lib/DOM.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-magical-init', 1 => 'javelin-install', 2 => 'javelin-util', 3 => 'javelin-vector', 4 => 'javelin-stratcom', ), 'disk' => '/rsrc/externals/javelin/lib/DOM.js', ), 'javelin-dynval' => array( 'uri' => '/res/ea6f2a9d/rsrc/externals/javelin/ext/reactor/core/DynVal.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-reactornode', 2 => 'javelin-util', 3 => 'javelin-reactor', ), 'disk' => '/rsrc/externals/javelin/ext/reactor/core/DynVal.js', ), 'javelin-event' => array( 'uri' => '/res/5f70f4d0/rsrc/externals/javelin/core/Event.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', ), 'disk' => '/rsrc/externals/javelin/core/Event.js', ), 'javelin-fx' => array( 'uri' => '/res/23fb3d44/rsrc/externals/javelin/ext/fx/FX.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-color', 1 => 'javelin-install', 2 => 'javelin-util', ), 'disk' => '/rsrc/externals/javelin/ext/fx/FX.js', ), 'javelin-history' => array( 'uri' => '/res/6c084b09/rsrc/externals/javelin/lib/History.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-stratcom', 1 => 'javelin-install', 2 => 'javelin-uri', 3 => 'javelin-util', ), 'disk' => '/rsrc/externals/javelin/lib/History.js', ), 'javelin-install' => array( 'uri' => '/res/904356c0/rsrc/externals/javelin/core/install.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-util', 1 => 'javelin-magical-init', ), 'disk' => '/rsrc/externals/javelin/core/install.js', ), 'javelin-json' => array( 'uri' => '/res/cf83e72c/rsrc/externals/javelin/lib/JSON.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', ), 'disk' => '/rsrc/externals/javelin/lib/JSON.js', ), 'javelin-magical-init' => array( 'uri' => '/res/374d1f02/rsrc/externals/javelin/core/init.js', 'type' => 'js', 'requires' => array( ), 'disk' => '/rsrc/externals/javelin/core/init.js', ), 'javelin-mask' => array( 'uri' => '/res/465cf513/rsrc/externals/javelin/lib/Mask.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', ), 'disk' => '/rsrc/externals/javelin/lib/Mask.js', ), 'javelin-reactor' => array( 'uri' => '/res/c05f2658/rsrc/externals/javelin/ext/reactor/core/Reactor.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', ), 'disk' => '/rsrc/externals/javelin/ext/reactor/core/Reactor.js', ), 'javelin-reactor-dom' => array( 'uri' => '/res/5e03117e/rsrc/externals/javelin/ext/reactor/dom/RDOM.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-dom', 1 => 'javelin-dynval', 2 => 'javelin-reactor', 3 => 'javelin-reactornode', 4 => 'javelin-install', 5 => 'javelin-util', ), 'disk' => '/rsrc/externals/javelin/ext/reactor/dom/RDOM.js', ), 'javelin-reactor-node-calmer' => array( 'uri' => '/res/a93dd6b6/rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-reactor', 2 => 'javelin-util', ), 'disk' => '/rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js', ), 'javelin-reactornode' => array( 'uri' => '/res/4eac475b/rsrc/externals/javelin/ext/reactor/core/ReactorNode.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-reactor', 2 => 'javelin-util', 3 => 'javelin-reactor-node-calmer', ), 'disk' => '/rsrc/externals/javelin/ext/reactor/core/ReactorNode.js', ), 'javelin-request' => array( 'uri' => '/res/687bdcfc/rsrc/externals/javelin/lib/Request.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-stratcom', 2 => 'javelin-util', 3 => 'javelin-behavior', 4 => 'javelin-json', 5 => 'javelin-dom', 6 => 'javelin-resource', ), 'disk' => '/rsrc/externals/javelin/lib/Request.js', ), 'javelin-resource' => array( 'uri' => '/res/33a3bb57/rsrc/externals/javelin/lib/Resource.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-util', 1 => 'javelin-uri', 2 => 'javelin-install', ), 'disk' => '/rsrc/externals/javelin/lib/Resource.js', ), 'javelin-stratcom' => array( 'uri' => '/res/714946e7/rsrc/externals/javelin/core/Stratcom.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-event', 2 => 'javelin-util', 3 => 'javelin-magical-init', ), 'disk' => '/rsrc/externals/javelin/core/Stratcom.js', ), 'javelin-tokenizer' => array( 'uri' => '/res/cddb70f3/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-dom', 1 => 'javelin-util', 2 => 'javelin-stratcom', 3 => 'javelin-install', ), 'disk' => '/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js', ), 'javelin-typeahead' => array( 'uri' => '/res/fd79f758/rsrc/externals/javelin/lib/control/typeahead/Typeahead.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-vector', 3 => 'javelin-util', ), 'disk' => '/rsrc/externals/javelin/lib/control/typeahead/Typeahead.js', ), 'javelin-typeahead-composite-source' => array( 'uri' => '/res/487b3da2/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-typeahead-source', 2 => 'javelin-util', ), 'disk' => '/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js', ), 'javelin-typeahead-normalizer' => array( 'uri' => '/res/5a4bd979/rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', ), 'disk' => '/rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js', ), 'javelin-typeahead-ondemand-source' => array( 'uri' => '/res/92286a21/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-request', 3 => 'javelin-typeahead-source', ), 'disk' => '/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js', ), 'javelin-typeahead-preloaded-source' => array( 'uri' => '/res/147900c7/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-request', 3 => 'javelin-typeahead-source', ), 'disk' => '/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js', ), 'javelin-typeahead-source' => array( 'uri' => '/res/13289259/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-dom', 3 => 'javelin-typeahead-normalizer', ), 'disk' => '/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js', ), 'javelin-typeahead-static-source' => array( 'uri' => '/res/bb0a5173/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-typeahead-source', ), 'disk' => '/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js', ), 'javelin-uri' => array( 'uri' => '/res/75aa4597/rsrc/externals/javelin/lib/URI.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-stratcom', ), 'disk' => '/rsrc/externals/javelin/lib/URI.js', ), 'javelin-util' => array( 'uri' => '/res/90222113/rsrc/externals/javelin/core/util.js', 'type' => 'js', 'requires' => array( ), 'disk' => '/rsrc/externals/javelin/core/util.js', ), 'javelin-vector' => array( 'uri' => '/res/58ea3dd7/rsrc/externals/javelin/lib/Vector.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-event', ), 'disk' => '/rsrc/externals/javelin/lib/Vector.js', ), 'javelin-view' => array( 'uri' => '/res/38daaec0/rsrc/externals/javelin/ext/view/View.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', ), 'disk' => '/rsrc/externals/javelin/ext/view/View.js', ), 'javelin-view-html' => array( 'uri' => '/res/0d225e8c/rsrc/externals/javelin/ext/view/HTMLView.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-view-visitor', 3 => 'javelin-util', ), 'disk' => '/rsrc/externals/javelin/ext/view/HTMLView.js', ), 'javelin-view-interpreter' => array( 'uri' => '/res/b0c07f96/rsrc/externals/javelin/ext/view/ViewInterpreter.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-view', 1 => 'javelin-install', 2 => 'javelin-dom', ), 'disk' => '/rsrc/externals/javelin/ext/view/ViewInterpreter.js', ), 'javelin-view-renderer' => array( 'uri' => '/res/fe0d2f60/rsrc/externals/javelin/ext/view/ViewRenderer.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', ), 'disk' => '/rsrc/externals/javelin/ext/view/ViewRenderer.js', ), 'javelin-view-visitor' => array( 'uri' => '/res/b1606cec/rsrc/externals/javelin/ext/view/ViewVisitor.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', ), 'disk' => '/rsrc/externals/javelin/ext/view/ViewVisitor.js', ), 'javelin-workflow' => array( 'uri' => '/res/09a97dda/rsrc/externals/javelin/lib/Workflow.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-stratcom', 1 => 'javelin-request', 2 => 'javelin-dom', 3 => 'javelin-vector', 4 => 'javelin-install', 5 => 'javelin-util', 6 => 'javelin-mask', 7 => 'javelin-uri', ), 'disk' => '/rsrc/externals/javelin/lib/Workflow.js', ), 'lightbox-attachment-css' => array( 'uri' => '/res/4657e15d/rsrc/css/aphront/lightbox-attachment.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/lightbox-attachment.css', ), 'maniphest-batch-editor' => array( 'uri' => '/res/fb15d744/rsrc/css/application/maniphest/batch-editor.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/maniphest/batch-editor.css', ), 'maniphest-report-css' => array( 'uri' => '/res/2e633fcf/rsrc/css/application/maniphest/report.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/maniphest/report.css', ), 'maniphest-task-edit-css' => array( 'uri' => '/res/f5926f5a/rsrc/css/application/maniphest/task-edit.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/maniphest/task-edit.css', ), 'maniphest-task-summary-css' => array( 'uri' => '/res/5de3b188/rsrc/css/application/maniphest/task-summary.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/maniphest/task-summary.css', ), 'multirow-row-manager' => array( 'uri' => '/res/408fae4f/rsrc/js/core/MultirowRowManager.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-stratcom', 2 => 'javelin-dom', 3 => 'javelin-util', ), 'disk' => '/rsrc/js/core/MultirowRowManager.js', ), 'owners-path-editor' => array( 'uri' => '/res/29b68354/rsrc/js/application/owners/OwnersPathEditor.js', 'type' => 'js', 'requires' => array( 0 => 'multirow-row-manager', 1 => 'javelin-install', 2 => 'path-typeahead', 3 => 'javelin-dom', 4 => 'javelin-util', 5 => 'phabricator-prefab', ), 'disk' => '/rsrc/js/application/owners/OwnersPathEditor.js', ), 'owners-path-editor-css' => array( 'uri' => '/res/c91cc4a8/rsrc/css/application/owners/owners-path-editor.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/owners/owners-path-editor.css', ), 'paste-css' => array( 'uri' => '/res/216fbfe9/rsrc/css/application/paste/paste.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/paste/paste.css', ), 'path-typeahead' => array( 'uri' => '/res/50246fb6/rsrc/js/application/herald/PathTypeahead.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-typeahead', 2 => 'javelin-dom', 3 => 'javelin-request', 4 => 'javelin-typeahead-ondemand-source', 5 => 'javelin-util', ), 'disk' => '/rsrc/js/application/herald/PathTypeahead.js', ), 'people-profile-css' => array( 'uri' => '/res/f1da102e/rsrc/css/application/people/people-profile.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/people/people-profile.css', ), 'phabricator-action-header-view-css' => array( 'uri' => '/res/cd8b4a61/rsrc/css/layout/phabricator-action-header-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/layout/phabricator-action-header-view.css', ), 'phabricator-action-list-view-css' => array( 'uri' => '/res/2dce4556/rsrc/css/layout/phabricator-action-list-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/layout/phabricator-action-list-view.css', ), 'phabricator-application-launch-view-css' => array( 'uri' => '/res/21a67228/rsrc/css/application/base/phabricator-application-launch-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/base/phabricator-application-launch-view.css', ), 'phabricator-busy' => array( 'uri' => '/res/083c11d2/rsrc/js/core/Busy.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-fx', ), 'disk' => '/rsrc/js/core/Busy.js', ), 'phabricator-chatlog-css' => array( 'uri' => '/res/cf9b0aa7/rsrc/css/application/chatlog/chatlog.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/chatlog/chatlog.css', ), 'phabricator-content-source-view-css' => array( 'uri' => '/res/f15a9527/rsrc/css/application/contentsource/content-source-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/contentsource/content-source-view.css', ), 'phabricator-core-css' => array( 'uri' => '/res/9e767fb1/rsrc/css/core/core.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/core/core.css', ), 'phabricator-countdown-css' => array( 'uri' => '/res/d85bdfd5/rsrc/css/application/countdown/timer.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/countdown/timer.css', ), 'phabricator-crumbs-view-css' => array( 'uri' => '/res/f3c7068b/rsrc/css/layout/phabricator-crumbs-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/layout/phabricator-crumbs-view.css', ), 'phabricator-drag-and-drop-file-upload' => array( 'uri' => '/res/396d3b3b/rsrc/js/core/DragAndDropFileUpload.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-request', 3 => 'javelin-dom', 4 => 'javelin-uri', 5 => 'phabricator-file-upload', ), 'disk' => '/rsrc/js/core/DragAndDropFileUpload.js', ), 'phabricator-draggable-list' => array( 'uri' => '/res/75c556db/rsrc/js/core/DraggableList.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-util', 4 => 'javelin-vector', 5 => 'javelin-magical-init', ), 'disk' => '/rsrc/js/core/DraggableList.js', ), 'phabricator-dropdown-menu' => array( 'uri' => '/res/147ca011/rsrc/js/core/DropdownMenu.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-dom', 3 => 'javelin-vector', 4 => 'javelin-stratcom', 5 => 'phabricator-menu-item', ), 'disk' => '/rsrc/js/core/DropdownMenu.js', ), 'phabricator-fatal-config-template-css' => array( 'uri' => '/res/6e1a8d22/rsrc/css/application/config/config-template.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/config/config-template.css', ), 'phabricator-feed-css' => array( 'uri' => '/res/e19633ed/rsrc/css/application/feed/feed.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/feed/feed.css', ), 'phabricator-file-upload' => array( 'uri' => '/res/c9605008/rsrc/js/core/FileUpload.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'phabricator-notification', ), 'disk' => '/rsrc/js/core/FileUpload.js', ), 'phabricator-filetree-view-css' => array( 'uri' => '/res/c912ed91/rsrc/css/layout/phabricator-filetree-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/layout/phabricator-filetree-view.css', ), 'phabricator-flag-css' => array( 'uri' => '/res/cdb5cb1b/rsrc/css/application/flag/flag.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/flag/flag.css', ), 'phabricator-hovercard' => array( 'uri' => '/res/7fb94260/rsrc/js/core/Hovercard.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-vector', 3 => 'javelin-request', 4 => 'javelin-uri', ), 'disk' => '/rsrc/js/core/Hovercard.js', ), 'phabricator-hovercard-view-css' => array( 'uri' => '/res/79c61f0e/rsrc/css/layout/phabricator-hovercard-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/layout/phabricator-hovercard-view.css', ), 'phabricator-jump-nav' => array( 'uri' => '/res/7db8cead/rsrc/css/application/directory/phabricator-jump-nav.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/directory/phabricator-jump-nav.css', ), 'phabricator-keyboard-shortcut' => array( 'uri' => '/res/44747afd/rsrc/js/core/KeyboardShortcut.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'phabricator-keyboard-shortcut-manager', ), 'disk' => '/rsrc/js/core/KeyboardShortcut.js', ), 'phabricator-keyboard-shortcut-manager' => array( 'uri' => '/res/bf9bc02a/rsrc/js/core/KeyboardShortcutManager.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-stratcom', 3 => 'javelin-dom', 4 => 'javelin-vector', ), 'disk' => '/rsrc/js/core/KeyboardShortcutManager.js', ), 'phabricator-main-menu-view' => array( 'uri' => '/res/95ff522a/rsrc/css/application/base/main-menu-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/base/main-menu-view.css', ), 'phabricator-menu-item' => array( 'uri' => '/res/e810b0a1/rsrc/js/core/DropdownMenuItem.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', ), 'disk' => '/rsrc/js/core/DropdownMenuItem.js', ), 'phabricator-nav-view-css' => array( 'uri' => '/res/37955b6a/rsrc/css/aphront/phabricator-nav-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/phabricator-nav-view.css', ), 'phabricator-notification' => array( 'uri' => '/res/0764da14/rsrc/js/core/Notification.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-stratcom', 3 => 'javelin-util', 4 => 'phabricator-notification-css', ), 'disk' => '/rsrc/js/core/Notification.js', ), 'phabricator-notification-css' => array( 'uri' => '/res/2b9cdac0/rsrc/css/aphront/notification.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/notification.css', ), 'phabricator-notification-menu-css' => array( 'uri' => '/res/c6b17cfb/rsrc/css/application/base/notification-menu.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/base/notification-menu.css', ), 'phabricator-object-list-view-css' => array( 'uri' => '/res/4f183668/rsrc/css/application/projects/phabricator-object-list-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/projects/phabricator-object-list-view.css', ), 'phabricator-object-selector-css' => array( 'uri' => '/res/20c94e28/rsrc/css/application/objectselector/object-selector.css', 'type' => 'css', 'requires' => array( 0 => 'aphront-dialog-view-css', ), 'disk' => '/rsrc/css/application/objectselector/object-selector.css', ), 'phabricator-phtize' => array( 'uri' => '/res/dc655a81/rsrc/js/core/phtize.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-util', ), 'disk' => '/rsrc/js/core/phtize.js', ), 'phabricator-prefab' => array( 'uri' => '/res/511859ca/rsrc/js/core/Prefab.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-dom', 3 => 'javelin-typeahead', 4 => 'javelin-tokenizer', 5 => 'javelin-typeahead-preloaded-source', 6 => 'javelin-typeahead-ondemand-source', 7 => 'javelin-dom', 8 => 'javelin-stratcom', 9 => 'javelin-util', ), 'disk' => '/rsrc/js/core/Prefab.js', ), 'phabricator-profile-css' => array( 'uri' => '/res/c1f72695/rsrc/css/application/profile/profile-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/profile/profile-view.css', ), 'phabricator-project-tag-css' => array( 'uri' => '/res/535b8b7a/rsrc/css/application/projects/project-tag.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/projects/project-tag.css', ), 'phabricator-remarkup-css' => array( 'uri' => '/res/4c313572/rsrc/css/core/remarkup.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/core/remarkup.css', ), 'phabricator-search-results-css' => array( 'uri' => '/res/5407f3ea/rsrc/css/application/search/search-results.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/search/search-results.css', ), 'phabricator-settings-css' => array( 'uri' => '/res/fb9d017f/rsrc/css/application/settings/settings.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/settings/settings.css', ), 'phabricator-shaped-request' => array( 'uri' => '/res/d173af85/rsrc/js/core/ShapedRequest.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-request', ), 'disk' => '/rsrc/js/core/ShapedRequest.js', ), 'phabricator-side-menu-view-css' => array( 'uri' => '/res/e83fbe58/rsrc/css/layout/phabricator-side-menu-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/layout/phabricator-side-menu-view.css', ), 'phabricator-slowvote-css' => array( 'uri' => '/res/e61a54eb/rsrc/css/application/slowvote/slowvote.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/slowvote/slowvote.css', ), 'phabricator-source-code-view-css' => array( 'uri' => '/res/aee63670/rsrc/css/layout/phabricator-source-code-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/layout/phabricator-source-code-view.css', ), 'phabricator-standard-page-view' => array( 'uri' => '/res/eebd59cd/rsrc/css/application/base/standard-page-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/base/standard-page-view.css', ), 'phabricator-tag-view-css' => array( 'uri' => '/res/65ad2dc3/rsrc/css/layout/phabricator-tag-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/layout/phabricator-tag-view.css', ), 'phabricator-textareautils' => array( 'uri' => '/res/03c03e8b/rsrc/js/core/TextAreaUtils.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', ), 'disk' => '/rsrc/js/core/TextAreaUtils.js', ), 'phabricator-timeline-view-css' => array( 'uri' => '/res/d139291d/rsrc/css/layout/phabricator-timeline-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/layout/phabricator-timeline-view.css', ), 'phabricator-tooltip' => array( 'uri' => '/res/a23bc887/rsrc/js/core/ToolTip.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-util', 2 => 'javelin-dom', 3 => 'javelin-vector', ), 'disk' => '/rsrc/js/core/ToolTip.js', ), 'phabricator-transaction-view-css' => array( 'uri' => '/res/5e6237c6/rsrc/css/aphront/transaction.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/transaction.css', ), 'phabricator-ui-example-css' => array( 'uri' => '/res/376ab671/rsrc/css/application/uiexample/example.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/uiexample/example.css', ), 'phabricator-uiexample-javelin-view' => array( 'uri' => '/res/d42834b6/rsrc/js/application/uiexample/JavelinViewExample.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-view', ), 'disk' => '/rsrc/js/application/uiexample/JavelinViewExample.js', ), 'phabricator-uiexample-reactor-button' => array( 'uri' => '/res/6bfe4f05/rsrc/js/application/uiexample/ReactorButtonExample.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-util', 3 => 'javelin-dynval', 4 => 'javelin-reactor-dom', ), 'disk' => '/rsrc/js/application/uiexample/ReactorButtonExample.js', ), 'phabricator-uiexample-reactor-checkbox' => array( 'uri' => '/res/3e8b30ac/rsrc/js/application/uiexample/ReactorCheckboxExample.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), 'disk' => '/rsrc/js/application/uiexample/ReactorCheckboxExample.js', ), 'phabricator-uiexample-reactor-focus' => array( 'uri' => '/res/d8f3b56e/rsrc/js/application/uiexample/ReactorFocusExample.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), 'disk' => '/rsrc/js/application/uiexample/ReactorFocusExample.js', ), 'phabricator-uiexample-reactor-input' => array( 'uri' => '/res/936352d9/rsrc/js/application/uiexample/ReactorInputExample.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-reactor-dom', 2 => 'javelin-view-html', 3 => 'javelin-view-interpreter', 4 => 'javelin-view-renderer', ), 'disk' => '/rsrc/js/application/uiexample/ReactorInputExample.js', ), 'phabricator-uiexample-reactor-mouseover' => array( 'uri' => '/res/031a9f4f/rsrc/js/application/uiexample/ReactorMouseoverExample.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), 'disk' => '/rsrc/js/application/uiexample/ReactorMouseoverExample.js', ), 'phabricator-uiexample-reactor-radio' => array( 'uri' => '/res/208c58e3/rsrc/js/application/uiexample/ReactorRadioExample.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), 'disk' => '/rsrc/js/application/uiexample/ReactorRadioExample.js', ), 'phabricator-uiexample-reactor-select' => array( 'uri' => '/res/1b68a6db/rsrc/js/application/uiexample/ReactorSelectExample.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), 'disk' => '/rsrc/js/application/uiexample/ReactorSelectExample.js', ), 'phabricator-uiexample-reactor-sendclass' => array( 'uri' => '/res/00cb3131/rsrc/js/application/uiexample/ReactorSendClassExample.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), 'disk' => '/rsrc/js/application/uiexample/ReactorSendClassExample.js', ), 'phabricator-uiexample-reactor-sendproperties' => array( 'uri' => '/res/392f1e02/rsrc/js/application/uiexample/ReactorSendPropertiesExample.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), 'disk' => '/rsrc/js/application/uiexample/ReactorSendPropertiesExample.js', ), 'phabricator-zindex-css' => array( 'uri' => '/res/b443d508/rsrc/css/core/z-index.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/core/z-index.css', ), 'phame-css' => array( 'uri' => '/res/405e77b8/rsrc/css/application/phame/phame.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/phame/phame.css', ), 'pholio-css' => array( 'uri' => '/res/e64264c9/rsrc/css/application/pholio/pholio.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/pholio/pholio.css', ), 'pholio-edit-css' => array( 'uri' => '/res/04013652/rsrc/css/application/pholio/pholio-edit.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/pholio/pholio-edit.css', ), 'pholio-inline-comments-css' => array( 'uri' => '/res/67e2f18c/rsrc/css/application/pholio/pholio-inline-comments.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/pholio/pholio-inline-comments.css', ), 'phortune-credit-card-form' => array( 'uri' => '/res/bc948778/rsrc/js/application/phortune/phortune-credit-card-form.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-install', 1 => 'javelin-dom', 2 => 'javelin-json', 3 => 'javelin-workflow', 4 => 'javelin-util', ), 'disk' => '/rsrc/js/application/phortune/phortune-credit-card-form.js', ), 'phortune-credit-card-form-css' => array( 'uri' => '/res/563c8c6d/rsrc/css/application/phortune/phortune-credit-card-form.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/phortune/phortune-credit-card-form.css', ), 'phrequent-css' => array( 'uri' => '/res/e2f09149/rsrc/css/application/phrequent/phrequent.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/phrequent/phrequent.css', ), 'phriction-document-css' => array( 'uri' => '/res/97cbd7c6/rsrc/css/application/phriction/phriction-document-css.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/phriction/phriction-document-css.css', ), 'phui-box-css' => array( 'uri' => '/res/cd1b04cf/rsrc/css/phui/phui-box.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-box.css', ), 'phui-button-css' => array( 'uri' => '/res/abf52ae9/rsrc/css/phui/phui-button.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-button.css', ), 'phui-document-view-css' => array( 'uri' => '/res/cac7a825/rsrc/css/phui/phui-document.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-document.css', ), 'phui-feed-story-css' => array( 'uri' => '/res/8f28c686/rsrc/css/phui/phui-feed-story.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-feed-story.css', ), 'phui-form-css' => array( 'uri' => '/res/29b48d06/rsrc/css/phui/phui-form.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-form.css', ), 'phui-form-view-css' => array( 'uri' => '/res/3621b05d/rsrc/css/phui/phui-form-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-form-view.css', ), 'phui-header-view-css' => array( 'uri' => '/res/d6ca0939/rsrc/css/phui/phui-header-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-header-view.css', ), 'phui-icon-view-css' => array( 'uri' => '/res/28fb5ae5/rsrc/css/phui/phui-icon.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-icon.css', ), 'phui-info-panel-css' => array( 'uri' => '/res/e0ba8d04/rsrc/css/phui/phui-info-panel.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-info-panel.css', ), 'phui-list-view-css' => array( 'uri' => '/res/fbf42225/rsrc/css/phui/phui-list.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-list.css', ), 'phui-object-box-css' => array( 'uri' => '/res/8504279f/rsrc/css/phui/phui-object-box.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-object-box.css', ), 'phui-object-item-list-view-css' => array( 'uri' => '/res/c3a0ea74/rsrc/css/phui/phui-object-item-list-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-object-item-list-view.css', ), 'phui-pinboard-view-css' => array( 'uri' => '/res/f791ea99/rsrc/css/phui/phui-pinboard-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-pinboard-view.css', ), 'phui-property-list-view-css' => array( 'uri' => '/res/e1e6674b/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', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-remarkup-preview.css', ), 'phui-spacing-css' => array( 'uri' => '/res/28891fd3/rsrc/css/phui/phui-spacing.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-spacing.css', ), 'phui-status-list-view-css' => array( 'uri' => '/res/868f8a95/rsrc/css/phui/phui-status.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-status.css', ), 'phui-text-css' => array( 'uri' => '/res/63e53cac/rsrc/css/phui/phui-text.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-text.css', ), 'phui-workboard-view-css' => array( 'uri' => '/res/908b64b3/rsrc/css/phui/phui-workboard-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/phui/phui-workboard-view.css', ), 'phui-workpanel-view-css' => array( 'uri' => '/res/0b9a41d8/rsrc/css/phui/phui-workpanel-view.css', 'type' => 'css', 'requires' => array( ), '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', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/ponder/comments.css', ), 'ponder-feed-view-css' => array( 'uri' => '/res/cab09075/rsrc/css/application/ponder/feed.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/ponder/feed.css', ), 'ponder-post-css' => array( 'uri' => '/res/013b9e2c/rsrc/css/application/ponder/post.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/ponder/post.css', ), 'ponder-vote-css' => array( 'uri' => '/res/6bbe8538/rsrc/css/application/ponder/vote.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/ponder/vote.css', ), 'raphael-core' => array( 'uri' => '/res/5dc5e17c/rsrc/externals/raphael/raphael.js', 'type' => 'js', 'requires' => array( ), 'disk' => '/rsrc/externals/raphael/raphael.js', ), 'raphael-g' => array( 'uri' => '/res/229b89a1/rsrc/externals/raphael/g.raphael.js', 'type' => 'js', 'requires' => array( ), 'disk' => '/rsrc/externals/raphael/g.raphael.js', ), 'raphael-g-line' => array( 'uri' => '/res/96da30f7/rsrc/externals/raphael/g.raphael.line.js', 'type' => 'js', 'requires' => array( ), 'disk' => '/rsrc/externals/raphael/g.raphael.line.js', ), 'releeph-branch' => array( 'uri' => '/res/6ad6420d/rsrc/css/application/releeph/releeph-branch.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/releeph/releeph-branch.css', ), 'releeph-colors' => array( 'uri' => '/res/dff4b26a/rsrc/css/application/releeph/releeph-colors.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/releeph/releeph-colors.css', ), 'releeph-core' => array( 'uri' => '/res/dad04eff/rsrc/css/application/releeph/releeph-core.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/releeph/releeph-core.css', ), 'releeph-intents' => array( 'uri' => '/res/4e73e9dd/rsrc/css/application/releeph/releeph-intents.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/releeph/releeph-intents.css', ), 'releeph-preview-branch' => array( 'uri' => '/res/65e5dece/rsrc/css/application/releeph/releeph-preview-branch.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/releeph/releeph-preview-branch.css', ), 'releeph-project' => array( 'uri' => '/res/b9376e59/rsrc/css/application/releeph/releeph-project.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/releeph/releeph-project.css', ), 'releeph-request-differential-create-dialog' => array( 'uri' => '/res/4df30ce1/rsrc/css/application/releeph/releeph-request-differential-create-dialog.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/releeph/releeph-request-differential-create-dialog.css', ), 'releeph-request-typeahead-css' => array( 'uri' => '/res/9c9a1acf/rsrc/css/application/releeph/releeph-request-typeahead.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/releeph/releeph-request-typeahead.css', ), 'releeph-status' => array( 'uri' => '/res/588529df/rsrc/css/application/releeph/releeph-status.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/releeph/releeph-status.css', ), 'setup-issue-css' => array( 'uri' => '/res/135c19ed/rsrc/css/application/config/setup-issue.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/config/setup-issue.css', ), 'sprite-actions-css' => array( 'uri' => '/res/bd43efa8/rsrc/css/sprite-actions.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-actions.css', ), 'sprite-apps-css' => array( 'uri' => '/res/774f4bad/rsrc/css/sprite-apps.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-apps.css', ), 'sprite-apps-large-css' => array( 'uri' => '/res/b547fab1/rsrc/css/sprite-apps-large.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-apps-large.css', ), 'sprite-apps-xlarge-css' => array( 'uri' => '/res/33a8e644/rsrc/css/sprite-apps-xlarge.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-apps-xlarge.css', ), 'sprite-conpherence-css' => array( 'uri' => '/res/f6793453/rsrc/css/sprite-conpherence.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-conpherence.css', ), 'sprite-docs-css' => array( 'uri' => '/res/b32f93bc/rsrc/css/sprite-docs.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-docs.css', ), 'sprite-gradient-css' => array( 'uri' => '/res/e31d9063/rsrc/css/sprite-gradient.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-gradient.css', ), 'sprite-icons-css' => array( 'uri' => '/res/cb634e79/rsrc/css/sprite-icons.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-icons.css', ), 'sprite-login-css' => array( 'uri' => '/res/48dc427d/rsrc/css/sprite-login.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-login.css', ), 'sprite-menu-css' => array( 'uri' => '/res/764ab039/rsrc/css/sprite-menu.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-menu.css', ), 'sprite-minicons-css' => array( 'uri' => '/res/2dba70cd/rsrc/css/sprite-minicons.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-minicons.css', ), 'sprite-payments-css' => array( 'uri' => '/res/876697b6/rsrc/css/sprite-payments.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-payments.css', ), 'sprite-projects-css' => array( 'uri' => '/res/40eacbfb/rsrc/css/sprite-projects.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-projects.css', ), 'sprite-status-css' => array( 'uri' => '/res/f08fd1e1/rsrc/css/sprite-status.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-status.css', ), 'sprite-tokens-css' => array( 'uri' => '/res/eeca7cf1/rsrc/css/sprite-tokens.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/sprite-tokens.css', ), 'syntax-highlighting-css' => array( 'uri' => '/res/db7c0e13/rsrc/css/core/syntax.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/core/syntax.css', ), 'tokens-css' => array( 'uri' => '/res/bbddf548/rsrc/css/application/tokens/tokens.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/tokens/tokens.css', ), ), array( 'packages' => array( - 'f0d63822' => + 'd831cac3' => array( 'name' => 'core.pkg.css', 'symbols' => array( 0 => 'phabricator-core-css', 1 => 'phabricator-zindex-css', 2 => 'phui-button-css', 3 => 'phabricator-standard-page-view', 4 => 'aphront-dialog-view-css', 5 => 'phui-form-view-css', 6 => 'aphront-panel-view-css', 7 => 'aphront-table-view-css', 8 => 'aphront-tokenizer-control-css', 9 => 'aphront-typeahead-control-css', 10 => 'aphront-list-filter-view-css', 11 => 'phabricator-jump-nav', 12 => 'phabricator-remarkup-css', 13 => 'syntax-highlighting-css', 14 => 'aphront-pager-view-css', 15 => 'phabricator-transaction-view-css', 16 => 'aphront-tooltip-css', 17 => 'phabricator-flag-css', 18 => 'aphront-error-view-css', 19 => 'sprite-icons-css', 20 => 'sprite-gradient-css', 21 => 'sprite-menu-css', 22 => 'sprite-apps-large-css', 23 => 'sprite-status-css', 24 => 'phabricator-main-menu-view', 25 => 'phabricator-notification-css', 26 => 'phabricator-notification-menu-css', 27 => 'lightbox-attachment-css', 28 => 'phui-header-view-css', 29 => 'phabricator-filetree-view-css', 30 => 'phabricator-nav-view-css', 31 => 'phabricator-side-menu-view-css', 32 => 'phabricator-crumbs-view-css', 33 => 'phui-object-item-list-view-css', 34 => 'global-drag-and-drop-css', 35 => 'phui-spacing-css', 36 => 'phui-form-css', 37 => 'phui-icon-view-css', 38 => 'phabricator-application-launch-view-css', 39 => 'phabricator-action-list-view-css', 40 => 'phui-property-list-view-css', 41 => 'phabricator-tag-view-css', 42 => 'phui-list-view-css', ), - 'uri' => '/res/pkg/f0d63822/core.pkg.css', + 'uri' => '/res/pkg/d831cac3/core.pkg.css', 'type' => 'css', ), '2c1dba03' => array( 'name' => 'core.pkg.js', 'symbols' => array( 0 => 'javelin-behavior-aphront-basic-tokenizer', 1 => 'javelin-behavior-workflow', 2 => 'javelin-behavior-aphront-form-disable-on-submit', 3 => 'phabricator-keyboard-shortcut-manager', 4 => 'phabricator-keyboard-shortcut', 5 => 'javelin-behavior-phabricator-keyboard-shortcuts', 6 => 'javelin-behavior-refresh-csrf', 7 => 'javelin-behavior-phabricator-watch-anchor', 8 => 'javelin-behavior-phabricator-autofocus', 9 => 'phabricator-menu-item', 10 => 'phabricator-dropdown-menu', 11 => 'phabricator-phtize', 12 => 'javelin-behavior-phabricator-oncopy', 13 => 'phabricator-tooltip', 14 => 'javelin-behavior-phabricator-tooltips', 15 => 'phabricator-prefab', 16 => 'javelin-behavior-device', 17 => 'javelin-behavior-toggle-class', 18 => 'javelin-behavior-lightbox-attachments', 19 => 'phabricator-busy', 20 => 'javelin-aphlict', 21 => 'phabricator-notification', 22 => 'javelin-behavior-aphlict-listen', 23 => 'javelin-behavior-phabricator-search-typeahead', 24 => 'javelin-behavior-konami', 25 => 'javelin-behavior-aphlict-dropdown', 26 => 'javelin-behavior-history-install', 27 => 'javelin-behavior-phabricator-gesture', 28 => 'javelin-behavior-phabricator-active-nav', 29 => 'javelin-behavior-phabricator-nav', 30 => 'javelin-behavior-phabricator-remarkup-assist', 31 => 'phabricator-textareautils', 32 => 'phabricator-file-upload', 33 => 'javelin-behavior-global-drag-and-drop', 34 => 'javelin-behavior-phabricator-reveal-content', 35 => 'phabricator-hovercard', 36 => 'javelin-behavior-phabricator-hovercards', 37 => 'javelin-color', 38 => 'javelin-fx', ), 'uri' => '/res/pkg/2c1dba03/core.pkg.js', 'type' => 'js', ), '4ccfeb47' => array( 'name' => 'darkconsole.pkg.js', 'symbols' => array( 0 => 'javelin-behavior-dark-console', 1 => 'javelin-behavior-error-log', ), 'uri' => '/res/pkg/4ccfeb47/darkconsole.pkg.js', 'type' => 'js', ), '1084b12b' => array( 'name' => 'differential.pkg.css', 'symbols' => array( 0 => 'differential-core-view-css', 1 => 'differential-changeset-view-css', 2 => 'differential-results-table-css', 3 => 'differential-revision-history-css', 4 => 'differential-revision-list-css', 5 => 'differential-table-of-contents-css', 6 => 'differential-revision-comment-css', 7 => 'differential-revision-add-comment-css', 8 => 'differential-revision-comment-list-css', 9 => 'phabricator-object-selector-css', 10 => 'phabricator-content-source-view-css', 11 => 'differential-local-commits-view-css', 12 => 'inline-comment-summary-css', ), 'uri' => '/res/pkg/1084b12b/differential.pkg.css', 'type' => 'css', ), '5e9e5c4e' => array( 'name' => 'differential.pkg.js', 'symbols' => array( 0 => 'phabricator-drag-and-drop-file-upload', 1 => 'phabricator-shaped-request', 2 => 'javelin-behavior-differential-feedback-preview', 3 => 'javelin-behavior-differential-edit-inline-comments', 4 => 'javelin-behavior-differential-populate', 5 => 'javelin-behavior-differential-show-more', 6 => 'javelin-behavior-differential-diff-radios', 7 => 'javelin-behavior-differential-accept-with-errors', 8 => 'javelin-behavior-differential-comment-jump', 9 => 'javelin-behavior-differential-add-reviewers-and-ccs', 10 => 'javelin-behavior-differential-keyboard-navigation', 11 => 'javelin-behavior-aphront-drag-and-drop-textarea', 12 => 'javelin-behavior-phabricator-object-selector', 13 => 'javelin-behavior-repository-crossreference', 14 => 'javelin-behavior-load-blame', 15 => 'differential-inline-comment-editor', 16 => 'javelin-behavior-differential-dropdown-menus', 17 => 'javelin-behavior-differential-toggle-files', 18 => 'javelin-behavior-differential-user-select', ), 'uri' => '/res/pkg/5e9e5c4e/differential.pkg.js', 'type' => 'js', ), '7aa115b4' => array( 'name' => 'diffusion.pkg.css', 'symbols' => array( 0 => 'diffusion-commit-view-css', 1 => 'diffusion-icons-css', ), 'uri' => '/res/pkg/7aa115b4/diffusion.pkg.css', 'type' => 'css', ), 96909266 => array( 'name' => 'diffusion.pkg.js', 'symbols' => array( 0 => 'javelin-behavior-diffusion-pull-lastmodified', 1 => 'javelin-behavior-diffusion-commit-graph', 2 => 'javelin-behavior-audit-preview', ), 'uri' => '/res/pkg/96909266/diffusion.pkg.js', 'type' => 'js', ), '3e3be199' => array( 'name' => 'javelin.pkg.js', 'symbols' => array( 0 => 'javelin-util', 1 => 'javelin-install', 2 => 'javelin-event', 3 => 'javelin-stratcom', 4 => 'javelin-behavior', 5 => 'javelin-resource', 6 => 'javelin-request', 7 => 'javelin-vector', 8 => 'javelin-dom', 9 => 'javelin-json', 10 => 'javelin-uri', 11 => 'javelin-workflow', 12 => 'javelin-mask', 13 => 'javelin-typeahead', 14 => 'javelin-typeahead-normalizer', 15 => 'javelin-typeahead-source', 16 => 'javelin-typeahead-preloaded-source', 17 => 'javelin-typeahead-ondemand-source', 18 => 'javelin-tokenizer', 19 => 'javelin-history', ), 'uri' => '/res/pkg/3e3be199/javelin.pkg.js', 'type' => 'js', ), 49898640 => array( 'name' => 'maniphest.pkg.css', 'symbols' => array( 0 => 'maniphest-task-summary-css', 1 => 'phabricator-project-tag-css', ), 'uri' => '/res/pkg/49898640/maniphest.pkg.css', 'type' => 'css', ), - '0a694954' => + '0474f45c' => array( 'name' => 'maniphest.pkg.js', 'symbols' => array( 0 => 'javelin-behavior-maniphest-batch-selector', 1 => 'javelin-behavior-maniphest-transaction-controls', 2 => 'javelin-behavior-maniphest-transaction-preview', 3 => 'javelin-behavior-maniphest-transaction-expand', 4 => 'javelin-behavior-maniphest-subpriority-editor', ), - 'uri' => '/res/pkg/0a694954/maniphest.pkg.js', + 'uri' => '/res/pkg/0474f45c/maniphest.pkg.js', 'type' => 'js', ), ), 'reverse' => array( - 'aphront-dialog-view-css' => 'f0d63822', - 'aphront-error-view-css' => 'f0d63822', - 'aphront-list-filter-view-css' => 'f0d63822', - 'aphront-pager-view-css' => 'f0d63822', - 'aphront-panel-view-css' => 'f0d63822', - 'aphront-table-view-css' => 'f0d63822', - 'aphront-tokenizer-control-css' => 'f0d63822', - 'aphront-tooltip-css' => 'f0d63822', - 'aphront-typeahead-control-css' => 'f0d63822', + 'aphront-dialog-view-css' => 'd831cac3', + 'aphront-error-view-css' => 'd831cac3', + 'aphront-list-filter-view-css' => 'd831cac3', + 'aphront-pager-view-css' => 'd831cac3', + 'aphront-panel-view-css' => 'd831cac3', + 'aphront-table-view-css' => 'd831cac3', + 'aphront-tokenizer-control-css' => 'd831cac3', + 'aphront-tooltip-css' => 'd831cac3', + 'aphront-typeahead-control-css' => 'd831cac3', 'differential-changeset-view-css' => '1084b12b', 'differential-core-view-css' => '1084b12b', 'differential-inline-comment-editor' => '5e9e5c4e', 'differential-local-commits-view-css' => '1084b12b', 'differential-results-table-css' => '1084b12b', 'differential-revision-add-comment-css' => '1084b12b', 'differential-revision-comment-css' => '1084b12b', 'differential-revision-comment-list-css' => '1084b12b', 'differential-revision-history-css' => '1084b12b', 'differential-revision-list-css' => '1084b12b', 'differential-table-of-contents-css' => '1084b12b', 'diffusion-commit-view-css' => '7aa115b4', 'diffusion-icons-css' => '7aa115b4', - 'global-drag-and-drop-css' => 'f0d63822', + 'global-drag-and-drop-css' => 'd831cac3', 'inline-comment-summary-css' => '1084b12b', 'javelin-aphlict' => '2c1dba03', 'javelin-behavior' => '3e3be199', 'javelin-behavior-aphlict-dropdown' => '2c1dba03', 'javelin-behavior-aphlict-listen' => '2c1dba03', 'javelin-behavior-aphront-basic-tokenizer' => '2c1dba03', 'javelin-behavior-aphront-drag-and-drop-textarea' => '5e9e5c4e', 'javelin-behavior-aphront-form-disable-on-submit' => '2c1dba03', 'javelin-behavior-audit-preview' => '96909266', 'javelin-behavior-dark-console' => '4ccfeb47', 'javelin-behavior-device' => '2c1dba03', 'javelin-behavior-differential-accept-with-errors' => '5e9e5c4e', 'javelin-behavior-differential-add-reviewers-and-ccs' => '5e9e5c4e', 'javelin-behavior-differential-comment-jump' => '5e9e5c4e', 'javelin-behavior-differential-diff-radios' => '5e9e5c4e', 'javelin-behavior-differential-dropdown-menus' => '5e9e5c4e', 'javelin-behavior-differential-edit-inline-comments' => '5e9e5c4e', 'javelin-behavior-differential-feedback-preview' => '5e9e5c4e', 'javelin-behavior-differential-keyboard-navigation' => '5e9e5c4e', 'javelin-behavior-differential-populate' => '5e9e5c4e', 'javelin-behavior-differential-show-more' => '5e9e5c4e', 'javelin-behavior-differential-toggle-files' => '5e9e5c4e', 'javelin-behavior-differential-user-select' => '5e9e5c4e', 'javelin-behavior-diffusion-commit-graph' => '96909266', 'javelin-behavior-diffusion-pull-lastmodified' => '96909266', 'javelin-behavior-error-log' => '4ccfeb47', 'javelin-behavior-global-drag-and-drop' => '2c1dba03', 'javelin-behavior-history-install' => '2c1dba03', 'javelin-behavior-konami' => '2c1dba03', 'javelin-behavior-lightbox-attachments' => '2c1dba03', '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-maniphest-batch-selector' => '0474f45c', + 'javelin-behavior-maniphest-subpriority-editor' => '0474f45c', + 'javelin-behavior-maniphest-transaction-controls' => '0474f45c', + 'javelin-behavior-maniphest-transaction-expand' => '0474f45c', + 'javelin-behavior-maniphest-transaction-preview' => '0474f45c', 'javelin-behavior-phabricator-active-nav' => '2c1dba03', 'javelin-behavior-phabricator-autofocus' => '2c1dba03', 'javelin-behavior-phabricator-gesture' => '2c1dba03', 'javelin-behavior-phabricator-hovercards' => '2c1dba03', 'javelin-behavior-phabricator-keyboard-shortcuts' => '2c1dba03', 'javelin-behavior-phabricator-nav' => '2c1dba03', 'javelin-behavior-phabricator-object-selector' => '5e9e5c4e', 'javelin-behavior-phabricator-oncopy' => '2c1dba03', 'javelin-behavior-phabricator-remarkup-assist' => '2c1dba03', 'javelin-behavior-phabricator-reveal-content' => '2c1dba03', 'javelin-behavior-phabricator-search-typeahead' => '2c1dba03', 'javelin-behavior-phabricator-tooltips' => '2c1dba03', 'javelin-behavior-phabricator-watch-anchor' => '2c1dba03', 'javelin-behavior-refresh-csrf' => '2c1dba03', 'javelin-behavior-repository-crossreference' => '5e9e5c4e', 'javelin-behavior-toggle-class' => '2c1dba03', 'javelin-behavior-workflow' => '2c1dba03', 'javelin-color' => '2c1dba03', 'javelin-dom' => '3e3be199', 'javelin-event' => '3e3be199', 'javelin-fx' => '2c1dba03', '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' => 'f0d63822', + 'lightbox-attachment-css' => 'd831cac3', 'maniphest-task-summary-css' => '49898640', - 'phabricator-action-list-view-css' => 'f0d63822', - 'phabricator-application-launch-view-css' => 'f0d63822', + 'phabricator-action-list-view-css' => 'd831cac3', + 'phabricator-application-launch-view-css' => 'd831cac3', 'phabricator-busy' => '2c1dba03', 'phabricator-content-source-view-css' => '1084b12b', - 'phabricator-core-css' => 'f0d63822', - 'phabricator-crumbs-view-css' => 'f0d63822', + 'phabricator-core-css' => 'd831cac3', + 'phabricator-crumbs-view-css' => 'd831cac3', 'phabricator-drag-and-drop-file-upload' => '5e9e5c4e', 'phabricator-dropdown-menu' => '2c1dba03', 'phabricator-file-upload' => '2c1dba03', - 'phabricator-filetree-view-css' => 'f0d63822', - 'phabricator-flag-css' => 'f0d63822', + 'phabricator-filetree-view-css' => 'd831cac3', + 'phabricator-flag-css' => 'd831cac3', 'phabricator-hovercard' => '2c1dba03', - 'phabricator-jump-nav' => 'f0d63822', + 'phabricator-jump-nav' => 'd831cac3', 'phabricator-keyboard-shortcut' => '2c1dba03', 'phabricator-keyboard-shortcut-manager' => '2c1dba03', - 'phabricator-main-menu-view' => 'f0d63822', + 'phabricator-main-menu-view' => 'd831cac3', 'phabricator-menu-item' => '2c1dba03', - 'phabricator-nav-view-css' => 'f0d63822', + 'phabricator-nav-view-css' => 'd831cac3', 'phabricator-notification' => '2c1dba03', - 'phabricator-notification-css' => 'f0d63822', - 'phabricator-notification-menu-css' => 'f0d63822', + 'phabricator-notification-css' => 'd831cac3', + 'phabricator-notification-menu-css' => 'd831cac3', 'phabricator-object-selector-css' => '1084b12b', 'phabricator-phtize' => '2c1dba03', 'phabricator-prefab' => '2c1dba03', 'phabricator-project-tag-css' => '49898640', - 'phabricator-remarkup-css' => 'f0d63822', + 'phabricator-remarkup-css' => 'd831cac3', 'phabricator-shaped-request' => '5e9e5c4e', - 'phabricator-side-menu-view-css' => 'f0d63822', - 'phabricator-standard-page-view' => 'f0d63822', - 'phabricator-tag-view-css' => 'f0d63822', + 'phabricator-side-menu-view-css' => 'd831cac3', + 'phabricator-standard-page-view' => 'd831cac3', + 'phabricator-tag-view-css' => 'd831cac3', 'phabricator-textareautils' => '2c1dba03', 'phabricator-tooltip' => '2c1dba03', - 'phabricator-transaction-view-css' => 'f0d63822', - 'phabricator-zindex-css' => 'f0d63822', - 'phui-button-css' => 'f0d63822', - 'phui-form-css' => 'f0d63822', - 'phui-form-view-css' => 'f0d63822', - 'phui-header-view-css' => 'f0d63822', - 'phui-icon-view-css' => 'f0d63822', - 'phui-list-view-css' => 'f0d63822', - 'phui-object-item-list-view-css' => 'f0d63822', - 'phui-property-list-view-css' => 'f0d63822', - 'phui-spacing-css' => 'f0d63822', - 'sprite-apps-large-css' => 'f0d63822', - 'sprite-gradient-css' => 'f0d63822', - 'sprite-icons-css' => 'f0d63822', - 'sprite-menu-css' => 'f0d63822', - 'sprite-status-css' => 'f0d63822', - 'syntax-highlighting-css' => 'f0d63822', + 'phabricator-transaction-view-css' => 'd831cac3', + 'phabricator-zindex-css' => 'd831cac3', + 'phui-button-css' => 'd831cac3', + 'phui-form-css' => 'd831cac3', + 'phui-form-view-css' => 'd831cac3', + 'phui-header-view-css' => 'd831cac3', + 'phui-icon-view-css' => 'd831cac3', + 'phui-list-view-css' => 'd831cac3', + 'phui-object-item-list-view-css' => 'd831cac3', + 'phui-property-list-view-css' => 'd831cac3', + 'phui-spacing-css' => 'd831cac3', + 'sprite-apps-large-css' => 'd831cac3', + 'sprite-gradient-css' => 'd831cac3', + 'sprite-icons-css' => 'd831cac3', + 'sprite-menu-css' => 'd831cac3', + 'sprite-status-css' => 'd831cac3', + 'syntax-highlighting-css' => 'd831cac3', ), )); diff --git a/src/applications/auth/controller/PhabricatorEmailVerificationController.php b/src/applications/auth/controller/PhabricatorEmailVerificationController.php index 1836e1015..091575309 100644 --- a/src/applications/auth/controller/PhabricatorEmailVerificationController.php +++ b/src/applications/auth/controller/PhabricatorEmailVerificationController.php @@ -1,76 +1,88 @@ code = $data['code']; } public function shouldRequireEmailVerification() { // Since users need to be able to hit this endpoint in order to verify // email, we can't ever require email verification here. return false; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $email = id(new PhabricatorUserEmail())->loadOneWhere( 'userPHID = %s AND verificationCode = %s', $user->getPHID(), $this->code); if (!$email) { $title = pht('Unable to Verify Email'); $content = pht( 'The verification code you provided is incorrect, or the email '. 'address has been removed, or the email address is owned by another '. 'user. Make sure you followed the link in the email correctly and are '. 'logged in with the user account associated with the email address.'); $continue = pht('Rats!'); } else if ($email->getIsVerified()) { $title = pht('Address Already Verified'); $content = pht( 'This email address has already been verified.'); $continue = pht('Continue to Phabricator'); } else { $guard = AphrontWriteGuard::beginScopedUnguardedWrites(); - $email->setIsVerified(1); - $email->save(); + $email->openTransaction(); + + $email->setIsVerified(1); + $email->save(); + + // If the user just verified their primary email address, mark their + // account as email verified. + $user_primary = $user->loadPrimaryEmail(); + if ($user_primary->getID() == $email->getID()) { + $user->setIsEmailVerified(1); + $user->save(); + } + + $email->saveTransaction(); unset($guard); $title = pht('Address Verified'); $content = pht( 'The email address %s is now verified.', phutil_tag('strong', array(), $email->getAddress())); $continue = pht('Continue to Phabricator'); } $dialog = id(new AphrontDialogView()) ->setUser($user) ->setTitle($title) ->setMethod('GET') ->addCancelButton('/', $continue) ->appendChild($content); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('Verify Email'))); return $this->buildApplicationPage( array( $crumbs, $dialog, ), array( 'title' => pht('Verify Email'), 'device' => true, )); } } diff --git a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php index 7b580a1ef..beae85a3a 100644 --- a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php +++ b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php @@ -1,77 +1,68 @@ getRequest(); $user = $request->getUser(); $email = $user->loadPrimaryEmail(); if ($email->getIsVerified()) { return id(new AphrontRedirectResponse())->setURI('/'); } $email_address = $email->getAddress(); $sent = null; if ($request->isFormPost()) { $email->sendVerificationEmail($user); $sent = new AphrontErrorView(); $sent->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $sent->setTitle(pht('Email Sent')); - $sent->appendChild(phutil_tag( - 'p', - array(), + $sent->appendChild( pht( 'Another verification email was sent to %s.', - phutil_tag('strong', array(), $email_address)))); + phutil_tag('strong', array(), $email_address))); } - $error_view = new AphrontRequestFailureView(); - $error_view->setHeader(pht('Check Your Email')); - $error_view->appendChild(phutil_tag('p', array(), pht( - 'You must verify your email address to login. You should have a new '. - 'email message from Phabricator with verification instructions in your '. - 'inbox (%s).', phutil_tag('strong', array(), $email_address)))); - $error_view->appendChild(phutil_tag('p', array(), pht( + $must_verify = pht( + 'You must verify your email address to login. You should have a '. + 'new email message from Phabricator with verification instructions '. + 'in your inbox (%s).', + phutil_tag('strong', array(), $email_address)); + + $send_again = pht( 'If you did not receive an email, you can click the button below '. - 'to try sending another one.'))); - $error_view->appendChild(phutil_tag_div( - 'aphront-failure-continue', - phabricator_form( - $user, - array( - 'action' => '/login/mustverify/', - 'method' => 'POST', - ), - phutil_tag( - 'button', - array( - ), - pht('Send Another Email'))))); + 'to try sending another one.'); + $dialog = id(new AphrontDialogView()) + ->setUser($user) + ->setTitle(pht('Check Your Email')) + ->appendParagraph($must_verify) + ->appendParagraph($send_again) + ->addSubmitButton(pht('Send Another Email')); return $this->buildApplicationPage( array( $sent, - $error_view, + $dialog, ), array( 'title' => pht('Must Verify Email'), 'device' => true )); } } diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index d4cbd60de..6d7a6f0ae 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -1,411 +1,406 @@ getRequest(); if ($request->getUser()) { // NOTE: Unit tests can set a user explicitly. Normal requests are not // permitted to do this. PhabricatorTestCase::assertExecutingUnitTests(); $user = $request->getUser(); } else { $user = new PhabricatorUser(); $phusr = $request->getCookie('phusr'); $phsid = $request->getCookie('phsid'); if (strlen($phusr) && $phsid) { $info = queryfx_one( $user->establishConnection('r'), 'SELECT u.* FROM %T u JOIN %T s ON u.phid = s.userPHID AND s.type LIKE %> AND s.sessionKey = %s', $user->getTableName(), 'phabricator_session', 'web-', PhabricatorHash::digest($phsid)); if ($info) { $user->loadFromArray($info); } } $request->setUser($user); } $translation = $user->getTranslation(); if ($translation && $translation != PhabricatorEnv::getEnvConfig('translation.provider')) { $translation = newv($translation, array()); PhutilTranslator::getInstance() ->setLanguage($translation->getLanguage()) ->addTranslations($translation->getTranslations()); } $preferences = $user->loadPreferences(); if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) { $dark_console = PhabricatorUserPreferences::PREFERENCE_DARK_CONSOLE; if ($preferences->getPreference($dark_console) || PhabricatorEnv::getEnvConfig('darkconsole.always-on')) { $console = new DarkConsoleCore(); $request->getApplicationConfiguration()->setConsole($console); } } if ($user->getIsDisabled() && $this->shouldRequireEnabledUser()) { $disabled_user_controller = new PhabricatorDisabledUserController( $request); return $this->delegateToController($disabled_user_controller); } $event = new PhabricatorEvent( PhabricatorEventType::TYPE_CONTROLLER_CHECKREQUEST, array( 'request' => $request, 'controller' => $this, )); $event->setUser($user); PhutilEventEngine::dispatchEvent($event); $checker_controller = $event->getValue('controller'); if ($checker_controller != $this) { return $this->delegateToController($checker_controller); } if ($this->shouldRequireLogin()) { // This actually means we need either: // - a valid user, or a public controller; and // - permission to see the application. $auth_class = 'PhabricatorApplicationAuth'; $auth_application = PhabricatorApplication::getByClass($auth_class); $allow_public = $this->shouldAllowPublic() && PhabricatorEnv::getEnvConfig('policy.allow-public'); // If this controller isn't public, and the user isn't logged in, require // login. if (!$allow_public && !$user->isLoggedIn()) { $login_controller = new PhabricatorAuthStartController($request); $this->setCurrentApplication($auth_application); return $this->delegateToController($login_controller); } if ($user->isLoggedIn()) { if ($this->shouldRequireEmailVerification()) { - $email = $user->loadPrimaryEmail(); - if (!$email) { - throw new Exception( - "No primary email address associated with this account!"); - } - if (!$email->getIsVerified()) { + if (!$user->getIsEmailVerified()) { $controller = new PhabricatorMustVerifyEmailController($request); $this->setCurrentApplication($auth_application); return $this->delegateToController($controller); } } } // If the user doesn't have access to the application, don't let them use // any of its controllers. We query the application in order to generate // a policy exception if the viewer doesn't have permission. $application = $this->getCurrentApplication(); if ($application) { id(new PhabricatorApplicationQuery()) ->setViewer($user) ->withPHIDs(array($application->getPHID())) ->executeOne(); } } // NOTE: We do this last so that users get a login page instead of a 403 // if they need to login. if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) { return new Aphront403Response(); } } public function buildStandardPageView() { $view = new PhabricatorStandardPageView(); $view->setRequest($this->getRequest()); $view->setController($this); return $view; } public function buildStandardPageResponse($view, array $data) { $page = $this->buildStandardPageView(); $page->appendChild($view); $response = new AphrontWebpageResponse(); $response->setContent($page->render()); return $response; } public function getApplicationURI($path = '') { if (!$this->getCurrentApplication()) { throw new Exception("No application!"); } return $this->getCurrentApplication()->getApplicationURI($path); } public function buildApplicationPage($view, array $options) { $page = $this->buildStandardPageView(); $title = PhabricatorEnv::getEnvConfig('phabricator.serious-business') ? 'Phabricator' : pht('Bacon Ice Cream for Breakfast'); $application = $this->getCurrentApplication(); $page->setTitle(idx($options, 'title', $title)); if ($application) { $page->setApplicationName($application->getName()); if ($application->getTitleGlyph()) { $page->setGlyph($application->getTitleGlyph()); } } if (!($view instanceof AphrontSideNavFilterView)) { $nav = new AphrontSideNavFilterView(); $nav->appendChild($view); $view = $nav; } $user = $this->getRequest()->getUser(); $view->setUser($user); $page->appendChild($view); $object_phids = idx($options, 'pageObjects', array()); if ($object_phids) { $page->appendPageObjects($object_phids); foreach ($object_phids as $object_phid) { PhabricatorFeedStoryNotification::updateObjectNotificationViews( $user, $object_phid); } } if (idx($options, 'device')) { $page->setDeviceReady(true); } $page->setShowChrome(idx($options, 'chrome', true)); $application_menu = $this->buildApplicationMenu(); if ($application_menu) { $page->setApplicationMenu($application_menu); } $response = new AphrontWebpageResponse(); return $response->setContent($page->render()); } public function didProcessRequest($response) { $request = $this->getRequest(); $response->setRequest($request); $seen = array(); while ($response instanceof AphrontProxyResponse) { $hash = spl_object_hash($response); if (isset($seen[$hash])) { $seen[] = get_class($response); throw new Exception( "Cycle while reducing proxy responses: ". implode(' -> ', $seen)); } $seen[$hash] = get_class($response); $response = $response->reduceProxyResponse(); } if ($response instanceof AphrontDialogResponse) { if (!$request->isAjax()) { $view = new PhabricatorStandardPageView(); $view->setRequest($request); $view->setController($this); $view->appendChild(phutil_tag( 'div', array('style' => 'padding: 2em 0;'), $response->buildResponseString())); $page_response = new AphrontWebpageResponse(); $page_response->setContent($view->render()); $page_response->setHTTPResponseCode($response->getHTTPResponseCode()); return $page_response; } else { $response->getDialog()->setIsStandalone(true); return id(new AphrontAjaxResponse()) ->setContent(array( 'dialog' => $response->buildResponseString(), )); } } else if ($response instanceof AphrontRedirectResponse) { if ($request->isAjax()) { return id(new AphrontAjaxResponse()) ->setContent( array( 'redirect' => $response->getURI(), )); } } return $response; } protected function getHandle($phid) { if (empty($this->handles[$phid])) { throw new Exception( "Attempting to access handle which wasn't loaded: {$phid}"); } return $this->handles[$phid]; } protected function loadHandles(array $phids) { $phids = array_filter($phids); $this->handles = $this->loadViewerHandles($phids); return $this; } protected function getLoadedHandles() { return $this->handles; } protected function loadViewerHandles(array $phids) { return id(new PhabricatorHandleQuery()) ->setViewer($this->getRequest()->getUser()) ->withPHIDs($phids) ->execute(); } /** * Render a list of links to handles, identified by PHIDs. The handles must * already be loaded. * * @param list List of PHIDs to render links to. * @param string Style, one of "\n" (to put each item on its own line) * or "," (to list items inline, separated by commas). * @return string Rendered list of handle links. */ protected function renderHandlesForPHIDs(array $phids, $style = "\n") { $style_map = array( "\n" => phutil_tag('br'), ',' => ', ', ); if (empty($style_map[$style])) { throw new Exception("Unknown handle list style '{$style}'!"); } return implode_selected_handle_links($style_map[$style], $this->getLoadedHandles(), array_filter($phids)); } protected function buildApplicationMenu() { return null; } protected function buildApplicationCrumbs() { $crumbs = array(); $application = $this->getCurrentApplication(); if ($application) { $sprite = $application->getIconName(); if (!$sprite) { $sprite = 'application'; } $crumbs[] = id(new PhabricatorCrumbView()) ->setHref($this->getApplicationURI()) ->setIcon($sprite); } $view = new PhabricatorCrumbsView(); foreach ($crumbs as $crumb) { $view->addCrumb($crumb); } return $view; } protected function hasApplicationCapability($capability) { return PhabricatorPolicyFilter::hasCapability( $this->getRequest()->getUser(), $this->getCurrentApplication(), $capability); } protected function requireApplicationCapability($capability) { PhabricatorPolicyFilter::requireCapability( $this->getRequest()->getUser(), $this->getCurrentApplication(), $capability); } 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); } } diff --git a/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php b/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php index 1d6be0e79..28e7ca618 100644 --- a/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php +++ b/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php @@ -1,268 +1,268 @@ true, ); } public function testControllerAccessControls() { $root = dirname(phutil_get_library_root('phabricator')); require_once $root.'/support/PhabricatorStartup.php'; $application_configuration = new AphrontDefaultApplicationConfiguration(); $host = 'meow.example.com'; $_SERVER['REQUEST_METHOD'] = 'GET'; $request = id(new AphrontRequest($host, '/')) ->setApplicationConfiguration($application_configuration) ->setRequestData(array()); $controller = new PhabricatorTestController($request); $u_public = id(new PhabricatorUser()) ->setUsername('public'); $u_unverified = $this->generateNewTestUser() ->setUsername('unverified') ->save(); - $u_unverified->loadPrimaryEmail()->setIsVerified(0)->save(); + $u_unverified->setIsEmailVerified(0)->save(); $u_normal = $this->generateNewTestUser() ->setUsername('normal') ->save(); $u_disabled = $this->generateNewTestUser() ->setIsDisabled(true) ->setUsername('disabled') ->save(); $u_admin = $this->generateNewTestUser() ->setIsAdmin(true) ->setUsername('admin') ->save(); $env = PhabricatorEnv::beginScopedEnv(); $env->overrideEnvConfig('phabricator.base-uri', 'http://'.$host); $env->overrideEnvConfig('policy.allow-public', false); $env->overrideEnvConfig('auth.require-email-verification', false); $env->overrideEnvConfig('auth.email-domains', array()); // Test standard defaults. $this->checkAccess( "Default", id(clone $controller), $request, array( $u_normal, $u_admin, $u_unverified, ), array( $u_public, $u_disabled, )); // Test email verification. $env->overrideEnvConfig('auth.require-email-verification', true); $this->checkAccess( "Email Verification Required", id(clone $controller), $request, array( $u_normal, $u_admin, ), array( $u_unverified, $u_public, $u_disabled, )); $this->checkAccess( "Email Verification Required, With Exception", id(clone $controller)->setConfig('email', false), $request, array( $u_normal, $u_admin, $u_unverified, ), array( $u_public, $u_disabled, )); $env->overrideEnvConfig('auth.require-email-verification', false); // Test admin access. $this->checkAccess( "Admin Required", id(clone $controller)->setConfig('admin', true), $request, array( $u_admin, ), array( $u_normal, $u_unverified, $u_public, $u_disabled, )); // Test disabled access. $this->checkAccess( "Allow Disabled", id(clone $controller)->setConfig('enabled', false), $request, array( $u_normal, $u_unverified, $u_admin, $u_disabled, ), array( $u_public, )); // Test no login required. $this->checkAccess( "No Login Required", id(clone $controller)->setConfig('login', false), $request, array( $u_normal, $u_unverified, $u_admin, $u_public, ), array( $u_disabled, )); // Test public access. $this->checkAccess( "No Login Required", id(clone $controller)->setConfig('public', true), $request, array( $u_normal, $u_unverified, $u_admin, ), array( $u_disabled, $u_public, )); $env->overrideEnvConfig('policy.allow-public', true); $this->checkAccess( "Public + configured", id(clone $controller)->setConfig('public', true), $request, array( $u_normal, $u_unverified, $u_admin, $u_public, ), array( $u_disabled, )); $env->overrideEnvConfig('policy.allow-public', false); $app = PhabricatorApplication::getByClass('PhabricatorApplicationTest'); $app->reset(); $app->setPolicy( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicies::POLICY_NOONE); $app_controller = id(clone $controller)->setCurrentApplication($app); $this->checkAccess( "Application Controller", $app_controller, $request, array( ), array( $u_normal, $u_unverified, $u_admin, $u_public, $u_disabled, )); $this->checkAccess( "Application Controller", id(clone $app_controller)->setConfig('login', false), $request, array( $u_normal, $u_unverified, $u_admin, $u_public, ), array( $u_disabled, )); } private function checkAccess( $label, $controller, $request, array $yes, array $no) { foreach ($yes as $user) { $request->setUser($user); $uname = $user->getUsername(); try { $result = id(clone $controller)->willBeginExecution(); } catch (Exception $ex) { $result = $ex; } $this->assertEqual( true, ($result === null), "Expect user '{$uname}' to be allowed access to '{$label}'."); } foreach ($no as $user) { $request->setUser($user); $uname = $user->getUsername(); try { $result = id(clone $controller)->willBeginExecution(); } catch (Exception $ex) { $result = $ex; } $this->assertEqual( false, ($result === null), "Expect user '{$uname}' to be denied access to '{$label}'."); } } } diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php index ce6b6a02c..b08873f51 100644 --- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php @@ -1,505 +1,492 @@ method = $data['method']; return $this; } public function processRequest() { $time_start = microtime(true); $request = $this->getRequest(); $method = $this->method; $api_request = null; $log = new PhabricatorConduitMethodCallLog(); $log->setMethod($method); $metadata = array(); try { $params = $this->decodeConduitParams($request, $method); $metadata = idx($params, '__conduit__', array()); unset($params['__conduit__']); $call = new ConduitCall( $method, $params, idx($metadata, 'isProxied', false)); $result = null; // TODO: Straighten out the auth pathway here. We shouldn't be creating // a ConduitAPIRequest at this level, but some of the auth code expects // it. Landing a halfway version of this to unblock T945. $api_request = new ConduitAPIRequest($params); $allow_unguarded_writes = false; $auth_error = null; $conduit_username = '-'; if ($call->shouldRequireAuthentication()) { $metadata['scope'] = $call->getRequiredScope(); $auth_error = $this->authenticateUser($api_request, $metadata); // If we've explicitly authenticated the user here and either done // CSRF validation or are using a non-web authentication mechanism. $allow_unguarded_writes = true; if (isset($metadata['actAsUser'])) { $this->actAsUser($api_request, $metadata['actAsUser']); } if ($auth_error === null) { $conduit_user = $api_request->getUser(); if ($conduit_user && $conduit_user->getPHID()) { $conduit_username = $conduit_user->getUsername(); } $call->setUser($api_request->getUser()); } } $access_log = PhabricatorAccessLog::getLog(); if ($access_log) { $access_log->setData( array( 'u' => $conduit_username, 'm' => $method, )); } if ($call->shouldAllowUnguardedWrites()) { $allow_unguarded_writes = true; } if ($auth_error === null) { if ($allow_unguarded_writes) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); } try { $result = $call->execute(); $error_code = null; $error_info = null; } catch (ConduitException $ex) { $result = null; $error_code = $ex->getMessage(); if ($ex->getErrorDescription()) { $error_info = $ex->getErrorDescription(); } else { $error_info = $call->getErrorDescription($error_code); } } if ($allow_unguarded_writes) { unset($unguarded); } } else { list($error_code, $error_info) = $auth_error; } } catch (Exception $ex) { phlog($ex); $result = null; $error_code = ($ex instanceof ConduitException ? 'ERR-CONDUIT-CALL' : 'ERR-CONDUIT-CORE'); $error_info = $ex->getMessage(); } $time_end = microtime(true); $connection_id = null; if (idx($metadata, 'connectionID')) { $connection_id = $metadata['connectionID']; } else if (($method == 'conduit.connect') && $result) { $connection_id = idx($result, 'connectionID'); } $log ->setCallerPHID( isset($conduit_user) ? $conduit_user->getPHID() : null) ->setConnectionID($connection_id) ->setError((string)$error_code) ->setDuration(1000000 * ($time_end - $time_start)); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $log->save(); unset($unguarded); $response = id(new ConduitAPIResponse()) ->setResult($result) ->setErrorCode($error_code) ->setErrorInfo($error_info); switch ($request->getStr('output')) { case 'human': return $this->buildHumanReadableResponse( $method, $api_request, $response->toDictionary()); case 'json': default: return id(new AphrontJSONResponse()) ->setAddJSONShield(false) ->setContent($response->toDictionary()); } } /** * Change the api request user to the user that we want to act as. * Only admins can use actAsUser * * @param ConduitAPIRequest Request being executed. * @param string The username of the user we want to act as */ private function actAsUser( ConduitAPIRequest $api_request, $user_name) { if (!$api_request->getUser()->getIsAdmin()) { throw new Exception("Only administrators can use actAsUser"); } $user = id(new PhabricatorUser())->loadOneWhere( 'userName = %s', $user_name); if (!$user) { throw new Exception( "The actAsUser username '{$user_name}' is not a valid user." ); } $api_request->setUser($user); } /** * Authenticate the client making the request to a Phabricator user account. * * @param ConduitAPIRequest Request being executed. * @param dict Request metadata. * @return null|pair Null to indicate successful authentication, or * an error code and error message pair. */ private function authenticateUser( ConduitAPIRequest $api_request, array $metadata) { $request = $this->getRequest(); if ($request->getUser()->getPHID()) { $request->validateCSRF(); return $this->validateAuthenticatedUser( $api_request, $request->getUser()); } // handle oauth $access_token = $request->getStr('access_token'); $method_scope = $metadata['scope']; if ($access_token && $method_scope != PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE) { $token = id(new PhabricatorOAuthServerAccessToken()) ->loadOneWhere('token = %s', $access_token); if (!$token) { return array( 'ERR-INVALID-AUTH', 'Access token does not exist.', ); } $oauth_server = new PhabricatorOAuthServer(); $valid = $oauth_server->validateAccessToken($token, $method_scope); if (!$valid) { return array( 'ERR-INVALID-AUTH', 'Access token is invalid.', ); } // valid token, so let's log in the user! $user_phid = $token->getUserPHID(); $user = id(new PhabricatorUser()) ->loadOneWhere('phid = %s', $user_phid); if (!$user) { return array( 'ERR-INVALID-AUTH', 'Access token is for invalid user.', ); } return $this->validateAuthenticatedUser( $api_request, $user); } // Handle sessionless auth. TOOD: This is super messy. if (isset($metadata['authUser'])) { $user = id(new PhabricatorUser())->loadOneWhere( 'userName = %s', $metadata['authUser']); if (!$user) { return array( 'ERR-INVALID-AUTH', 'Authentication is invalid.', ); } $token = idx($metadata, 'authToken'); $signature = idx($metadata, 'authSignature'); $certificate = $user->getConduitCertificate(); if (sha1($token.$certificate) !== $signature) { return array( 'ERR-INVALID-AUTH', 'Authentication is invalid.', ); } return $this->validateAuthenticatedUser( $api_request, $user); } $session_key = idx($metadata, 'sessionKey'); if (!$session_key) { return array( 'ERR-INVALID-SESSION', 'Session key is not present.' ); } $session = queryfx_one( id(new PhabricatorUser())->establishConnection('r'), 'SELECT * FROM %T WHERE sessionKey = %s', PhabricatorUser::SESSION_TABLE, PhabricatorHash::digest($session_key)); if (!$session) { return array( 'ERR-INVALID-SESSION', 'Session key is invalid.', ); } // TODO: Make sessions timeout. // TODO: When we pull a session, read connectionID from the session table. $user = id(new PhabricatorUser())->loadOneWhere( 'phid = %s', $session['userPHID']); if (!$user) { return array( 'ERR-INVALID-SESSION', 'Session is for nonexistent user.', ); } return $this->validateAuthenticatedUser( $api_request, $user); } private function validateAuthenticatedUser( ConduitAPIRequest $request, PhabricatorUser $user) { - if ($user->getIsDisabled()) { + if (!$user->isUserActivated()) { return array( 'ERR-USER-DISABLED', - 'User is disabled.'); - } - - if (PhabricatorUserEmail::isEmailVerificationRequired()) { - $email = $user->loadPrimaryEmail(); - if (!$email) { - return array( - 'ERR-USER-NOEMAIL', - 'User has no primary email address.'); - } - if (!$email->getIsVerified()) { - return array( - 'ERR-USER-UNVERIFIED', - 'User has unverified email address.'); - } + pht('User account is not activated.'), + ); } $request->setUser($user); return null; } private function buildHumanReadableResponse( $method, ConduitAPIRequest $request = null, $result = null) { $param_rows = array(); $param_rows[] = array('Method', $this->renderAPIValue($method)); if ($request) { foreach ($request->getAllParameters() as $key => $value) { $param_rows[] = array( $key, $this->renderAPIValue($value), ); } } $param_table = new AphrontTableView($param_rows); $param_table->setDeviceReadyTable(true); $param_table->setColumnClasses( array( 'header', 'wide', )); $result_rows = array(); foreach ($result as $key => $value) { $result_rows[] = array( $key, $this->renderAPIValue($value), ); } $result_table = new AphrontTableView($result_rows); $result_table->setDeviceReadyTable(true); $result_table->setColumnClasses( array( 'header', 'wide', )); $param_panel = new AphrontPanelView(); $param_panel->setHeader('Method Parameters'); $param_panel->appendChild($param_table); $result_panel = new AphrontPanelView(); $result_panel->setHeader('Method Result'); $result_panel->appendChild($result_table); $param_head = id(new PHUIHeaderView()) ->setHeader(pht('Method Parameters')); $result_head = id(new PHUIHeaderView()) ->setHeader(pht('Method Result')); $method_uri = $this->getApplicationURI('method/'.$method.'/'); $crumbs = $this->buildApplicationCrumbs(); $crumbs ->addCrumb( id(new PhabricatorCrumbView()) ->setName($method) ->setHref($method_uri)) ->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('Call'))); return $this->buildApplicationPage( array( $crumbs, $param_head, $param_table, $result_head, $result_table, ), array( 'title' => 'Method Call Result', 'device' => true, )); } private function renderAPIValue($value) { $json = new PhutilJSON(); if (is_array($value)) { $value = $json->encodeFormatted($value); } $value = phutil_tag( 'pre', array('style' => 'white-space: pre-wrap;'), $value); return $value; } private function decodeConduitParams( AphrontRequest $request, $method) { // Look for parameters from the Conduit API Console, which are encoded // as HTTP POST parameters in an array, e.g.: // // params[name]=value¶ms[name2]=value2 // // The fields are individually JSON encoded, since we require users to // enter JSON so that we avoid type ambiguity. $params = $request->getArr('params', null); if ($params !== null) { foreach ($params as $key => $value) { if ($value == '') { // Interpret empty string null (e.g., the user didn't type anything // into the box). $value = 'null'; } $decoded_value = json_decode($value, true); if ($decoded_value === null && strtolower($value) != 'null') { // When json_decode() fails, it returns null. This almost certainly // indicates that a user was using the web UI and didn't put quotes // around a string value. We can either do what we think they meant // (treat it as a string) or fail. For now, err on the side of // caution and fail. In the future, if we make the Conduit API // actually do type checking, it might be reasonable to treat it as // a string if the parameter type is string. throw new Exception( "The value for parameter '{$key}' is not valid JSON. All ". "parameters must be encoded as JSON values, including strings ". "(which means you need to surround them in double quotes). ". "Check your syntax. Value was: {$value}"); } $params[$key] = $decoded_value; } return $params; } // Otherwise, look for a single parameter called 'params' which has the // entire param dictionary JSON encoded. This is the usual case for remote // requests. $params_json = $request->getStr('params'); if (!strlen($params_json)) { if ($request->getBool('allowEmptyParams')) { // TODO: This is a bit messy, but otherwise you can't call // "conduit.ping" from the web console. $params = array(); } else { throw new Exception( "Request has no 'params' key. This may mean that an extension like ". "Suhosin has dropped data from the request. Check the PHP ". "configuration on your server. If you are developing a Conduit ". "client, you MUST provide a 'params' parameter when making a ". "Conduit request, even if the value is empty (e.g., provide '{}')."); } } else { $params = json_decode($params_json, true); if (!is_array($params)) { throw new Exception( "Invalid parameter information was passed to method ". "'{$method}', could not decode JSON serialization. Data: ". $params_json); } } return $params; } } diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 3a7312c39..c009fbe83 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -1,549 +1,549 @@ getHTTPHeader('Content-Type'); $user_agent = idx($_SERVER, 'HTTP_USER_AGENT'); $vcs = null; if ($request->getExists('service')) { $service = $request->getStr('service'); // We get this initially for `info/refs`. // Git also gives us a User-Agent like "git/1.8.2.3". $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; } else if (strncmp($user_agent, "git/", 4) === 0) { $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; } else if ($content_type == 'application/x-git-upload-pack-request') { // We get this for `git-upload-pack`. $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; } else if ($content_type == 'application/x-git-receive-pack-request') { // We get this for `git-receive-pack`. $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; } else if ($request->getExists('cmd')) { // Mercurial also sends an Accept header like // "application/mercurial-0.1", and a User-Agent like // "mercurial/proto-1.0". $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL; } else { // Subversion also sends an initial OPTIONS request (vs GET/POST), and // has a User-Agent like "SVN/1.8.3 (x86_64-apple-darwin11.4.2) // serf/1.3.2". $dav = $request->getHTTPHeader('DAV'); $dav = new PhutilURI($dav); if ($dav->getDomain() === 'subversion.tigris.org') { $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_SVN; } } return $vcs; } private static function getCallsign(AphrontRequest $request) { $uri = $request->getRequestURI(); $regex = '@^/diffusion/(?P[A-Z]+)(/|$)@'; $matches = null; if (!preg_match($regex, (string)$uri, $matches)) { return null; } return $matches['callsign']; } public function processRequest() { $request = $this->getRequest(); $callsign = self::getCallsign($request); // If authentication credentials have been provided, try to find a user // that actually matches those credentials. if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { $username = $_SERVER['PHP_AUTH_USER']; $password = new PhutilOpaqueEnvelope($_SERVER['PHP_AUTH_PW']); $viewer = $this->authenticateHTTPRepositoryUser($username, $password); if (!$viewer) { return new PhabricatorVCSResponse( 403, pht('Invalid credentials.')); } } else { // User hasn't provided credentials, which means we count them as // being "not logged in". $viewer = new PhabricatorUser(); } $allow_public = PhabricatorEnv::getEnvConfig('policy.allow-public'); $allow_auth = PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth'); if (!$allow_public) { if (!$viewer->isLoggedIn()) { if ($allow_auth) { return new PhabricatorVCSResponse( 401, pht('You must log in to access repositories.')); } else { return new PhabricatorVCSResponse( 403, pht('Public and authenticated HTTP access are both forbidden.')); } } } try { $repository = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) ->withCallsigns(array($callsign)) ->executeOne(); if (!$repository) { return new PhabricatorVCSResponse( 404, pht('No such repository exists.')); } } catch (PhabricatorPolicyException $ex) { if ($viewer->isLoggedIn()) { return new PhabricatorVCSResponse( 403, pht('You do not have permission to access this repository.')); } else { if ($allow_auth) { return new PhabricatorVCSResponse( 401, pht('You must log in to access this repository.')); } else { return new PhabricatorVCSResponse( 403, pht( 'This repository requires authentication, which is forbidden '. 'over HTTP.')); } } } if (!$repository->isTracked()) { return new PhabricatorVCSResponse( 403, pht('This repository is inactive.')); } $is_push = !$this->isReadOnlyRequest($repository); switch ($repository->getServeOverHTTP()) { case PhabricatorRepository::SERVE_READONLY: if ($is_push) { return new PhabricatorVCSResponse( 403, pht('This repository is read-only over HTTP.')); } break; case PhabricatorRepository::SERVE_READWRITE: if ($is_push) { $can_push = PhabricatorPolicyFilter::hasCapability( $viewer, $repository, DiffusionCapabilityPush::CAPABILITY); if (!$can_push) { if ($viewer->isLoggedIn()) { return new PhabricatorVCSResponse( 403, pht('You do not have permission to push to this repository.')); } else { if ($allow_auth) { return new PhabricatorVCSResponse( 401, pht('You must log in to push to this repository.')); } else { return new PhabricatorVCSResponse( 403, pht( 'Pushing to this repository requires authentication, '. 'which is forbidden over HTTP.')); } } } } break; case PhabricatorRepository::SERVE_OFF: default: return new PhabricatorVCSResponse( 403, pht('This repository is not available over HTTP.')); } $vcs_type = $repository->getVersionControlSystem(); $req_type = $this->isVCSRequest($request); if ($vcs_type != $req_type) { switch ($req_type) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $result = new PhabricatorVCSResponse( 500, pht('This is not a Git repository.')); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $result = new PhabricatorVCSResponse( 500, pht('This is not a Mercurial repository.')); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $result = new PhabricatorVCSResponse( 500, pht('This is not a Subversion repository.')); break; default: $result = new PhabricatorVCSResponse( 500, pht('Unknown request type.')); break; } } else { switch ($vcs_type) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $result = $this->serveGitRequest($repository, $viewer); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $result = $this->serveMercurialRequest($repository, $viewer); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $result = new PhabricatorVCSResponse( 500, pht( 'Phabricator does not support HTTP access to Subversion '. 'repositories.')); break; default: $result = new PhabricatorVCSResponse( 500, pht('Unknown version control system.')); break; } } $code = $result->getHTTPResponseCode(); if ($is_push && ($code == 200)) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $repository->writeStatusMessage( PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE, PhabricatorRepositoryStatusMessage::CODE_OKAY); unset($unguarded); } return $result; } private function isReadOnlyRequest( PhabricatorRepository $repository) { $request = $this->getRequest(); $method = $_SERVER['REQUEST_METHOD']; // TODO: This implementation is safe by default, but very incomplete. switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $service = $request->getStr('service'); $path = $this->getRequestDirectoryPath(); // NOTE: Service names are the reverse of what you might expect, as they // are from the point of view of the server. The main read service is // "git-upload-pack", and the main write service is "git-receive-pack". if ($method == 'GET' && $path == '/info/refs' && $service == 'git-upload-pack') { return true; } if ($path == '/git-upload-pack') { return true; } break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $cmd = $request->getStr('cmd'); if ($cmd == 'batch') { $cmds = idx($this->getMercurialArguments(), 'cmds'); return DiffusionMercurialWireProtocol::isReadOnlyBatchCommand($cmds); } return DiffusionMercurialWireProtocol::isReadOnlyCommand($cmd); case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: break; } return false; } /** * @phutil-external-symbol class PhabricatorStartup */ private function serveGitRequest( PhabricatorRepository $repository, PhabricatorUser $viewer) { $request = $this->getRequest(); $request_path = $this->getRequestDirectoryPath(); $repository_root = $repository->getLocalPath(); // Rebuild the query string to strip `__magic__` parameters and prevent // issues where we might interpret inputs like "service=read&service=write" // differently than the server does and pass it an unsafe command. // NOTE: This does not use getPassthroughRequestParameters() because // that code is HTTP-method agnostic and will encode POST data. $query_data = $_GET; foreach ($query_data as $key => $value) { if (!strncmp($key, '__', 2)) { unset($query_data[$key]); } } $query_string = http_build_query($query_data, '', '&'); // We're about to wipe out PATH with the rest of the environment, so // resolve the binary first. $bin = Filesystem::resolveBinary('git-http-backend'); if (!$bin) { throw new Exception("Unable to find `git-http-backend` in PATH!"); } $env = array( 'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'], 'QUERY_STRING' => $query_string, 'CONTENT_TYPE' => $request->getHTTPHeader('Content-Type'), 'HTTP_CONTENT_ENCODING' => $request->getHTTPHeader('Content-Encoding'), 'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'], 'GIT_PROJECT_ROOT' => $repository_root, 'GIT_HTTP_EXPORT_ALL' => '1', 'PATH_INFO' => $request_path, 'REMOTE_USER' => $viewer->getUsername(), // TODO: Set these correctly. // GIT_COMMITTER_NAME // GIT_COMMITTER_EMAIL ); $input = PhabricatorStartup::getRawInput(); list($err, $stdout, $stderr) = id(new ExecFuture('%s', $bin)) ->setEnv($env, true) ->write($input) ->resolve(); if ($err) { if ($this->isValidGitShallowCloneResponse($stdout, $stderr)) { // Ignore the error if the response passes this special check for // validity. $err = 0; } } if ($err) { return new PhabricatorVCSResponse( 500, pht('Error %d: %s', $err, $stderr)); } return id(new DiffusionGitResponse())->setGitData($stdout); } private function getRequestDirectoryPath() { $request = $this->getRequest(); $request_path = $request->getRequestURI()->getPath(); return preg_replace('@^/diffusion/[A-Z]+@', '', $request_path); } private function authenticateHTTPRepositoryUser( $username, PhutilOpaqueEnvelope $password) { if (!PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth')) { // No HTTP auth permitted. return null; } if (!strlen($username)) { // No username. return null; } if (!strlen($password->openEnvelope())) { // No password. return null; } $user = id(new PhabricatorPeopleQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withUsernames(array($username)) ->executeOne(); if (!$user) { // Username doesn't match anything. return null; } + if (!$user->isUserActivated()) { + // User is not activated. + return null; + } + $password_entry = id(new PhabricatorRepositoryVCSPassword()) ->loadOneWhere('userPHID = %s', $user->getPHID()); if (!$password_entry) { // User doesn't have a password set. return null; } if (!$password_entry->comparePassword($password, $user)) { // Password doesn't match. return null; } - if ($user->getIsDisabled()) { - // User is disabled. - return null; - } - return $user; } private function serveMercurialRequest(PhabricatorRepository $repository) { $request = $this->getRequest(); $bin = Filesystem::resolveBinary('hg'); if (!$bin) { throw new Exception("Unable to find `hg` in PATH!"); } $env = array(); $input = PhabricatorStartup::getRawInput(); $cmd = $request->getStr('cmd'); $args = $this->getMercurialArguments(); $args = $this->formatMercurialArguments($cmd, $args); if (strlen($input)) { $input = strlen($input)."\n".$input."0\n"; } list($err, $stdout, $stderr) = id(new ExecFuture('%s serve --stdio', $bin)) ->setEnv($env, true) ->setCWD($repository->getLocalPath()) ->write("{$cmd}\n{$args}{$input}") ->resolve(); if ($err) { return new PhabricatorVCSResponse( 500, pht('Error %d: %s', $err, $stderr)); } if ($cmd == 'getbundle' || $cmd == 'changegroup' || $cmd == 'changegroupsubset') { // We're not completely sure that "changegroup" and "changegroupsubset" // actually work, they're for very old Mercurial. $body = gzcompress($stdout); } else if ($cmd == 'unbundle') { // This includes diagnostic information and anything echoed by commit // hooks. We ignore `stdout` since it just has protocol garbage, and // substitute `stderr`. $body = strlen($stderr)."\n".$stderr; } else { list($length, $body) = explode("\n", $stdout, 2); } return id(new DiffusionMercurialResponse())->setContent($body); } private function getMercurialArguments() { // Mercurial sends arguments in HTTP headers. "Why?", you might wonder, // "Why would you do this?". $args_raw = array(); for ($ii = 1; ; $ii++) { $header = 'HTTP_X_HGARG_'.$ii; if (!array_key_exists($header, $_SERVER)) { break; } $args_raw[] = $_SERVER[$header]; } $args_raw = implode('', $args_raw); return id(new PhutilQueryStringParser()) ->parseQueryString($args_raw); } private function formatMercurialArguments($command, array $arguments) { $spec = DiffusionMercurialWireProtocol::getCommandArgs($command); $out = array(); // Mercurial takes normal arguments like this: // // name // value $has_star = false; foreach ($spec as $arg_key) { if ($arg_key == '*') { $has_star = true; continue; } if (isset($arguments[$arg_key])) { $value = $arguments[$arg_key]; $size = strlen($value); $out[] = "{$arg_key} {$size}\n{$value}"; unset($arguments[$arg_key]); } } if ($has_star) { // Mercurial takes arguments for variable argument lists roughly like // this: // // * // argname1 // argvalue1 // argname2 // argvalue2 $count = count($arguments); $out[] = "* {$count}\n"; foreach ($arguments as $key => $value) { if (in_array($key, $spec)) { // We already added this argument above, so skip it. continue; } $size = strlen($value); $out[] = "{$key} {$size}\n{$value}"; } } return implode('', $out); } private function isValidGitShallowCloneResponse($stdout, $stderr) { // If you execute `git clone --depth N ...`, git sends a request which // `git-http-backend` responds to by emitting valid output and then exiting // with a failure code and an error message. If we ignore this error, // everything works. // This is a pretty funky fix: it would be nice to more precisely detect // that a request is a `--depth N` clone request, but we don't have any code // to decode protocol frames yet. Instead, look for reasonable evidence // in the error and output that we're looking at a `--depth` clone. // For evidence this isn't completely crazy, see: // https://github.com/schacon/grack/pull/7 $stdout_regexp = '(^Content-Type: application/x-git-upload-pack-result)m'; $stderr_regexp = '(The remote end hung up unexpectedly)'; $has_pack = preg_match($stdout_regexp, $stdout); $is_hangup = preg_match($stderr_regexp, $stderr); return $has_pack && $is_hangup; } } diff --git a/src/applications/herald/query/HeraldRuleQuery.php b/src/applications/herald/query/HeraldRuleQuery.php index e9d3d05d5..2b2924874 100644 --- a/src/applications/herald/query/HeraldRuleQuery.php +++ b/src/applications/herald/query/HeraldRuleQuery.php @@ -1,237 +1,237 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withAuthorPHIDs(array $author_phids) { $this->authorPHIDs = $author_phids; return $this; } public function withRuleTypes(array $types) { $this->ruleTypes = $types; return $this; } public function withContentTypes(array $types) { $this->contentTypes = $types; return $this; } public function withExecutableRules($executable) { $this->executable = $executable; return $this; } public function withDisabled($disabled) { $this->disabled = $disabled; return $this; } public function needConditionsAndActions($need) { $this->needConditionsAndActions = $need; return $this; } public function needAppliedToPHIDs(array $phids) { $this->needAppliedToPHIDs = $phids; return $this; } public function needValidateAuthors($need) { $this->needValidateAuthors = $need; return $this; } public function loadPage() { $table = new HeraldRule(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT rule.* FROM %T rule %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } public function willFilterPage(array $rules) { $rule_ids = mpull($rules, 'getID'); // Filter out any rules that have invalid adapters, or have adapters the // viewer isn't permitted to see or use (for example, Differential rules // if the user can't use Differential or Differential is not installed). $types = HeraldAdapter::getEnabledAdapterMap($this->getViewer()); foreach ($rules as $key => $rule) { if (empty($types[$rule->getContentType()])) { $this->didRejectResult($rule); unset($rules[$key]); } } if ($this->needValidateAuthors) { $this->validateRuleAuthors($rules); } if ($this->needConditionsAndActions) { $conditions = id(new HeraldCondition())->loadAllWhere( 'ruleID IN (%Ld)', $rule_ids); $conditions = mgroup($conditions, 'getRuleID'); $actions = id(new HeraldAction())->loadAllWhere( 'ruleID IN (%Ld)', $rule_ids); $actions = mgroup($actions, 'getRuleID'); foreach ($rules as $rule) { $rule->attachActions(idx($actions, $rule->getID(), array())); $rule->attachConditions(idx($conditions, $rule->getID(), array())); } } if ($this->needAppliedToPHIDs) { $conn_r = id(new HeraldRule())->establishConnection('r'); $applied = queryfx_all( $conn_r, 'SELECT * FROM %T WHERE ruleID IN (%Ld) AND phid IN (%Ls)', HeraldRule::TABLE_RULE_APPLIED, $rule_ids, $this->needAppliedToPHIDs); $map = array(); foreach ($applied as $row) { $map[$row['ruleID']][$row['phid']] = true; } foreach ($rules as $rule) { foreach ($this->needAppliedToPHIDs as $phid) { $rule->setRuleApplied( $phid, isset($map[$rule->getID()][$phid])); } } } return $rules; } private function buildWhereClause($conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'rule.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'rule.phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs) { $where[] = qsprintf( $conn_r, 'rule.authorPHID IN (%Ls)', $this->authorPHIDs); } if ($this->ruleTypes) { $where[] = qsprintf( $conn_r, 'rule.ruleType IN (%Ls)', $this->ruleTypes); } if ($this->contentTypes) { $where[] = qsprintf( $conn_r, 'rule.contentType IN (%Ls)', $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); } private function validateRuleAuthors(array $rules) { // "Global" rules always have valid authors. foreach ($rules as $key => $rule) { if ($rule->isGlobalRule()) { $rule->attachValidAuthor(true); unset($rules[$key]); continue; } } if (!$rules) { return; } // For personal rules, the author needs to exist and not be disabled. $user_phids = mpull($rules, 'getAuthorPHID'); $users = id(new PhabricatorPeopleQuery()) ->setViewer($this->getViewer()) ->withPHIDs($user_phids) ->execute(); $users = mpull($users, null, 'getPHID'); foreach ($rules as $key => $rule) { $author_phid = $rule->getAuthorPHID(); if (empty($users[$author_phid])) { $rule->attachValidAuthor(false); continue; } - if ($users[$author_phid]->getIsDisabled()) { + if (!$users[$author_phid]->isUserActivated()) { $rule->attachValidAuthor(false); continue; } $rule->attachValidAuthor(true); $rule->attachAuthor($users[$author_phid]); } } public function getQueryApplicationClass() { return 'PhabricatorApplicationHerald'; } } diff --git a/src/applications/metamta/query/PhabricatorMetaMTAActorQuery.php b/src/applications/metamta/query/PhabricatorMetaMTAActorQuery.php index 2e893dc93..ad94b9ff3 100644 --- a/src/applications/metamta/query/PhabricatorMetaMTAActorQuery.php +++ b/src/applications/metamta/query/PhabricatorMetaMTAActorQuery.php @@ -1,186 +1,191 @@ viewer = $viewer; return $this; } public function getViewer() { return $this->viewer; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function execute() { $phids = array_fuse($this->phids); $actors = array(); $type_map = array(); foreach ($phids as $phid) { $type_map[phid_get_type($phid)][] = $phid; $actors[$phid] = id(new PhabricatorMetaMTAActor())->setPHID($phid); } // TODO: Move this to PhabricatorPHIDType, or the objects, or some // interface. foreach ($type_map as $type => $phids) { switch ($type) { case PhabricatorPeoplePHIDTypeUser::TYPECONST: $this->loadUserActors($actors, $phids); break; case PhabricatorPeoplePHIDTypeExternal::TYPECONST: $this->loadExternalUserActors($actors, $phids); break; case PhabricatorMailingListPHIDTypeList::TYPECONST: $this->loadMailingListActors($actors, $phids); break; default: $this->loadUnknownActors($actors, $phids); break; } } return $actors; } private function loadUserActors(array $actors, array $phids) { assert_instances_of($actors, 'PhabricatorMetaMTAActor'); $emails = id(new PhabricatorUserEmail())->loadAllWhere( 'userPHID IN (%Ls) AND isPrimary = 1', $phids); $emails = mpull($emails, null, 'getUserPHID'); $users = id(new PhabricatorPeopleQuery()) ->setViewer($this->getViewer()) ->withPHIDs($phids) ->execute(); $users = mpull($users, null, 'getPHID'); foreach ($phids as $phid) { $actor = $actors[$phid]; $user = idx($users, $phid); if (!$user) { $actor->setUndeliverable( pht('Unable to load user record for this PHID.')); } else { $actor->setName($this->getUserName($user)); if ($user->getIsDisabled()) { $actor->setUndeliverable( pht('This user is disabled; disabled users do not receive mail.')); } if ($user->getIsSystemAgent()) { $actor->setUndeliverable( pht('This user is a bot; bot accounts do not receive mail.')); } + + // NOTE: We do send email to unapproved users, and to unverified users, + // because it would otherwise be impossible to get them to verify their + // email addresses. Possibly we should white-list this kind of mail and + // deny all other types of mail. } $email = idx($emails, $phid); if (!$email) { $actor->setUndeliverable( pht('Unable to load email record for this PHID.')); } else { $actor->setEmailAddress($email->getAddress()); } } } private function loadExternalUserActors(array $actors, array $phids) { assert_instances_of($actors, 'PhabricatorMetaMTAActor'); $xusers = id(new PhabricatorExternalAccountQuery()) ->setViewer($this->getViewer()) ->withPHIDs($phids) ->execute(); $xusers = mpull($xusers, null, 'getPHID'); foreach ($phids as $phid) { $actor = $actors[$phid]; $xuser = idx($xusers, $phid); if (!$xuser) { $actor->setUndeliverable( pht('Unable to load external user record for this PHID.')); continue; } $actor->setName($xuser->getDisplayName()); if ($xuser->getAccountType() != 'email') { $actor->setUndeliverable( pht( 'Only external accounts of type "email" are deliverable; this '. 'account has a different type.')); continue; } $actor->setEmailAddress($xuser->getAccountID()); } } private function loadMailingListActors(array $actors, array $phids) { assert_instances_of($actors, 'PhabricatorMetaMTAActor'); $lists = id(new PhabricatorMailingListQuery()) ->setViewer($this->getViewer()) ->withPHIDs($phids) ->execute(); $lists = mpull($lists, null, 'getPHID'); foreach ($phids as $phid) { $actor = $actors[$phid]; $list = idx($lists, $phid); if (!$list) { $actor->setUndeliverable( pht( 'Unable to load mailing list record for this PHID.')); continue; } $actor->setName($list->getName()); $actor->setEmailAddress($list->getEmail()); } } private function loadUnknownActors(array $actors, array $phids) { foreach ($phids as $phid) { $actor = $actors[$phid]; $actor->setUndeliverable(pht('This PHID type is not mailable.')); } } /** * Small helper function to make sure we format the username properly as * specified by the `metamta.user-address-format` configuration value. */ private function getUserName(PhabricatorUser $user) { $format = PhabricatorEnv::getEnvConfig('metamta.user-address-format'); switch ($format) { case 'short': $name = $user->getUserName(); break; case 'real': $name = $user->getRealName(); break; case 'full': default: $name = $user->getFullName(); break; } return $name; } } diff --git a/src/applications/metamta/receiver/PhabricatorMailReceiver.php b/src/applications/metamta/receiver/PhabricatorMailReceiver.php index d621497ec..8899b182e 100644 --- a/src/applications/metamta/receiver/PhabricatorMailReceiver.php +++ b/src/applications/metamta/receiver/PhabricatorMailReceiver.php @@ -1,192 +1,189 @@ processReceivedMail($mail, $sender); } public function validateSender( PhabricatorMetaMTAReceivedMail $mail, PhabricatorUser $sender) { - if ($sender->getIsDisabled()) { + if (!$sender->isUserActivated()) { throw new PhabricatorMetaMTAReceivedMailProcessingException( MetaMTAReceivedMailStatus::STATUS_DISABLED_SENDER, pht( - "Sender '%s' has a disabled user account.", + "Sender '%s' does not have an activated user account.", $sender->getUsername())); } - - - return; } /** * Identifies the sender's user account for a piece of received mail. Note * that this method does not validate that the sender is who they say they * are, just that they've presented some credential which corresponds to a * recognizable user. */ public function loadSender(PhabricatorMetaMTAReceivedMail $mail) { $raw_from = $mail->getHeader('From'); $from = self::getRawAddress($raw_from); $reasons = array(); // Try to find a user with this email address. $user = PhabricatorUser::loadOneWithEmailAddress($from); if ($user) { return $user; } else { $reasons[] = pht( "The email was sent from '%s', but this address does not correspond ". "to any user account.", $raw_from); } // If we missed on "From", try "Reply-To" if we're configured for it. $reply_to_key = 'metamta.insecure-auth-with-reply-to'; $allow_reply_to = PhabricatorEnv::getEnvConfig($reply_to_key); if ($allow_reply_to) { $raw_reply_to = $mail->getHeader('Reply-To'); $reply_to = self::getRawAddress($raw_reply_to); $user = PhabricatorUser::loadOneWithEmailAddress($reply_to); if ($user) { return $user; } else { $reasons[] = pht( "Phabricator is configured to try to authenticate users using ". "'Reply-To', but the reply to address ('%s') does not correspond ". "to any user account.", $raw_reply_to); } } else { $reasons[] = pht( "Phabricator is not configured to authenticate users using ". "'Reply-To' (`metamta.insecure-auth-with-reply-to`), so the ". "'Reply-To' header was not examined."); } // If we don't know who this user is, load or create an external user // account for them if we're configured for it. $email_key = 'phabricator.allow-email-users'; $allow_email_users = PhabricatorEnv::getEnvConfig($email_key); if ($allow_email_users) { $xuser = id(new PhabricatorExternalAccount())->loadOneWhere( 'accountType = %s AND accountDomain = %s and accountID = %s', 'email', 'self', $from); if (!$xuser) { $xuser = id(new PhabricatorExternalAccount()) ->setAccountID($from) ->setAccountType('email') ->setAccountDomain('self') ->setDisplayName($from) ->setEmail($from) ->save(); } return $xuser->getPhabricatorUser(); } else { $reasons[] = pht( "Phabricator is not configured to allow unknown external users to ". "send mail to the system using just an email address ". "(`phabricator.allow-email-users`), so an implicit external acount ". "could not be created."); } throw new PhabricatorMetaMTAReceivedMailProcessingException( MetaMTAReceivedMailStatus::STATUS_UNKNOWN_SENDER, pht('Unknown sender: %s', implode(' ', $reasons))); } /** * Determine if two inbound email addresses are effectively identical. This * method strips and normalizes addresses so that equivalent variations are * correctly detected as identical. For example, these addresses are all * considered to match one another: * * "Abraham Lincoln" * alincoln@example.com * * "Abraham" # With configured prefix. * * @param string Email address. * @param string Another email address. * @return bool True if addresses match. */ public static function matchAddresses($u, $v) { $u = self::getRawAddress($u); $v = self::getRawAddress($v); $u = self::stripMailboxPrefix($u); $v = self::stripMailboxPrefix($v); return ($u === $v); } /** * Strip a global mailbox prefix from an address if it is present. Phabricator * can be configured to prepend a prefix to all reply addresses, which can * make forwarding rules easier to write. A prefix looks like: * * example@phabricator.example.com # No Prefix * phabricator+example@phabricator.example.com # Prefix "phabricator" * * @param string Email address, possibly with a mailbox prefix. * @return string Email address with any prefix stripped. */ public static function stripMailboxPrefix($address) { $address = id(new PhutilEmailAddress($address))->getAddress(); $prefix_key = 'metamta.single-reply-handler-prefix'; $prefix = PhabricatorEnv::getEnvConfig($prefix_key); $len = strlen($prefix); if ($len) { $prefix = $prefix.'+'; $len = $len + 1; } if ($len) { if (!strncasecmp($address, $prefix, $len)) { $address = substr($address, strlen($prefix)); } } return $address; } /** * Reduce an email address to its canonical form. For example, an adddress * like: * * "Abraham Lincoln" < ALincoln@example.com > * * ...will be reduced to: * * alincoln@example.com * * @param string Email address in noncanonical form. * @return string Canonical email address. */ public static function getRawAddress($address) { $address = id(new PhutilEmailAddress($address))->getAddress(); return trim(phutil_utf8_strtolower($address)); } } diff --git a/src/applications/people/conduit/ConduitAPI_user_Method.php b/src/applications/people/conduit/ConduitAPI_user_Method.php index a8207f417..8f9e2f899 100644 --- a/src/applications/people/conduit/ConduitAPI_user_Method.php +++ b/src/applications/people/conduit/ConduitAPI_user_Method.php @@ -1,52 +1,60 @@ getIsDisabled()) { $roles[] = 'disabled'; } if ($user->getIsSystemAgent()) { $roles[] = 'agent'; } if ($user->getIsAdmin()) { $roles[] = 'admin'; } $primary = $user->loadPrimaryEmail(); if ($primary && $primary->getIsVerified()) { $roles[] = 'verified'; } else { $roles[] = 'unverified'; } + if ($user->getIsApproved()) { + $roles[] = 'approved'; + } + + if ($user->isUserActivated()) { + $roles[] = 'activated'; + } + $return = array( 'phid' => $user->getPHID(), 'userName' => $user->getUserName(), 'realName' => $user->getRealName(), 'image' => $user->loadProfileImageURI(), 'uri' => PhabricatorEnv::getURI('/p/'.$user->getUsername().'/'), 'roles' => $roles, ); if ($current_status) { $return['currentStatus'] = $current_status->getTextStatus(); $return['currentStatusUntil'] = $current_status->getDateTo(); } return $return; } } diff --git a/src/applications/people/controller/PhabricatorPeopleEditController.php b/src/applications/people/controller/PhabricatorPeopleEditController.php index ab466dabf..904402dfb 100644 --- a/src/applications/people/controller/PhabricatorPeopleEditController.php +++ b/src/applications/people/controller/PhabricatorPeopleEditController.php @@ -1,836 +1,838 @@ id = idx($data, 'id'); $this->view = idx($data, 'view'); } public function processRequest() { $request = $this->getRequest(); $admin = $request->getUser(); $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); if ($this->id) { $user = id(new PhabricatorUser())->load($this->id); if (!$user) { return new Aphront404Response(); } $base_uri = '/people/edit/'.$user->getID().'/'; $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('Edit User')) ->setHref('/people/edit/')); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName($user->getFullName()) ->setHref($base_uri)); } else { $user = new PhabricatorUser(); $base_uri = '/people/edit/'; $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('Create New User')) ->setHref($base_uri)); } $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($base_uri)); $nav->addLabel(pht('User Information')); $nav->addFilter('basic', pht('Basic Information')); $nav->addFilter('role', pht('Edit Roles')); $nav->addFilter('cert', pht('Conduit Certificate')); $nav->addFilter('profile', pht('View Profile'), '/p/'.$user->getUsername().'/'); $nav->addLabel(pht('Special')); $nav->addFilter('rename', pht('Change Username')); if ($user->getIsSystemAgent()) { $nav->addFilter('picture', pht('Set Account Picture')); } $nav->addFilter('delete', pht('Delete User')); if (!$user->getID()) { $this->view = 'basic'; } $view = $nav->selectFilter($this->view, 'basic'); $content = array(); if ($request->getStr('saved')) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle(pht('Changes Saved')); $notice->appendChild( phutil_tag('p', array(), pht('Your changes were saved.'))); $content[] = $notice; } switch ($view) { case 'basic': $response = $this->processBasicRequest($user); break; case 'role': $response = $this->processRoleRequest($user); break; case 'cert': $response = $this->processCertificateRequest($user); break; case 'rename': $response = $this->processRenameRequest($user); break; case 'picture': $response = $this->processSetAccountPicture($user); break; case 'delete': $response = $this->processDeleteRequest($user); break; default: return new Aphront404Response(); } if ($response instanceof AphrontResponse) { return $response; } $content[] = $response; if ($user->getID()) { $nav->appendChild($content); } else { $nav = $this->buildSideNavView(); $nav->selectFilter('edit'); $nav->appendChild($content); } $nav->setCrumbs($crumbs); return $this->buildApplicationPage( $nav, array( 'title' => pht('Edit User'), 'device' => true, )); } private function processBasicRequest(PhabricatorUser $user) { $request = $this->getRequest(); $admin = $request->getUser(); $e_username = true; $e_realname = true; $e_email = true; $errors = array(); $welcome_checked = true; $new_email = null; $request = $this->getRequest(); if ($request->isFormPost()) { $welcome_checked = $request->getInt('welcome'); $is_new = !$user->getID(); if ($is_new) { $user->setUsername($request->getStr('username')); $new_email = $request->getStr('email'); if (!strlen($new_email)) { $errors[] = pht('Email is required.'); $e_email = pht('Required'); } else if (!PhabricatorUserEmail::isAllowedAddress($new_email)) { $e_email = pht('Invalid'); $errors[] = PhabricatorUserEmail::describeAllowedAddresses(); } else { $e_email = null; } } $user->setRealName($request->getStr('realname')); if (!strlen($user->getUsername())) { $errors[] = pht("Username is required."); $e_username = pht('Required'); } else if (!PhabricatorUser::validateUsername($user->getUsername())) { $errors[] = PhabricatorUser::describeValidUsername(); $e_username = pht('Invalid'); } else { $e_username = null; } if (!strlen($user->getRealName())) { $errors[] = pht('Real name is required.'); $e_realname = pht('Required'); } else { $e_realname = null; } if (!$errors) { try { if (!$is_new) { id(new PhabricatorUserEditor()) ->setActor($admin) ->updateUser($user); } else { $email = id(new PhabricatorUserEmail()) ->setAddress($new_email) ->setIsVerified(0); id(new PhabricatorUserEditor()) ->setActor($admin) ->createNewUser($user, $email); if ($request->getStr('role') == 'agent') { id(new PhabricatorUserEditor()) ->setActor($admin) ->makeSystemAgentUser($user, true); } } if ($welcome_checked) { $user->sendWelcomeEmail($admin); } $response = id(new AphrontRedirectResponse()) ->setURI('/people/edit/'.$user->getID().'/?saved=true'); return $response; } catch (AphrontQueryDuplicateKeyException $ex) { $errors[] = pht('Username and email must be unique.'); $same_username = id(new PhabricatorUser()) ->loadOneWhere('username = %s', $user->getUsername()); $same_email = id(new PhabricatorUserEmail()) ->loadOneWhere('address = %s', $new_email); if ($same_username) { $e_username = pht('Duplicate'); } if ($same_email) { $e_email = pht('Duplicate'); } } } } $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle(pht('Form Errors')) ->setErrors($errors); } $form = new AphrontFormView(); $form->setUser($admin); if ($user->getID()) { $form->setAction('/people/edit/'.$user->getID().'/'); } else { $form->setAction('/people/edit/'); } if ($user->getID()) { $is_immutable = true; } else { $is_immutable = false; } $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Username')) ->setName('username') ->setValue($user->getUsername()) ->setError($e_username) ->setDisabled($is_immutable)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Real Name')) ->setName('realname') ->setValue($user->getRealName()) ->setError($e_realname)); if (!$user->getID()) { $form->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Email')) ->setName('email') ->setDisabled($is_immutable) ->setValue($new_email) ->setCaption(PhabricatorUserEmail::describeAllowedAddresses()) ->setError($e_email)); } else { $email = $user->loadPrimaryEmail(); if ($email) { $status = $email->getIsVerified() ? pht('Verified') : pht('Unverified'); } else { $status = pht('No Email Address'); } $form->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Email')) ->setValue($status)); $form->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'welcome', 1, pht('Re-send "Welcome to Phabricator" email.'), false)); } $form->appendChild($this->getRoleInstructions()); if (!$user->getID()) { $form ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Role')) ->setName('role') ->setValue('user') ->setOptions( array( 'user' => pht('Normal User'), 'agent' => pht('System Agent'), )) ->setCaption( pht('You can create a "system agent" account for bots, '. 'scripts, etc.'))) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'welcome', 1, pht('Send "Welcome to Phabricator" email.'), $welcome_checked)); } else { $roles = array(); if ($user->getIsSystemAgent()) { $roles[] = pht('System Agent'); } if ($user->getIsAdmin()) { $roles[] = pht('Admin'); } if ($user->getIsDisabled()) { $roles[] = pht('Disabled'); } - + if (!$user->getIsApproved()) { + $roles[] = pht('Not Approved'); + } if (!$roles) { $roles[] = pht('Normal User'); } $roles = implode(', ', $roles); $form->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Roles')) ->setValue($roles)); } $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save'))); if ($user->getID()) { $title = pht('Edit User'); } else { $title = pht('Create New User'); } $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormError($error_view) ->setForm($form); return array($form_box); } private function processRoleRequest(PhabricatorUser $user) { $request = $this->getRequest(); $admin = $request->getUser(); $is_self = ($user->getID() == $admin->getID()); $errors = array(); if ($request->isFormPost()) { $log_template = PhabricatorUserLog::newLog( $admin, $user, null); $logs = array(); if ($is_self) { $errors[] = pht("You can not edit your own role."); } else { $new_admin = (bool)$request->getBool('is_admin'); $old_admin = (bool)$user->getIsAdmin(); if ($new_admin != $old_admin) { id(new PhabricatorUserEditor()) ->setActor($admin) ->makeAdminUser($user, $new_admin); } $new_disabled = (bool)$request->getBool('is_disabled'); $old_disabled = (bool)$user->getIsDisabled(); if ($new_disabled != $old_disabled) { id(new PhabricatorUserEditor()) ->setActor($admin) ->disableUser($user, $new_disabled); } } if (!$errors) { return id(new AphrontRedirectResponse()) ->setURI($request->getRequestURI()->alter('saved', 'true')); } } $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle(pht('Form Errors')) ->setErrors($errors); } $form = id(new AphrontFormView()) ->setUser($admin) ->setAction($request->getRequestURI()->alter('saved', null)); if ($is_self) { $inst = pht('NOTE: You can not edit your own role.'); $form->appendChild( phutil_tag('p', array('class' => 'aphront-form-instructions'), $inst)); } $form ->appendChild($this->getRoleInstructions()) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'is_admin', 1, pht('Administrator'), $user->getIsAdmin()) ->setDisabled($is_self)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'is_disabled', 1, pht('Disabled'), $user->getIsDisabled()) ->setDisabled($is_self)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'is_agent', 1, pht('System Agent (Bot/Script User)'), $user->getIsSystemAgent()) ->setDisabled(true)); if (!$is_self) { $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Edit Role'))); } $title = pht('Edit Role'); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormError($error_view) ->setForm($form); return array($form_box); } private function processCertificateRequest($user) { $request = $this->getRequest(); $admin = $request->getUser(); $inst = pht('You can use this certificate '. 'to write scripts or bots which interface with Phabricator over '. 'Conduit.'); $form = new AphrontFormView(); $form ->setUser($admin) ->setAction($request->getRequestURI()) ->appendChild( phutil_tag('p', array('class' => 'aphront-form-instructions'), $inst)); if ($user->getIsSystemAgent()) { $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Username')) ->setValue($user->getUsername())) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel(pht('Certificate')) ->setValue($user->getConduitCertificate())); } else { $form->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Certificate')) ->setValue( pht('You may only view the certificates of System Agents.'))); } $title = pht('Conduit Certificate'); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setForm($form); return array($form_box); } private function processRenameRequest(PhabricatorUser $user) { $request = $this->getRequest(); $admin = $request->getUser(); $e_username = true; $username = $user->getUsername(); $errors = array(); if ($request->isFormPost()) { $username = $request->getStr('username'); if (!strlen($username)) { $e_username = pht('Required'); $errors[] = pht('New username is required.'); } else if ($username == $user->getUsername()) { $e_username = pht('Invalid'); $errors[] = pht('New username must be different from old username.'); } else if (!PhabricatorUser::validateUsername($username)) { $e_username = pht('Invalid'); $errors[] = PhabricatorUser::describeValidUsername(); } if (!$errors) { try { id(new PhabricatorUserEditor()) ->setActor($admin) ->changeUsername($user, $username); return id(new AphrontRedirectResponse()) ->setURI($request->getRequestURI()->alter('saved', true)); } catch (AphrontQueryDuplicateKeyException $ex) { $e_username = pht('Not Unique'); $errors[] = pht('Another user already has that username.'); } } } if ($errors) { $errors = id(new AphrontErrorView()) ->setTitle(pht('Form Errors')) ->setErrors($errors); } else { $errors = null; } $inst1 = pht('Be careful when renaming users!'); $inst2 = pht('The old username will no longer be tied to the user, so '. 'anything which uses it (like old commit messages) will no longer '. 'associate correctly. And if you give a user a username which some '. 'other user used to have, username lookups will begin returning '. 'the wrong user.'); $inst3 = pht('It is generally safe to rename newly created users (and '. 'test users and so on), but less safe to rename established users '. 'and unsafe to reissue a username.'); $inst4 = pht('Users who rely on password auth will need to reset their '. 'passwordafter their username is changed (their username is part '. 'of the salt in the password hash). They will receive an email '. 'with instructions on how to do this.'); $form = new AphrontFormView(); $form ->setUser($admin) ->setAction($request->getRequestURI()) ->appendChild(hsprintf( '

'. '%s '. '%s'. '

'. '

'. '%s'. '

'. '

'. '%s'. '

', $inst1, $inst2, $inst3, $inst4)) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Old Username')) ->setValue($user->getUsername())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('New Username')) ->setValue($username) ->setName('username') ->setError($e_username)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Change Username'))); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Change Username')) ->setFormError($errors) ->setForm($form); return array($form_box); } private function processDeleteRequest(PhabricatorUser $user) { $request = $this->getRequest(); $admin = $request->getUser(); $far1 = pht('As you stare into the gaping maw of the abyss, something '. 'hold you back.'); $far2 = pht('You can not delete your own account.'); if ($user->getPHID() == $admin->getPHID()) { $error = new AphrontErrorView(); $error->setTitle(pht('You Shall Journey No Farther')); $error->appendChild(hsprintf( '

%s

%s

', $far1, $far2)); return $error; } $e_username = true; $username = null; $errors = array(); if ($request->isFormPost()) { $username = $request->getStr('username'); if (!strlen($username)) { $e_username = pht('Required'); $errors[] = pht('You must type the username to confirm deletion.'); } else if ($username != $user->getUsername()) { $e_username = pht('Invalid'); $errors[] = pht('You must type the username correctly.'); } if (!$errors) { id(new PhabricatorUserEditor()) ->setActor($admin) ->deleteUser($user); return id(new AphrontRedirectResponse())->setURI('/people/'); } } if ($errors) { $errors = id(new AphrontErrorView()) ->setTitle(pht('Form Errors')) ->setErrors($errors); } else { $errors = null; } $str1 = pht('Be careful when deleting users!'); $str2 = pht('If this user interacted with anything, it is generally '. 'better to disable them, not delete them. If you delete them, it will '. 'no longer be possible to search for their objects, for example, '. 'and you will lose other information about their history. Disabling '. 'them instead will prevent them from logging in but not destroy '. 'any of their data.'); $str3 = pht('It is generally safe to delete newly created users (and '. 'test users and so on), but less safe to delete established users. '. 'If possible, disable them instead.'); $form = new AphrontFormView(); $form ->setUser($admin) ->setAction($request->getRequestURI()) ->appendChild(hsprintf( '

'. '%s %s'. '

'. '

'. '%s'. '

', $str1, $str2, $str3)) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Username')) ->setValue($user->getUsername())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Confirm')) ->setValue($username) ->setName('username') ->setCaption(pht("Type the username again to confirm deletion.")) ->setError($e_username)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Delete User'))); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Delete User')) ->setFormError($errors) ->setForm($form); return array($form_box); } private function getRoleInstructions() { $roles_link = phutil_tag( 'a', array( 'href' => PhabricatorEnv::getDoclink( 'article/User_Guide_Account_Roles.html'), 'target' => '_blank', ), pht('User Guide: Account Roles')); return phutil_tag( 'p', array('class' => 'aphront-form-instructions'), pht('For a detailed explanation of account roles, see %s.', $roles_link)); } private function processSetAccountPicture(PhabricatorUser $user) { $request = $this->getRequest(); $admin = $request->getUser(); $profile = $user->loadUserProfile(); if (!$profile->getID()) { $profile->setTitle(''); $profile->setBlurb(''); } $supported_formats = PhabricatorFile::getTransformableImageFormats(); $e_image = null; $errors = array(); if ($request->isFormPost()) { $default_image = $request->getExists('default_image'); if ($default_image) { $profile->setProfileImagePHID(null); $user->setProfileImagePHID(null); } else if ($request->getFileExists('image')) { $file = null; $file = PhabricatorFile::newFromPHPUpload( $_FILES['image'], array( 'authorPHID' => $admin->getPHID(), )); $okay = $file->isTransformableImage(); if ($okay) { $xformer = new PhabricatorImageTransformer(); // Generate the large picture for the profile page. $large_xformed = $xformer->executeProfileTransform( $file, $width = 280, $min_height = 140, $max_height = 420); $profile->setProfileImagePHID($large_xformed->getPHID()); // Generate the small picture for comments, etc. $small_xformed = $xformer->executeProfileTransform( $file, $width = 50, $min_height = 50, $max_height = 50); $user->setProfileImagePHID($small_xformed->getPHID()); } else { $e_image = pht('Not Supported'); $errors[] = pht('This server only supports these image formats:'). ' ' .implode(', ', $supported_formats); } } if (!$errors) { $user->save(); $profile->save(); $response = id(new AphrontRedirectResponse()) ->setURI('/people/edit/'.$user->getID().'/picture/'); return $response; } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle(pht('Form Errors')); $error_view->setErrors($errors); } else { if ($request->getStr('saved')) { $error_view = new AphrontErrorView(); $error_view->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $error_view->setTitle(pht('Changes Saved')); $error_view->appendChild( phutil_tag('p', array(), pht('Your changes have been saved.'))); $error_view = $error_view->render(); } } $img_src = $user->loadProfileImageURI(); $form = new AphrontFormView(); $form ->setUser($admin) ->setAction($request->getRequestURI()) ->setEncType('multipart/form-data') ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Profile Image')) ->setValue( phutil_tag( 'img', array( 'src' => $img_src, )))) ->appendChild( id(new AphrontFormImageControl()) ->setLabel(pht('Change Image')) ->setName('image') ->setError($e_image) ->setCaption( pht('Supported formats: %s', implode(', ', $supported_formats)))); $form->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save')) ->addCancelButton('/people/edit/'.$user->getID().'/')); $panel = new AphrontPanelView(); $panel->setHeader(pht('Set Profile Picture')); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->setNoBackground(); $panel->appendChild($form); return array($error_view, $panel); } } diff --git a/src/applications/people/controller/PhabricatorPeopleListController.php b/src/applications/people/controller/PhabricatorPeopleListController.php index 878ca311c..b084de30b 100644 --- a/src/applications/people/controller/PhabricatorPeopleListController.php +++ b/src/applications/people/controller/PhabricatorPeopleListController.php @@ -1,85 +1,89 @@ key = idx($data, 'key'); } public function processRequest() { $request = $this->getRequest(); $controller = id(new PhabricatorApplicationSearchController($request)) ->setQueryKey($this->key) ->setSearchEngine(new PhabricatorPeopleSearchEngine()) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); } public function renderResultsList( array $users, PhabricatorSavedQuery $query) { assert_instances_of($users, 'PhabricatorUser'); $request = $this->getRequest(); $viewer = $request->getUser(); $list = new PHUIObjectItemListView(); foreach ($users as $user) { $primary_email = $user->loadPrimaryEmail(); if ($primary_email && $primary_email->getIsVerified()) { $email = pht('Verified'); } else { $email = pht('Unverified'); } $user_handle = new PhabricatorObjectHandle(); $user_handle->setImageURI($user->loadProfileImageURI()); $item = new PHUIObjectItemView(); $item->setHeader($user->getFullName()) ->setHref('/p/'.$user->getUsername().'/') ->addAttribute(hsprintf('%s %s', phabricator_date($user->getDateCreated(), $viewer), phabricator_time($user->getDateCreated(), $viewer))) ->addAttribute($email); if ($user->getIsDisabled()) { $item->addIcon('disable', pht('Disabled')); } + if (!$user->getIsApproved()) { + $item->addIcon('raise-priority', pht('Not Approved')); + } + if ($user->getIsAdmin()) { $item->addIcon('highlight', pht('Admin')); } if ($user->getIsSystemAgent()) { $item->addIcon('computer', pht('System Agent')); } if ($viewer->getIsAdmin()) { $uid = $user->getID(); $item->addAction( id(new PHUIListItemView()) ->setIcon('edit') ->setHref($this->getApplicationURI('edit/'.$uid.'/'))); } $list->addItem($item); } return $list; } } diff --git a/src/applications/people/customfield/PhabricatorUserRolesField.php b/src/applications/people/customfield/PhabricatorUserRolesField.php index dca0b3aaf..add52385a 100644 --- a/src/applications/people/customfield/PhabricatorUserRolesField.php +++ b/src/applications/people/customfield/PhabricatorUserRolesField.php @@ -1,45 +1,48 @@ getObject(); $roles = array(); if ($user->getIsAdmin()) { $roles[] = pht('Administrator'); } if ($user->getIsDisabled()) { $roles[] = pht('Disabled'); } + if (!$user->getIsApproved()) { + $roles[] = pht('Not Approved'); + } if ($user->getIsSystemAgent()) { $roles[] = pht('Bot'); } if ($roles) { return implode(', ', $roles); } return null; } } diff --git a/src/applications/people/editor/PhabricatorUserEditor.php b/src/applications/people/editor/PhabricatorUserEditor.php index f8fb6811a..e0835cb78 100644 --- a/src/applications/people/editor/PhabricatorUserEditor.php +++ b/src/applications/people/editor/PhabricatorUserEditor.php @@ -1,534 +1,540 @@ getID()) { throw new Exception("User has already been created!"); } if ($email->getID()) { throw new Exception("Email has already been created!"); } if (!PhabricatorUser::validateUsername($user->getUsername())) { $valid = PhabricatorUser::describeValidUsername(); throw new Exception("Username is invalid! {$valid}"); } // Always set a new user's email address to primary. $email->setIsPrimary(1); + // If the primary address is already verified, also set the verified flag + // on the user themselves. + if ($email->getIsVerified()) { + $user->setIsEmailVerified(1); + } + $this->willAddEmail($email); $user->openTransaction(); try { $user->save(); $email->setUserPHID($user->getPHID()); $email->save(); } catch (AphrontQueryDuplicateKeyException $ex) { // We might have written the user but failed to write the email; if // so, erase the IDs we attached. $user->setID(null); $user->setPHID(null); $user->killTransaction(); throw $ex; } $log = PhabricatorUserLog::newLog( $this->requireActor(), $user, PhabricatorUserLog::ACTION_CREATE); $log->setNewValue($email->getAddress()); $log->save(); $user->saveTransaction(); return $this; } /** * @task edit */ public function updateUser( PhabricatorUser $user, PhabricatorUserEmail $email = null) { if (!$user->getID()) { throw new Exception("User has not been created yet!"); } $user->openTransaction(); $user->save(); if ($email) { $email->save(); } $log = PhabricatorUserLog::newLog( $this->requireActor(), $user, PhabricatorUserLog::ACTION_EDIT); $log->save(); $user->saveTransaction(); return $this; } /** * @task edit */ public function changePassword( PhabricatorUser $user, PhutilOpaqueEnvelope $envelope) { if (!$user->getID()) { throw new Exception("User has not been created yet!"); } $user->openTransaction(); $user->reload(); $user->setPassword($envelope); $user->save(); $log = PhabricatorUserLog::newLog( $this->requireActor(), $user, PhabricatorUserLog::ACTION_CHANGE_PASSWORD); $log->save(); $user->saveTransaction(); } /** * @task edit */ public function changeUsername(PhabricatorUser $user, $username) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception("User has not been created yet!"); } if (!PhabricatorUser::validateUsername($username)) { $valid = PhabricatorUser::describeValidUsername(); throw new Exception("Username is invalid! {$valid}"); } $old_username = $user->getUsername(); $user->openTransaction(); $user->reload(); $user->setUsername($username); try { $user->save(); } catch (AphrontQueryDuplicateKeyException $ex) { $user->setUsername($old_username); $user->killTransaction(); throw $ex; } $log = PhabricatorUserLog::newLog( $actor, $user, PhabricatorUserLog::ACTION_CHANGE_USERNAME); $log->setOldValue($old_username); $log->setNewValue($username); $log->save(); $user->saveTransaction(); $user->sendUsernameChangeEmail($actor, $old_username); } /* -( Editing Roles )------------------------------------------------------ */ /** * @task role */ public function makeAdminUser(PhabricatorUser $user, $admin) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception("User has not been created yet!"); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); if ($user->getIsAdmin() == $admin) { $user->endWriteLocking(); $user->killTransaction(); return $this; } $log = PhabricatorUserLog::newLog( $actor, $user, PhabricatorUserLog::ACTION_ADMIN); $log->setOldValue($user->getIsAdmin()); $log->setNewValue($admin); $user->setIsAdmin($admin); $user->save(); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); return $this; } /** * @task role */ public function makeSystemAgentUser(PhabricatorUser $user, $system_agent) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception("User has not been created yet!"); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); if ($user->getIsSystemAgent() == $system_agent) { $user->endWriteLocking(); $user->killTransaction(); return $this; } $log = PhabricatorUserLog::newLog( $actor, $user, PhabricatorUserLog::ACTION_SYSTEM_AGENT); $log->setOldValue($user->getIsSystemAgent()); $log->setNewValue($system_agent); $user->setIsSystemAgent($system_agent); $user->save(); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); return $this; } /** * @task role */ public function disableUser(PhabricatorUser $user, $disable) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception("User has not been created yet!"); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); if ($user->getIsDisabled() == $disable) { $user->endWriteLocking(); $user->killTransaction(); return $this; } $log = PhabricatorUserLog::newLog( $actor, $user, PhabricatorUserLog::ACTION_DISABLE); $log->setOldValue($user->getIsDisabled()); $log->setNewValue($disable); $user->setIsDisabled($disable); $user->save(); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); return $this; } /** * @task role */ public function deleteUser(PhabricatorUser $user, $disable) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception("User has not been created yet!"); } if ($actor->getPHID() == $user->getPHID()) { throw new Exception("You can not delete yourself!"); } $user->openTransaction(); $externals = id(new PhabricatorExternalAccount())->loadAllWhere( 'userPHID = %s', $user->getPHID()); foreach ($externals as $external) { $external->delete(); } $prefs = id(new PhabricatorUserPreferences())->loadAllWhere( 'userPHID = %s', $user->getPHID()); foreach ($prefs as $pref) { $pref->delete(); } $profiles = id(new PhabricatorUserProfile())->loadAllWhere( 'userPHID = %s', $user->getPHID()); foreach ($profiles as $profile) { $profile->delete(); } $keys = id(new PhabricatorUserSSHKey())->loadAllWhere( 'userPHID = %s', $user->getPHID()); foreach ($keys as $key) { $key->delete(); } $emails = id(new PhabricatorUserEmail())->loadAllWhere( 'userPHID = %s', $user->getPHID()); foreach ($emails as $email) { $email->delete(); } $log = PhabricatorUserLog::newLog( $actor, $user, PhabricatorUserLog::ACTION_DELETE); $log->save(); $user->delete(); $user->saveTransaction(); return $this; } /* -( Adding, Removing and Changing Email )-------------------------------- */ /** * @task email */ public function addEmail( PhabricatorUser $user, PhabricatorUserEmail $email) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception("User has not been created yet!"); } if ($email->getID()) { throw new Exception("Email has already been created!"); } // Use changePrimaryEmail() to change primary email. $email->setIsPrimary(0); $email->setUserPHID($user->getPHID()); $this->willAddEmail($email); $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); try { $email->save(); } catch (AphrontQueryDuplicateKeyException $ex) { $user->endWriteLocking(); $user->killTransaction(); throw $ex; } $log = PhabricatorUserLog::newLog( $actor, $user, PhabricatorUserLog::ACTION_EMAIL_ADD); $log->setNewValue($email->getAddress()); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); return $this; } /** * @task email */ public function removeEmail( PhabricatorUser $user, PhabricatorUserEmail $email) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception("User has not been created yet!"); } if (!$email->getID()) { throw new Exception("Email has not been created yet!"); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); $email->reload(); if ($email->getIsPrimary()) { throw new Exception("Can't remove primary email!"); } if ($email->getUserPHID() != $user->getPHID()) { throw new Exception("Email not owned by user!"); } $email->delete(); $log = PhabricatorUserLog::newLog( $actor, $user, PhabricatorUserLog::ACTION_EMAIL_REMOVE); $log->setOldValue($email->getAddress()); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); return $this; } /** * @task email */ public function changePrimaryEmail( PhabricatorUser $user, PhabricatorUserEmail $email) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception("User has not been created yet!"); } if (!$email->getID()) { throw new Exception("Email has not been created yet!"); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); $email->reload(); if ($email->getUserPHID() != $user->getPHID()) { throw new Exception("User does not own email!"); } if ($email->getIsPrimary()) { throw new Exception("Email is already primary!"); } if (!$email->getIsVerified()) { throw new Exception("Email is not verified!"); } $old_primary = $user->loadPrimaryEmail(); if ($old_primary) { $old_primary->setIsPrimary(0); $old_primary->save(); } $email->setIsPrimary(1); $email->save(); $log = PhabricatorUserLog::newLog( $actor, $user, PhabricatorUserLog::ACTION_EMAIL_PRIMARY); $log->setOldValue($old_primary ? $old_primary->getAddress() : null); $log->setNewValue($email->getAddress()); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); if ($old_primary) { $old_primary->sendOldPrimaryEmail($user, $email); } $email->sendNewPrimaryEmail($user); return $this; } /* -( Internals )---------------------------------------------------------- */ /** * @task internal */ private function willAddEmail(PhabricatorUserEmail $email) { // Hard check before write to prevent creation of disallowed email // addresses. Normally, the application does checks and raises more // user friendly errors for us, but we omit the courtesy checks on some // pathways like administrative scripts for simplicity. if (!PhabricatorUserEmail::isAllowedAddress($email->getAddress())) { throw new Exception(PhabricatorUserEmail::describeAllowedAddresses()); } } } diff --git a/src/applications/people/event/PhabricatorPeopleHovercardEventListener.php b/src/applications/people/event/PhabricatorPeopleHovercardEventListener.php index 31ebf778c..380e446b6 100644 --- a/src/applications/people/event/PhabricatorPeopleHovercardEventListener.php +++ b/src/applications/people/event/PhabricatorPeopleHovercardEventListener.php @@ -1,65 +1,67 @@ listen(PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD); } public function handleEvent(PhutilEvent $event) { switch ($event->getType()) { case PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD: $this->handleHovercardEvent($event); break; } } private function handleHovercardEvent($event) { $viewer = $event->getUser(); $hovercard = $event->getValue('hovercard'); $object_handle = $event->getValue('handle'); $phid = $object_handle->getPHID(); $user = $event->getValue('object'); if (!($user instanceof PhabricatorUser)) { return; } $profile = $user->loadUserProfile(); $hovercard->setTitle($user->getUsername()); $hovercard->setDetail(pht('%s - %s.', $user->getRealname(), nonempty($profile->getTitle(), pht('No title was found befitting of this rare specimen')))); if ($user->getIsDisabled()) { $hovercard->addField(pht('Account'), pht('Disabled')); + } else if (!$user->isUserActivated()) { + $hovercard->addField(pht('Account'), pht('Not Activated')); } else { $statuses = id(new PhabricatorUserStatus())->loadCurrentStatuses( array($user->getPHID())); if ($statuses) { $current_status = reset($statuses); $dateto = phabricator_datetime($current_status->getDateTo(), $user); $hovercard->addField(pht('Status'), $current_status->getDescription()); $hovercard->addField(pht('Until'), $dateto); } else { $hovercard->addField(pht('Status'), pht('Available')); } } $hovercard->addField(pht('User since'), phabricator_date($user->getDateCreated(), $user)); if ($profile->getBlurb()) { $hovercard->addField(pht('Blurb'), phutil_utf8_shorten($profile->getBlurb(), 120)); } $event->setValue('hovercard', $hovercard); } } diff --git a/src/applications/people/phid/PhabricatorPeoplePHIDTypeUser.php b/src/applications/people/phid/PhabricatorPeoplePHIDTypeUser.php index a0bce6ea4..a6df8adb1 100644 --- a/src/applications/people/phid/PhabricatorPeoplePHIDTypeUser.php +++ b/src/applications/people/phid/PhabricatorPeoplePHIDTypeUser.php @@ -1,51 +1,51 @@ withPHIDs($phids) ->needProfileImage(true) ->needStatus(true); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $user = $objects[$phid]; $handle->setName($user->getUsername()); $handle->setURI('/p/'.$user->getUsername().'/'); $handle->setFullName( $user->getUsername().' ('.$user->getRealName().')'); $handle->setImageURI($user->loadProfileImageURI()); - $handle->setDisabled($user->getIsDisabled()); + $handle->setDisabled(!$user->isUserActivated()); if ($user->hasStatus()) { $status = $user->getStatus(); $handle->setStatus($status->getTextStatus()); $handle->setTitle($status->getTerseSummary($query->getViewer())); } } } } diff --git a/src/applications/people/remarkup/PhabricatorRemarkupRuleMention.php b/src/applications/people/remarkup/PhabricatorRemarkupRuleMention.php index 31d2f4a2c..1ca9e0a23 100644 --- a/src/applications/people/remarkup/PhabricatorRemarkupRuleMention.php +++ b/src/applications/people/remarkup/PhabricatorRemarkupRuleMention.php @@ -1,147 +1,147 @@ getEngine(); if ($engine->isTextMode()) { return $engine->storeText($matches[0]); } $token = $engine->storeText(''); // Store the original text exactly so we can preserve casing if it doesn't // resolve into a username. $original_key = self::KEY_RULE_MENTION_ORIGINAL; $original = $engine->getTextMetadata($original_key, array()); $original[$token] = $matches[1]; $engine->setTextMetadata($original_key, $original); $metadata_key = self::KEY_RULE_MENTION; $metadata = $engine->getTextMetadata($metadata_key, array()); $username = strtolower($matches[1]); if (empty($metadata[$username])) { $metadata[$username] = array(); } $metadata[$username][] = $token; $engine->setTextMetadata($metadata_key, $metadata); return $token; } public function didMarkupText() { $engine = $this->getEngine(); $metadata_key = self::KEY_RULE_MENTION; $metadata = $engine->getTextMetadata($metadata_key, array()); if (empty($metadata)) { // No mentions, or we already processed them. return; } $original_key = self::KEY_RULE_MENTION_ORIGINAL; $original = $engine->getTextMetadata($original_key, array()); $usernames = array_keys($metadata); $users = id(new PhabricatorPeopleQuery()) ->setViewer($this->getEngine()->getConfig('viewer')) ->withUsernames($usernames) ->execute(); if ($users) { $user_statuses = id(new PhabricatorUserStatus()) ->loadCurrentStatuses(mpull($users, 'getPHID')); $user_statuses = mpull($user_statuses, null, 'getUserPHID'); } else { $user_statuses = array(); } $actual_users = array(); $mentioned_key = self::KEY_MENTIONED; $mentioned = $engine->getTextMetadata($mentioned_key, array()); foreach ($users as $row) { $actual_users[strtolower($row->getUserName())] = $row; $mentioned[$row->getPHID()] = $row->getPHID(); } $engine->setTextMetadata($mentioned_key, $mentioned); foreach ($metadata as $username => $tokens) { $exists = isset($actual_users[$username]); if ($exists) { $user = $actual_users[$username]; Javelin::initBehavior('phabricator-hovercards'); $tag = id(new PhabricatorTagView()) ->setType(PhabricatorTagView::TYPE_PERSON) ->setPHID($user->getPHID()) ->setName('@'.$user->getUserName()) ->setHref('/p/'.$user->getUserName().'/'); - if ($user->getIsDisabled()) { + if (!$user->isUserActivated()) { $tag->setDotColor(PhabricatorTagView::COLOR_GREY); } else { $status = idx($user_statuses, $user->getPHID()); if ($status) { $status = $status->getStatus(); if ($status == PhabricatorUserStatus::STATUS_AWAY) { $tag->setDotColor(PhabricatorTagView::COLOR_RED); } else if ($status == PhabricatorUserStatus::STATUS_AWAY) { $tag->setDotColor(PhabricatorTagView::COLOR_ORANGE); } } } foreach ($tokens as $token) { $engine->overwriteStoredText($token, $tag); } } else { // NOTE: The structure here is different from the 'exists' branch, // because we want to preserve the original text capitalization and it // may differ for each token. foreach ($tokens as $token) { $tag = phutil_tag( 'span', array( 'class' => 'phabricator-remarkup-mention-unknown', ), '@'.idx($original, $token, $username)); $engine->overwriteStoredText($token, $tag); } } } // Don't re-process these mentions. $engine->setTextMetadata($metadata_key, array()); } } diff --git a/src/applications/people/search/PhabricatorUserSearchIndexer.php b/src/applications/people/search/PhabricatorUserSearchIndexer.php index 8e7cd6984..9a8c06961 100644 --- a/src/applications/people/search/PhabricatorUserSearchIndexer.php +++ b/src/applications/people/search/PhabricatorUserSearchIndexer.php @@ -1,33 +1,33 @@ loadDocumentByPHID($phid); $doc = new PhabricatorSearchAbstractDocument(); $doc->setPHID($user->getPHID()); $doc->setDocumentType(PhabricatorPeoplePHIDTypeUser::TYPECONST); $doc->setDocumentTitle($user->getUserName().' ('.$user->getRealName().')'); $doc->setDocumentCreated($user->getDateCreated()); $doc->setDocumentModified($user->getDateModified()); // TODO: Index the blurbs from their profile or something? Probably not // actually useful... - if (!$user->getIsDisabled()) { + if ($user->isUserActivated()) { $doc->addRelationship( PhabricatorSearchRelationship::RELATIONSHIP_OPEN, $user->getPHID(), PhabricatorPeoplePHIDTypeUser::TYPECONST, time()); } return $doc; } } diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index a0e055263..503407271 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -1,868 +1,900 @@ timezoneIdentifier, date_default_timezone_get()); // Make sure these return booleans. case 'isAdmin': return (bool)$this->isAdmin; case 'isDisabled': return (bool)$this->isDisabled; case 'isSystemAgent': return (bool)$this->isSystemAgent; + case 'isEmailVerified': + return (bool)$this->isEmailVerified; + case 'isApproved': + return (bool)$this->isApproved; default: return parent::readField($field); } } + + /** + * Is this a live account which has passed required approvals? Returns true + * if this is an enabled, verified (if required), approved (if required) + * account, and false otherwise. + * + * @return bool True if this is a standard, usable account. + */ + public function isUserActivated() { + if ($this->getIsDisabled()) { + return false; + } + + if (!$this->getIsApproved()) { + return false; + } + + if (PhabricatorUserEmail::isEmailVerificationRequired()) { + if (!$this->getIsEmailVerified()) { + return false; + } + } + + return true; + } + public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_PARTIAL_OBJECTS => true, ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPeoplePHIDTypeUser::TYPECONST); } public function setPassword(PhutilOpaqueEnvelope $envelope) { if (!$this->getPHID()) { throw new Exception( "You can not set a password for an unsaved user because their PHID ". "is a salt component in the password hash."); } if (!strlen($envelope->openEnvelope())) { $this->setPasswordHash(''); } else { $this->setPasswordSalt(md5(mt_rand())); $hash = $this->hashPassword($envelope); $this->setPasswordHash($hash); } return $this; } // To satisfy PhutilPerson. public function getSex() { return $this->sex; } public function getTranslation() { try { if ($this->translation && class_exists($this->translation) && is_subclass_of($this->translation, 'PhabricatorTranslation')) { return $this->translation; } } catch (PhutilMissingSymbolException $ex) { return null; } return null; } public function isLoggedIn() { return !($this->getPHID() === null); } public function save() { if (!$this->getConduitCertificate()) { $this->setConduitCertificate($this->generateConduitCertificate()); } $result = parent::save(); if ($this->profile) { $this->profile->save(); } $this->updateNameTokens(); id(new PhabricatorSearchIndexer()) ->indexDocumentByPHID($this->getPHID()); return $result; } private function generateConduitCertificate() { return Filesystem::readRandomCharacters(255); } public function comparePassword(PhutilOpaqueEnvelope $envelope) { if (!strlen($envelope->openEnvelope())) { return false; } if (!strlen($this->getPasswordHash())) { return false; } $password_hash = $this->hashPassword($envelope); return ($password_hash === $this->getPasswordHash()); } private function hashPassword(PhutilOpaqueEnvelope $envelope) { $hash = $this->getUsername(). $envelope->openEnvelope(). $this->getPHID(). $this->getPasswordSalt(); for ($ii = 0; $ii < 1000; $ii++) { $hash = md5($hash); } return $hash; } const CSRF_CYCLE_FREQUENCY = 3600; const CSRF_SALT_LENGTH = 8; const CSRF_TOKEN_LENGTH = 16; const CSRF_BREACH_PREFIX = 'B@'; const EMAIL_CYCLE_FREQUENCY = 86400; const EMAIL_TOKEN_LENGTH = 24; private function getRawCSRFToken($offset = 0) { return $this->generateToken( time() + (self::CSRF_CYCLE_FREQUENCY * $offset), self::CSRF_CYCLE_FREQUENCY, PhabricatorEnv::getEnvConfig('phabricator.csrf-key'), self::CSRF_TOKEN_LENGTH); } /** * @phutil-external-symbol class PhabricatorStartup */ public function getCSRFToken() { $salt = PhabricatorStartup::getGlobal('csrf.salt'); if (!$salt) { $salt = Filesystem::readRandomCharacters(self::CSRF_SALT_LENGTH); PhabricatorStartup::setGlobal('csrf.salt', $salt); } // Generate a token hash to mitigate BREACH attacks against SSL. See // discussion in T3684. $token = $this->getRawCSRFToken(); $hash = PhabricatorHash::digest($token, $salt); return 'B@'.$salt.substr($hash, 0, self::CSRF_TOKEN_LENGTH); } public function validateCSRFToken($token) { if (!$this->getPHID()) { return true; } $salt = null; $version = 'plain'; // This is a BREACH-mitigating token. See T3684. $breach_prefix = self::CSRF_BREACH_PREFIX; $breach_prelen = strlen($breach_prefix); if (!strncmp($token, $breach_prefix, $breach_prelen)) { $version = 'breach'; $salt = substr($token, $breach_prelen, self::CSRF_SALT_LENGTH); $token = substr($token, $breach_prelen + self::CSRF_SALT_LENGTH); } // When the user posts a form, we check that it contains a valid CSRF token. // Tokens cycle each hour (every CSRF_CYLCE_FREQUENCY seconds) and we accept // either the current token, the next token (users can submit a "future" // token if you have two web frontends that have some clock skew) or any of // the last 6 tokens. This means that pages are valid for up to 7 hours. // There is also some Javascript which periodically refreshes the CSRF // tokens on each page, so theoretically pages should be valid indefinitely. // However, this code may fail to run (if the user loses their internet // connection, or there's a JS problem, or they don't have JS enabled). // Choosing the size of the window in which we accept old CSRF tokens is // an issue of balancing concerns between security and usability. We could // choose a very narrow (e.g., 1-hour) window to reduce vulnerability to // attacks using captured CSRF tokens, but it's also more likely that real // users will be affected by this, e.g. if they close their laptop for an // hour, open it back up, and try to submit a form before the CSRF refresh // can kick in. Since the user experience of submitting a form with expired // CSRF is often quite bad (you basically lose data, or it's a big pain to // recover at least) and I believe we gain little additional protection // by keeping the window very short (the overwhelming value here is in // preventing blind attacks, and most attacks which can capture CSRF tokens // can also just capture authentication information [sniffing networks] // or act as the user [xss]) the 7 hour default seems like a reasonable // balance. Other major platforms have much longer CSRF token lifetimes, // like Rails (session duration) and Django (forever), which suggests this // is a reasonable analysis. $csrf_window = 6; for ($ii = -$csrf_window; $ii <= 1; $ii++) { $valid = $this->getRawCSRFToken($ii); switch ($version) { // TODO: We can remove this after the BREACH version has been in the // wild for a while. case 'plain': if ($token == $valid) { return true; } break; case 'breach': $digest = PhabricatorHash::digest($valid, $salt); if (substr($digest, 0, self::CSRF_TOKEN_LENGTH) == $token) { return true; } break; default: throw new Exception("Unknown CSRF token format!"); } } return false; } private function generateToken($epoch, $frequency, $key, $len) { $time_block = floor($epoch / $frequency); $vec = $this->getPHID().$this->getPasswordHash().$key.$time_block; return substr(PhabricatorHash::digest($vec), 0, $len); } /** * Issue a new session key to this user. Phabricator supports different * types of sessions (like "web" and "conduit") and each session type may * have multiple concurrent sessions (this allows a user to be logged in on * multiple browsers at the same time, for instance). * * Note that this method is transport-agnostic and does not set cookies or * issue other types of tokens, it ONLY generates a new session key. * * You can configure the maximum number of concurrent sessions for various * session types in the Phabricator configuration. * * @param string Session type, like "web". * @return string Newly generated session key. */ public function establishSession($session_type) { $conn_w = $this->establishConnection('w'); if (strpos($session_type, '-') !== false) { throw new Exception("Session type must not contain hyphen ('-')!"); } // We allow multiple sessions of the same type, so when a caller requests // a new session of type "web", we give them the first available session in // "web-1", "web-2", ..., "web-N", up to some configurable limit. If none // of these sessions is available, we overwrite the oldest session and // reissue a new one in its place. $session_limit = 1; switch ($session_type) { case 'web': $session_limit = PhabricatorEnv::getEnvConfig('auth.sessions.web'); break; case 'conduit': $session_limit = PhabricatorEnv::getEnvConfig('auth.sessions.conduit'); break; default: throw new Exception("Unknown session type '{$session_type}'!"); } $session_limit = (int)$session_limit; if ($session_limit <= 0) { throw new Exception( "Session limit for '{$session_type}' must be at least 1!"); } // NOTE: Session establishment is sensitive to race conditions, as when // piping `arc` to `arc`: // // arc export ... | arc paste ... // // To avoid this, we overwrite an old session only if it hasn't been // re-established since we read it. // Consume entropy to generate a new session key, forestalling the eventual // heat death of the universe. $session_key = Filesystem::readRandomCharacters(40); // Load all the currently active sessions. $sessions = queryfx_all( $conn_w, 'SELECT type, sessionKey, sessionStart FROM %T WHERE userPHID = %s AND type LIKE %>', PhabricatorUser::SESSION_TABLE, $this->getPHID(), $session_type.'-'); $sessions = ipull($sessions, null, 'type'); $sessions = isort($sessions, 'sessionStart'); $existing_sessions = array_keys($sessions); // UNGUARDED WRITES: Logging-in users don't have CSRF stuff yet. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $retries = 0; while (true) { // Choose which 'type' we'll actually establish, i.e. what number we're // going to append to the basic session type. To do this, just check all // the numbers sequentially until we find an available session. $establish_type = null; for ($ii = 1; $ii <= $session_limit; $ii++) { $try_type = $session_type.'-'.$ii; if (!in_array($try_type, $existing_sessions)) { $establish_type = $try_type; $expect_key = PhabricatorHash::digest($session_key); $existing_sessions[] = $try_type; // Ensure the row exists so we can issue an update below. We don't // care if we race here or not. queryfx( $conn_w, 'INSERT IGNORE INTO %T (userPHID, type, sessionKey, sessionStart) VALUES (%s, %s, %s, 0)', self::SESSION_TABLE, $this->getPHID(), $establish_type, PhabricatorHash::digest($session_key)); break; } } // If we didn't find an available session, choose the oldest session and // overwrite it. if (!$establish_type) { $oldest = reset($sessions); $establish_type = $oldest['type']; $expect_key = $oldest['sessionKey']; } // This is so that we'll only overwrite the session if it hasn't been // refreshed since we read it. If it has, the session key will be // different and we know we're racing other processes. Whichever one // won gets the session, we go back and try again. queryfx( $conn_w, 'UPDATE %T SET sessionKey = %s, sessionStart = UNIX_TIMESTAMP() WHERE userPHID = %s AND type = %s AND sessionKey = %s', self::SESSION_TABLE, PhabricatorHash::digest($session_key), $this->getPHID(), $establish_type, $expect_key); if ($conn_w->getAffectedRows()) { // The update worked, so the session is valid. break; } else { // We know this just got grabbed, so don't try it again. unset($sessions[$establish_type]); } if (++$retries > $session_limit) { throw new Exception("Failed to establish a session!"); } } $log = PhabricatorUserLog::newLog( $this, $this, PhabricatorUserLog::ACTION_LOGIN); $log->setDetails( array( 'session_type' => $session_type, 'session_issued' => $establish_type, )); $log->setSession($session_key); $log->save(); return $session_key; } public function destroySession($session_key) { $conn_w = $this->establishConnection('w'); queryfx( $conn_w, 'DELETE FROM %T WHERE userPHID = %s AND sessionKey = %s', self::SESSION_TABLE, $this->getPHID(), PhabricatorHash::digest($session_key)); } private function generateEmailToken( PhabricatorUserEmail $email, $offset = 0) { $key = implode( '-', array( PhabricatorEnv::getEnvConfig('phabricator.csrf-key'), $this->getPHID(), $email->getVerificationCode(), )); return $this->generateToken( time() + ($offset * self::EMAIL_CYCLE_FREQUENCY), self::EMAIL_CYCLE_FREQUENCY, $key, self::EMAIL_TOKEN_LENGTH); } public function validateEmailToken( PhabricatorUserEmail $email, $token) { for ($ii = -1; $ii <= 1; $ii++) { $valid = $this->generateEmailToken($email, $ii); if ($token == $valid) { return true; } } return false; } public function getEmailLoginURI(PhabricatorUserEmail $email = null) { if (!$email) { $email = $this->loadPrimaryEmail(); if (!$email) { throw new Exception("User has no primary email!"); } } $token = $this->generateEmailToken($email); $uri = PhabricatorEnv::getProductionURI('/login/etoken/'.$token.'/'); $uri = new PhutilURI($uri); return $uri->alter('email', $email->getAddress()); } public function attachUserProfile(PhabricatorUserProfile $profile) { $this->profile = $profile; return $this; } public function loadUserProfile() { if ($this->profile) { return $this->profile; } $profile_dao = new PhabricatorUserProfile(); $this->profile = $profile_dao->loadOneWhere('userPHID = %s', $this->getPHID()); if (!$this->profile) { $profile_dao->setUserPHID($this->getPHID()); $this->profile = $profile_dao; } return $this->profile; } public function loadPrimaryEmailAddress() { $email = $this->loadPrimaryEmail(); if (!$email) { throw new Exception("User has no primary email address!"); } return $email->getAddress(); } public function loadPrimaryEmail() { return $this->loadOneRelative( new PhabricatorUserEmail(), 'userPHID', 'getPHID', '(isPrimary = 1)'); } public function loadPreferences() { if ($this->preferences) { return $this->preferences; } $preferences = null; if ($this->getPHID()) { $preferences = id(new PhabricatorUserPreferences())->loadOneWhere( 'userPHID = %s', $this->getPHID()); } if (!$preferences) { $preferences = new PhabricatorUserPreferences(); $preferences->setUserPHID($this->getPHID()); $default_dict = array( PhabricatorUserPreferences::PREFERENCE_TITLES => 'glyph', PhabricatorUserPreferences::PREFERENCE_EDITOR => '', PhabricatorUserPreferences::PREFERENCE_MONOSPACED => '', PhabricatorUserPreferences::PREFERENCE_DARK_CONSOLE => 0); $preferences->setPreferences($default_dict); } $this->preferences = $preferences; return $preferences; } public function loadEditorLink($path, $line, $callsign) { $editor = $this->loadPreferences()->getPreference( PhabricatorUserPreferences::PREFERENCE_EDITOR); if (is_array($path)) { $multiedit = $this->loadPreferences()->getPreference( PhabricatorUserPreferences::PREFERENCE_MULTIEDIT); switch ($multiedit) { case '': $path = implode(' ', $path); break; case 'disable': return null; } } if ($editor) { return strtr($editor, array( '%%' => '%', '%f' => phutil_escape_uri($path), '%l' => phutil_escape_uri($line), '%r' => phutil_escape_uri($callsign), )); } } private static function tokenizeName($name) { if (function_exists('mb_strtolower')) { $name = mb_strtolower($name, 'UTF-8'); } else { $name = strtolower($name); } $name = trim($name); if (!strlen($name)) { return array(); } return preg_split('/\s+/', $name); } /** * Populate the nametoken table, which used to fetch typeahead results. When * a user types "linc", we want to match "Abraham Lincoln" from on-demand * typeahead sources. To do this, we need a separate table of name fragments. */ public function updateNameTokens() { $tokens = array_merge( self::tokenizeName($this->getRealName()), self::tokenizeName($this->getUserName())); $tokens = array_unique($tokens); $table = self::NAMETOKEN_TABLE; $conn_w = $this->establishConnection('w'); $sql = array(); foreach ($tokens as $token) { $sql[] = qsprintf( $conn_w, '(%d, %s)', $this->getID(), $token); } queryfx( $conn_w, 'DELETE FROM %T WHERE userID = %d', $table, $this->getID()); if ($sql) { queryfx( $conn_w, 'INSERT INTO %T (userID, token) VALUES %Q', $table, implode(', ', $sql)); } } public function sendWelcomeEmail(PhabricatorUser $admin) { $admin_username = $admin->getUserName(); $admin_realname = $admin->getRealName(); $user_username = $this->getUserName(); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $base_uri = PhabricatorEnv::getProductionURI('/'); $uri = $this->getEmailLoginURI(); $body = <<addTos(array($this->getPHID())) ->setSubject('[Phabricator] Welcome to Phabricator') ->setBody($body) ->saveAndSend(); } public function sendUsernameChangeEmail( PhabricatorUser $admin, $old_username) { $admin_username = $admin->getUserName(); $admin_realname = $admin->getRealName(); $new_username = $this->getUserName(); $password_instructions = null; if (PhabricatorAuthProviderPassword::getPasswordProvider()) { $uri = $this->getEmailLoginURI(); $password_instructions = <<addTos(array($this->getPHID())) ->setSubject('[Phabricator] Username Changed') ->setBody($body) ->saveAndSend(); } public static function describeValidUsername() { return pht( 'Usernames must contain only numbers, letters, period, underscore and '. 'hyphen, and can not end with a period. They must have no more than %d '. 'characters.', new PhutilNumber(self::MAXIMUM_USERNAME_LENGTH)); } public static function validateUsername($username) { // NOTE: If you update this, make sure to update: // // - Remarkup rule for @mentions. // - Routing rule for "/p/username/". // - Unit tests, obviously. // - describeValidUsername() method, above. if (strlen($username) > self::MAXIMUM_USERNAME_LENGTH) { return false; } return (bool)preg_match('/^[a-zA-Z0-9._-]*[a-zA-Z0-9_-]$/', $username); } public static function getDefaultProfileImageURI() { return celerity_get_resource_uri('/rsrc/image/avatar.png'); } public function attachStatus(PhabricatorUserStatus $status) { $this->status = $status; return $this; } public function getStatus() { $this->assertAttached($this->status); return $this->status; } public function hasStatus() { return $this->status !== self::ATTACHABLE; } public function attachProfileImageURI($uri) { $this->profileImage = $uri; return $this; } public function loadProfileImageURI() { if ($this->profileImage) { return $this->profileImage; } $src_phid = $this->getProfileImagePHID(); if ($src_phid) { // TODO: (T603) Can we get rid of this entirely and move it to // PeopleQuery with attach/attachable? $file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $src_phid); if ($file) { $this->profileImage = $file->getBestURI(); } } if (!$this->profileImage) { $this->profileImage = self::getDefaultProfileImageURI(); } return $this->profileImage; } public function getFullName() { return $this->getUsername().' ('.$this->getRealName().')'; } public function __toString() { return $this->getUsername(); } public static function loadOneWithEmailAddress($address) { $email = id(new PhabricatorUserEmail())->loadOneWhere( 'address = %s', $address); if (!$email) { return null; } return id(new PhabricatorUser())->loadOneWhere( 'phid = %s', $email->getUserPHID()); } /* -( Omnipotence )-------------------------------------------------------- */ /** * Returns true if this user is omnipotent. Omnipotent users bypass all policy * checks. * * @return bool True if the user bypasses policy checks. */ public function isOmnipotent() { return $this->omnipotent; } /** * Get an omnipotent user object for use in contexts where there is no acting * user, notably daemons. * * @return PhabricatorUser An omnipotent user. */ public static function getOmnipotentUser() { static $user = null; if (!$user) { $user = new PhabricatorUser(); $user->omnipotent = true; $user->makeEphemeral(); } return $user; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return PhabricatorPolicies::POLICY_PUBLIC; case PhabricatorPolicyCapability::CAN_EDIT: return PhabricatorPolicies::POLICY_NOONE; } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getPHID() && ($viewer->getPHID() === $this->getPHID()); } public function describeAutomaticCapability($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_EDIT: return pht('Only you can edit your information.'); default: return null; } } /* -( PhabricatorCustomFieldInterface )------------------------------------ */ public function getCustomFieldSpecificationForRole($role) { return PhabricatorEnv::getEnvConfig('user.fields'); } public function getCustomFieldBaseClass() { return 'PhabricatorUserCustomField'; } public function getCustomFields() { return $this->assertAttached($this->customFields); } public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { $this->customFields = $fields; return $this; } } diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index 08a4c2134..940a78e42 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -1,1753 +1,1761 @@ array( 'type' => 'db', 'name' => 'audit', 'after' => array( /* First Patch */ ), ), 'db.calendar' => array( 'type' => 'db', 'name' => 'calendar', ), 'db.chatlog' => array( 'type' => 'db', 'name' => 'chatlog', ), 'db.conduit' => array( 'type' => 'db', 'name' => 'conduit', ), 'db.countdown' => array( 'type' => 'db', 'name' => 'countdown', ), 'db.daemon' => array( 'type' => 'db', 'name' => 'daemon', ), 'db.differential' => array( 'type' => 'db', 'name' => 'differential', ), 'db.draft' => array( 'type' => 'db', 'name' => 'draft', ), 'db.drydock' => array( 'type' => 'db', 'name' => 'drydock', ), 'db.feed' => array( 'type' => 'db', 'name' => 'feed', ), 'db.file' => array( 'type' => 'db', 'name' => 'file', ), 'db.flag' => array( 'type' => 'db', 'name' => 'flag', ), 'db.harbormaster' => array( 'type' => 'db', 'name' => 'harbormaster', ), 'db.herald' => array( 'type' => 'db', 'name' => 'herald', ), 'db.maniphest' => array( 'type' => 'db', 'name' => 'maniphest', ), 'db.meta_data' => array( 'type' => 'db', 'name' => 'meta_data', ), 'db.metamta' => array( 'type' => 'db', 'name' => 'metamta', ), 'db.oauth_server' => array( 'type' => 'db', 'name' => 'oauth_server', ), 'db.owners' => array( 'type' => 'db', 'name' => 'owners', ), 'db.pastebin' => array( 'type' => 'db', 'name' => 'pastebin', ), 'db.phame' => array( 'type' => 'db', 'name' => 'phame', ), 'db.phriction' => array( 'type' => 'db', 'name' => 'phriction', ), 'db.project' => array( 'type' => 'db', 'name' => 'project', ), 'db.repository' => array( 'type' => 'db', 'name' => 'repository', ), 'db.search' => array( 'type' => 'db', 'name' => 'search', ), 'db.slowvote' => array( 'type' => 'db', 'name' => 'slowvote', ), 'db.timeline' => array( 'type' => 'db', 'name' => 'timeline', 'dead' => true, ), 'db.user' => array( 'type' => 'db', 'name' => 'user', ), 'db.worker' => array( 'type' => 'db', 'name' => 'worker', ), 'db.xhpastview' => array( 'type' => 'db', 'name' => 'xhpastview', ), 'db.cache' => array( 'type' => 'db', 'name' => 'cache', ), 'db.fact' => array( 'type' => 'db', 'name' => 'fact', ), 'db.ponder' => array( 'type' => 'db', 'name' => 'ponder', ), 'db.xhprof' => array( 'type' => 'db', 'name' => 'xhprof', ), 'db.pholio' => array( 'type' => 'db', 'name' => 'pholio', ), 'db.conpherence' => array( 'type' => 'db', 'name' => 'conpherence', ), 'db.config' => array( 'type' => 'db', 'name' => 'config', ), 'db.token' => array( 'type' => 'db', 'name' => 'token', ), 'db.releeph' => array( 'type' => 'db', 'name' => 'releeph', ), 'db.phlux' => array( 'type' => 'db', 'name' => 'phlux', ), 'db.phortune' => array( 'type' => 'db', 'name' => 'phortune', ), 'db.phrequent' => array( 'type' => 'db', 'name' => 'phrequent', ), 'db.diviner' => array( 'type' => 'db', 'name' => 'diviner', ), 'db.auth' => array( 'type' => 'db', 'name' => 'auth', ), 'db.doorkeeper' => array( 'type' => 'db', 'name' => 'doorkeeper', ), 'db.legalpad' => array( 'type' => 'db', 'name' => 'legalpad', ), 'db.policy' => array( 'type' => 'db', 'name' => 'policy', ), 'db.nuance' => array( 'type' => 'db', 'name' => 'nuance', ), '0000.legacy.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('0000.legacy.sql'), 'legacy' => 0, ), '000.project.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('000.project.sql'), 'legacy' => 0, ), '001.maniphest_projects.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('001.maniphest_projects.sql'), 'legacy' => 1, ), '002.oauth.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('002.oauth.sql'), 'legacy' => 2, ), '003.more_oauth.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('003.more_oauth.sql'), 'legacy' => 3, ), '004.daemonrepos.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('004.daemonrepos.sql'), 'legacy' => 4, ), '005.workers.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('005.workers.sql'), 'legacy' => 5, ), '006.repository.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('006.repository.sql'), 'legacy' => 6, ), '007.daemonlog.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('007.daemonlog.sql'), 'legacy' => 7, ), '008.repoopt.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('008.repoopt.sql'), 'legacy' => 8, ), '009.repo_summary.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('009.repo_summary.sql'), 'legacy' => 9, ), '010.herald.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('010.herald.sql'), 'legacy' => 10, ), '011.badcommit.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('011.badcommit.sql'), 'legacy' => 11, ), '012.dropphidtype.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('012.dropphidtype.sql'), 'legacy' => 12, ), '013.commitdetail.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('013.commitdetail.sql'), 'legacy' => 13, ), '014.shortcuts.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('014.shortcuts.sql'), 'legacy' => 14, ), '015.preferences.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('015.preferences.sql'), 'legacy' => 15, ), '016.userrealnameindex.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('016.userrealnameindex.sql'), 'legacy' => 16, ), '017.sessionkeys.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('017.sessionkeys.sql'), 'legacy' => 17, ), '018.owners.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('018.owners.sql'), 'legacy' => 18, ), '019.arcprojects.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('019.arcprojects.sql'), 'legacy' => 19, ), '020.pathcapital.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('020.pathcapital.sql'), 'legacy' => 20, ), '021.xhpastview.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('021.xhpastview.sql'), 'legacy' => 21, ), '022.differentialcommit.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('022.differentialcommit.sql'), 'legacy' => 22, ), '023.dxkeys.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('023.dxkeys.sql'), 'legacy' => 23, ), '024.mlistkeys.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('024.mlistkeys.sql'), 'legacy' => 24, ), '025.commentopt.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('025.commentopt.sql'), 'legacy' => 25, ), '026.diffpropkey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('026.diffpropkey.sql'), 'legacy' => 26, ), '027.metamtakeys.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('027.metamtakeys.sql'), 'legacy' => 27, ), '028.systemagent.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('028.systemagent.sql'), 'legacy' => 28, ), '029.cursors.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('029.cursors.sql'), 'legacy' => 29, ), '030.imagemacro.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('030.imagemacro.sql'), 'legacy' => 30, ), '031.workerrace.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('031.workerrace.sql'), 'legacy' => 31, ), '032.viewtime.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('032.viewtime.sql'), 'legacy' => 32, ), '033.privtest.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('033.privtest.sql'), 'legacy' => 33, ), '034.savedheader.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('034.savedheader.sql'), 'legacy' => 34, ), '035.proxyimage.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('035.proxyimage.sql'), 'legacy' => 35, ), '036.mailkey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('036.mailkey.sql'), 'legacy' => 36, ), '037.setuptest.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('037.setuptest.sql'), 'legacy' => 37, ), '038.admin.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('038.admin.sql'), 'legacy' => 38, ), '039.userlog.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('039.userlog.sql'), 'legacy' => 39, ), '040.transform.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('040.transform.sql'), 'legacy' => 40, ), '041.heraldrepetition.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('041.heraldrepetition.sql'), 'legacy' => 41, ), '042.commentmetadata.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('042.commentmetadata.sql'), 'legacy' => 42, ), '043.pastebin.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('043.pastebin.sql'), 'legacy' => 43, ), '044.countdown.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('044.countdown.sql'), 'legacy' => 44, ), '045.timezone.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('045.timezone.sql'), 'legacy' => 45, ), '046.conduittoken.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('046.conduittoken.sql'), 'legacy' => 46, ), '047.projectstatus.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('047.projectstatus.sql'), 'legacy' => 47, ), '048.relationshipkeys.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('048.relationshipkeys.sql'), 'legacy' => 48, ), '049.projectowner.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('049.projectowner.sql'), 'legacy' => 49, ), '050.taskdenormal.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('050.taskdenormal.sql'), 'legacy' => 50, ), '051.projectfilter.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('051.projectfilter.sql'), 'legacy' => 51, ), '052.pastelanguage.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('052.pastelanguage.sql'), 'legacy' => 52, ), '053.feed.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('053.feed.sql'), 'legacy' => 53, ), '054.subscribers.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('054.subscribers.sql'), 'legacy' => 54, ), '055.add_author_to_files.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('055.add_author_to_files.sql'), 'legacy' => 55, ), '056.slowvote.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('056.slowvote.sql'), 'legacy' => 56, ), '057.parsecache.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('057.parsecache.sql'), 'legacy' => 57, ), '058.missingkeys.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('058.missingkeys.sql'), 'legacy' => 58, ), '059.engines.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('059.engines.php'), 'legacy' => 59, ), '060.phriction.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('060.phriction.sql'), 'legacy' => 60, ), '061.phrictioncontent.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('061.phrictioncontent.sql'), 'legacy' => 61, ), '062.phrictionmenu.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('062.phrictionmenu.sql'), 'legacy' => 62, ), '063.pasteforks.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('063.pasteforks.sql'), 'legacy' => 63, ), '064.subprojects.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('064.subprojects.sql'), 'legacy' => 64, ), '065.sshkeys.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('065.sshkeys.sql'), 'legacy' => 65, ), '066.phrictioncontent.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('066.phrictioncontent.sql'), 'legacy' => 66, ), '067.preferences.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('067.preferences.sql'), 'legacy' => 67, ), '068.maniphestauxiliarystorage.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('068.maniphestauxiliarystorage.sql'), 'legacy' => 68, ), '069.heraldxscript.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('069.heraldxscript.sql'), 'legacy' => 69, ), '070.differentialaux.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('070.differentialaux.sql'), 'legacy' => 70, ), '071.contentsource.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('071.contentsource.sql'), 'legacy' => 71, ), '072.blamerevert.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('072.blamerevert.sql'), 'legacy' => 72, ), '073.reposymbols.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('073.reposymbols.sql'), 'legacy' => 73, ), '074.affectedpath.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('074.affectedpath.sql'), 'legacy' => 74, ), '075.revisionhash.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('075.revisionhash.sql'), 'legacy' => 75, ), '076.indexedlanguages.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('076.indexedlanguages.sql'), 'legacy' => 76, ), '077.originalemail.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('077.originalemail.sql'), 'legacy' => 77, ), '078.nametoken.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('078.nametoken.sql'), 'legacy' => 78, ), '079.nametokenindex.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('079.nametokenindex.php'), 'legacy' => 79, ), '080.filekeys.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('080.filekeys.sql'), 'legacy' => 80, ), '081.filekeys.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('081.filekeys.php'), 'legacy' => 81, ), '082.xactionkey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('082.xactionkey.sql'), 'legacy' => 82, ), '083.dxviewtime.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('083.dxviewtime.sql'), 'legacy' => 83, ), '084.pasteauthorkey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('084.pasteauthorkey.sql'), 'legacy' => 84, ), '085.packagecommitrelationship.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('085.packagecommitrelationship.sql'), 'legacy' => 85, ), '086.formeraffil.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('086.formeraffil.sql'), 'legacy' => 86, ), '087.phrictiondelete.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('087.phrictiondelete.sql'), 'legacy' => 87, ), '088.audit.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('088.audit.sql'), 'legacy' => 88, ), '089.projectwiki.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('089.projectwiki.sql'), 'legacy' => 89, ), '090.forceuniqueprojectnames.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('090.forceuniqueprojectnames.php'), 'legacy' => 90, ), '091.uniqueslugkey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('091.uniqueslugkey.sql'), 'legacy' => 91, ), '092.dropgithubnotification.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('092.dropgithubnotification.sql'), 'legacy' => 92, ), '093.gitremotes.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('093.gitremotes.php'), 'legacy' => 93, ), '094.phrictioncolumn.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('094.phrictioncolumn.sql'), 'legacy' => 94, ), '095.directory.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('095.directory.sql'), 'legacy' => 95, ), '096.filename.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('096.filename.sql'), 'legacy' => 96, ), '097.heraldruletypes.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('097.heraldruletypes.sql'), 'legacy' => 97, ), '098.heraldruletypemigration.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('098.heraldruletypemigration.php'), 'legacy' => 98, ), '099.drydock.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('099.drydock.sql'), 'legacy' => 99, ), '100.projectxaction.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('100.projectxaction.sql'), 'legacy' => 100, ), '101.heraldruleapplied.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('101.heraldruleapplied.sql'), 'legacy' => 101, ), '102.heraldcleanup.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('102.heraldcleanup.php'), 'legacy' => 102, ), '103.heraldedithistory.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('103.heraldedithistory.sql'), 'legacy' => 103, ), '104.searchkey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('104.searchkey.sql'), 'legacy' => 104, ), '105.mimetype.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('105.mimetype.sql'), 'legacy' => 105, ), '106.chatlog.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('106.chatlog.sql'), 'legacy' => 106, ), '107.oauthserver.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('107.oauthserver.sql'), 'legacy' => 107, ), '108.oauthscope.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('108.oauthscope.sql'), 'legacy' => 108, ), '109.oauthclientphidkey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('109.oauthclientphidkey.sql'), 'legacy' => 109, ), '110.commitaudit.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('110.commitaudit.sql'), 'legacy' => 110, ), '111.commitauditmigration.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('111.commitauditmigration.php'), 'legacy' => 111, ), '112.oauthaccesscoderedirecturi.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('112.oauthaccesscoderedirecturi.sql'), 'legacy' => 112, ), '113.lastreviewer.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('113.lastreviewer.sql'), 'legacy' => 113, ), '114.auditrequest.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('114.auditrequest.sql'), 'legacy' => 114, ), '115.prepareutf8.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('115.prepareutf8.sql'), 'legacy' => 115, ), '116.utf8-backup-first-expect-wait.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('116.utf8-backup-first-expect-wait.sql'), 'legacy' => 116, ), '117.repositorydescription.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('117.repositorydescription.php'), 'legacy' => 117, ), '118.auditinline.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('118.auditinline.sql'), 'legacy' => 118, ), '119.filehash.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('119.filehash.sql'), 'legacy' => 119, ), '120.noop.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('120.noop.sql'), 'legacy' => 120, ), '121.drydocklog.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('121.drydocklog.sql'), 'legacy' => 121, ), '122.flag.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('122.flag.sql'), 'legacy' => 122, ), '123.heraldrulelog.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('123.heraldrulelog.sql'), 'legacy' => 123, ), '124.subpriority.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('124.subpriority.sql'), 'legacy' => 124, ), '125.ipv6.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('125.ipv6.sql'), 'legacy' => 125, ), '126.edges.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('126.edges.sql'), 'legacy' => 126, ), '127.userkeybody.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('127.userkeybody.sql'), 'legacy' => 127, ), '128.phabricatorcom.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('128.phabricatorcom.sql'), 'legacy' => 128, ), '129.savedquery.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('129.savedquery.sql'), 'legacy' => 129, ), '130.denormalrevisionquery.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('130.denormalrevisionquery.sql'), 'legacy' => 130, ), '131.migraterevisionquery.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('131.migraterevisionquery.php'), 'legacy' => 131, ), '132.phame.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('132.phame.sql'), 'legacy' => 132, ), '133.imagemacro.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('133.imagemacro.sql'), 'legacy' => 133, ), '134.emptysearch.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('134.emptysearch.sql'), 'legacy' => 134, ), '135.datecommitted.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('135.datecommitted.sql'), 'legacy' => 135, ), '136.sex.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('136.sex.sql'), 'legacy' => 136, ), '137.auditmetadata.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('137.auditmetadata.sql'), 'legacy' => 137, ), '138.notification.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('138.notification.sql'), ), 'holidays.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('holidays.sql'), ), 'userstatus.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('userstatus.sql'), ), 'emailtable.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('emailtable.sql'), ), 'emailtableport.sql' => array( 'type' => 'php', 'name' => $this->getPatchPath('emailtableport.php'), ), 'emailtableremove.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('emailtableremove.sql'), ), 'phiddrop.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('phiddrop.sql'), ), 'testdatabase.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('testdatabase.sql'), ), 'ldapinfo.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('ldapinfo.sql'), ), 'threadtopic.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('threadtopic.sql'), ), 'usertranslation.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('usertranslation.sql'), ), 'differentialbookmarks.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('differentialbookmarks.sql'), ), 'harbormasterobject.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('harbormasterobject.sql'), ), 'markupcache.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('markupcache.sql'), ), 'maniphestxcache.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('maniphestxcache.sql'), ), 'migrate-maniphest-dependencies.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('migrate-maniphest-dependencies.php'), ), 'migrate-differential-dependencies.php' => array( 'type' => 'php', 'name' => $this->getPatchPath( 'migrate-differential-dependencies.php'), ), 'phameblog.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('phameblog.sql'), ), 'migrate-maniphest-revisions.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('migrate-maniphest-revisions.php'), ), 'daemonstatus.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('daemonstatus.sql'), ), 'symbolcontexts.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('symbolcontexts.sql'), ), 'migrate-project-edges.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('migrate-project-edges.php'), ), 'fact-raw.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('fact-raw.sql'), ), 'ponder.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('ponder.sql') ), 'policy-project.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('policy-project.sql'), ), 'daemonstatuskey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('daemonstatuskey.sql'), ), 'edgetype.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('edgetype.sql'), ), 'ponder-comments.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('ponder-comments.sql'), ), 'pastepolicy.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('pastepolicy.sql'), ), 'xhprof.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('xhprof.sql'), ), 'draft-metadata.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('draft-metadata.sql'), ), 'phamedomain.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('phamedomain.sql'), ), 'ponder-mailkey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('ponder-mailkey.sql'), ), 'ponder-mailkey-populate.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('ponder-mailkey-populate.php'), ), 'phamepolicy.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('phamepolicy.sql'), ), 'phameoneblog.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('phameoneblog.sql'), ), 'statustxt.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('statustxt.sql'), ), 'daemontaskarchive.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('daemontaskarchive.sql'), ), 'drydocktaskid.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('drydocktaskid.sql'), ), 'drydockresoucetype.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('drydockresourcetype.sql'), ), 'liskcounters.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('liskcounters.sql'), ), 'liskcounters.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('liskcounters.php'), ), 'dropfileproxyimage.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('dropfileproxyimage.sql'), ), 'repository-lint.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('repository-lint.sql'), ), 'liskcounters-task.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('liskcounters-task.sql'), ), 'pholio.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('pholio.sql'), ), 'owners-exclude.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('owners-exclude.sql'), ), '20121209.pholioxactions.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20121209.pholioxactions.sql'), ), '20121209.xmacroadd.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20121209.xmacroadd.sql'), ), '20121209.xmacromigrate.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20121209.xmacromigrate.php'), ), '20121209.xmacromigratekey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20121209.xmacromigratekey.sql'), ), '20121220.generalcache.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20121220.generalcache.sql'), ), '20121226.config.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20121226.config.sql'), ), '20130101.confxaction.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130101.confxaction.sql'), ), '20130102.metamtareceivedmailmessageidhash.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130102.metamtareceivedmailmessageidhash.sql'), ), '20130103.filemetadata.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130103.filemetadata.sql'), ), '20130111.conpherence.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130111.conpherence.sql'), ), '20130127.altheraldtranscript.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130127.altheraldtranscript.sql'), ), '20130201.revisionunsubscribed.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130201.revisionunsubscribed.php'), ), '20130201.revisionunsubscribed.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130201.revisionunsubscribed.sql'), ), '20130131.conpherencepics.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130131.conpherencepics.sql'), ), '20130214.chatlogchannel.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130214.chatlogchannel.sql'), ), '20130214.chatlogchannelid.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130214.chatlogchannelid.sql'), ), '20130214.token.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130214.token.sql'), ), '20130215.phabricatorfileaddttl.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130215.phabricatorfileaddttl.sql'), ), '20130217.cachettl.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130217.cachettl.sql'), ), '20130218.updatechannelid.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130218.updatechannelid.php'), ), '20130218.longdaemon.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130218.longdaemon.sql'), ), '20130219.commitsummary.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130219.commitsummary.sql'), ), '20130219.commitsummarymig.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130219.commitsummarymig.php'), ), '20130222.dropchannel.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130222.dropchannel.sql'), ), '20130226.commitkey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130226.commitkey.sql'), ), '20131302.maniphestvalue.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20131302.maniphestvalue.sql'), ), '20130304.lintauthor.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130304.lintauthor.sql'), ), 'releeph.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('releeph.sql'), ), '20130319.phabricatorfileexplicitupload.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath( '20130319.phabricatorfileexplicitupload.sql') ), '20130319.conpherence.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130319.conpherence.sql'), ), '20130320.phlux.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130320.phlux.sql'), ), '20130317.phrictionedge.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130317.phrictionedge.sql'), ), '20130321.token.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130321.token.sql'), ), '20130310.xactionmeta.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130310.xactionmeta.sql'), ), '20130322.phortune.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130322.phortune.sql'), ), '20130323.phortunepayment.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130323.phortunepayment.sql'), ), '20130324.phortuneproduct.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130324.phortuneproduct.sql'), ), '20130330.phrequent.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130330.phrequent.sql'), ), '20130403.conpherencecache.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130403.conpherencecache.sql'), ), '20130403.conpherencecachemig.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130403.conpherencecachemig.php'), ), '20130409.commitdrev.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130409.commitdrev.php'), ), '20130417.externalaccount.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130417.externalaccount.sql'), ), '20130423.updateexternalaccount.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130423.updateexternalaccount.sql'), ), '20130423.phortunepaymentrevised.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130423.phortunepaymentrevised.sql'), ), '20130423.conpherenceindices.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130423.conpherenceindices.sql'), ), '20130426.search_savedquery.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130426.search_savedquery.sql'), ), '20130502.countdownrevamp1.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130502.countdownrevamp1.sql'), ), '20130502.countdownrevamp2.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130502.countdownrevamp2.php'), ), '20130502.countdownrevamp3.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130502.countdownrevamp3.sql'), ), '20130507.releephrqsimplifycols.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130507.releephrqsimplifycols.sql'), ), '20130507.releephrqmailkey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130507.releephrqmailkey.sql'), ), '20130507.releephrqmailkeypop.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130507.releephrqmailkeypop.php'), ), '20130508.search_namedquery.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130508.search_namedquery.sql'), ), '20130508.releephtransactions.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130508.releephtransactions.sql'), ), '20130508.releephtransactionsmig.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130508.releephtransactionsmig.php'), ), '20130513.receviedmailstatus.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130513.receviedmailstatus.sql'), ), '20130519.diviner.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130519.diviner.sql'), ), '20130521.dropconphimages.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130521.dropconphimages.sql'), ), '20130523.maniphest_owners.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130523.maniphest_owners.sql'), ), '20130524.repoxactions.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130524.repoxactions.sql'), ), '20130529.macroauthor.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130529.macroauthor.sql'), ), '20130529.macroauthormig.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130529.macroauthormig.php'), ), '20130530.sessionhash.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130530.sessionhash.php'), ), '20130530.macrodatekey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130530.macrodatekey.sql'), ), '20130530.pastekeys.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130530.pastekeys.sql'), ), '20130531.filekeys.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130531.filekeys.sql'), ), '20130602.morediviner.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130602.morediviner.sql'), ), '20130602.namedqueries.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130602.namedqueries.sql'), ), '20130606.userxactions.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130606.userxactions.sql'), ), '20130607.xaccount.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130607.xaccount.sql'), ), '20130611.migrateoauth.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130611.migrateoauth.php'), ), '20130611.nukeldap.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130611.nukeldap.php'), ), '20130613.authdb.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130613.authdb.sql'), ), '20130619.authconf.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130619.authconf.php'), ), '20130620.diffxactions.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130620.diffxactions.sql'), ), '20130621.diffcommentphid.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130621.diffcommentphid.sql'), ), '20130621.diffcommentphidmig.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130621.diffcommentphidmig.php'), ), '20130621.diffcommentunphid.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130621.diffcommentunphid.sql'), ), '20130622.doorkeeper.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130622.doorkeeper.sql'), ), '20130628.legalpadv0.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130628.legalpadv0.sql'), ), '20130701.conduitlog.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130701.conduitlog.sql'), ), 'legalpad-mailkey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('legalpad-mailkey.sql'), ), 'legalpad-mailkey-populate.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('legalpad-mailkey-populate.php'), ), '20130703.legalpaddocdenorm.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130703.legalpaddocdenorm.sql'), ), '20130703.legalpaddocdenorm.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130703.legalpaddocdenorm.php'), ), '20130709.legalpadsignature.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130709.legalpadsignature.sql'), ), '20130709.droptimeline.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130709.droptimeline.sql'), ), '20130711.trimrealnames.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130711.trimrealnames.php'), ), '20130714.votexactions.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130714.votexactions.sql'), ), '20130715.votecomments.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130715.votecomments.php'), ), '20130715.voteedges.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130715.voteedges.sql'), ), '20130711.pholioimageobsolete.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130711.pholioimageobsolete.sql'), ), '20130711.pholioimageobsolete.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130711.pholioimageobsolete.php'), ), '20130711.pholioimageobsolete2.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130711.pholioimageobsolete2.sql'), ), '20130716.archivememberlessprojects.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130716.archivememberlessprojects.php'), ), '20130722.pholioreplace.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130722.pholioreplace.sql'), ), '20130723.taskstarttime.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130723.taskstarttime.sql'), ), '20130727.ponderquestionstatus.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130727.ponderquestionstatus.sql'), ), '20130726.ponderxactions.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130726.ponderxactions.sql'), ), '20130728.ponderunique.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130728.ponderunique.php'), ), '20130728.ponderuniquekey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130728.ponderuniquekey.sql'), ), '20130728.ponderxcomment.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130728.ponderxcomment.php'), ), '20130801.pastexactions.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130801.pastexactions.sql'), ), '20130801.pastexactions.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130801.pastexactions.php'), ), '20130805.pastemailkey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130805.pastemailkey.sql'), ), '20130805.pasteedges.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130805.pasteedges.sql'), ), '20130805.pastemailkeypop.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130805.pastemailkeypop.php'), ), '20130802.heraldphid.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130802.heraldphid.sql'), ), '20130802.heraldphids.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130802.heraldphids.php'), ), '20130802.heraldphidukey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130802.heraldphidukey.sql'), ), '20130802.heraldxactions.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130802.heraldxactions.sql'), ), '20130731.releephrepoid.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130731.releephrepoid.sql'), ), '20130731.releephproject.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130731.releephproject.sql'), ), '20130731.releephcutpointidentifier.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130731.releephcutpointidentifier.sql'), ), '20130814.usercustom.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130814.usercustom.sql'), ), '20130820.releephxactions.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130820.releephxactions.sql'), ), '20130826.divinernode.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130826.divinernode.sql'), ), '20130820.filexactions.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130820.filexactions.sql'), ), '20130820.filemailkey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130820.filemailkey.sql'), ), '20130820.file-mailkey-populate.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130820.file-mailkey-populate.php'), ), '20130912.maniphest.1.touch.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130912.maniphest.1.touch.sql'), ), '20130912.maniphest.2.created.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130912.maniphest.2.created.sql'), ), '20130912.maniphest.3.nameindex.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130912.maniphest.3.nameindex.sql'), ), '20130912.maniphest.4.fillindex.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130912.maniphest.4.fillindex.php'), ), '20130913.maniphest.1.migratesearch.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130913.maniphest.1.migratesearch.php'), ), '20130914.usercustom.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130914.usercustom.sql'), ), '20130915.maniphestcustom.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130915.maniphestcustom.sql'), ), '20130915.maniphestmigrate.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130915.maniphestmigrate.php'), ), '20130919.mfieldconf.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130919.mfieldconf.php'), ), '20130920.repokeyspolicy.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130920.repokeyspolicy.sql'), ), '20130921.mtransactions.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130921.mtransactions.sql'), ), '20130921.xmigratemaniphest.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130921.xmigratemaniphest.php'), ), '20130923.mrename.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130923.mrename.sql'), ), '20130924.mdraftkey.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130924.mdraftkey.sql'), ), '20130925.mpolicy.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130925.mpolicy.sql'), ), '20130925.xpolicy.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130925.xpolicy.sql'), ), '20130926.dcustom.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130926.dcustom.sql'), ), '20130926.dinkeys.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130926.dinkeys.sql'), ), '20130927.audiomacro.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130927.audiomacro.sql'), ), '20130929.filepolicy.sql' => array( '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'), ), '20130915.maniphestqdrop.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20130915.maniphestqdrop.sql'), ), '20130926.dinline.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20130926.dinline.php'), ), '20131020.pcustom.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20131020.pcustom.sql'), ), '20131020.col1.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20131020.col1.sql'), ), '20131020.pxaction.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20131020.pxaction.sql'), ), '20131020.pxactionmig.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20131020.pxactionmig.php'), ), '20131020.harbormaster.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20131020.harbormaster.sql'), ), '20131025.repopush.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20131025.repopush.sql'), ), '20131026.commitstatus.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20131026.commitstatus.sql'), ), '20131030.repostatusmessage.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20131030.repostatusmessage.sql'), ), '20131031.vcspassword.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20131031.vcspassword.sql'), ), '20131105.buildstep.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20131105.buildstep.sql'), ), '20131106.diffphid.1.col.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20131106.diffphid.1.col.sql'), ), '20131106.diffphid.2.mig.php' => array( 'type' => 'php', 'name' => $this->getPatchPath('20131106.diffphid.2.mig.php'), ), '20131106.diffphid.3.key.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20131106.diffphid.3.key.sql'), ), '20131106.nuance-v0.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20131106.nuance-v0.sql'), ), '20131107.buildlog.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('20131107.buildlog.sql'), ), + '20131112.userverified.1.col.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20131112.userverified.1.col.sql'), + ), + '20131112.userverified.2.mig.php' => array( + 'type' => 'php', + 'name' => $this->getPatchPath('20131112.userverified.2.mig.php'), + ), ); } } diff --git a/src/infrastructure/testing/PhabricatorTestCase.php b/src/infrastructure/testing/PhabricatorTestCase.php index 18c0ed7dc..85627c0bf 100644 --- a/src/infrastructure/testing/PhabricatorTestCase.php +++ b/src/infrastructure/testing/PhabricatorTestCase.php @@ -1,209 +1,214 @@ getPhabricatorTestCaseConfiguration() + array( self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK => true, self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => false, ); if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) { // Fixtures don't make sense with process isolation. $config[self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK] = false; } return $config; } public function willRunTestCases(array $test_cases) { $root = dirname(phutil_get_library_root('phabricator')); require_once $root.'/scripts/__init_script__.php'; $config = $this->getComputedConfiguration(); if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) { ++self::$storageFixtureReferences; if (!self::$storageFixture) { self::$storageFixture = $this->newStorageFixture(); } } ++self::$testsAreRunning; } public function didRunTestCases(array $test_cases) { if (self::$storageFixture) { self::$storageFixtureReferences--; if (!self::$storageFixtureReferences) { self::$storageFixture = null; } } --self::$testsAreRunning; } protected function willRunTests() { $config = $this->getComputedConfiguration(); if ($config[self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK]) { LiskDAO::beginIsolateAllLiskEffectsToCurrentProcess(); } $this->env = PhabricatorEnv::beginScopedEnv(); // NOTE: While running unit tests, we act as though all applications are // installed, regardless of the install's configuration. Tests which need // to uninstall applications are responsible for adjusting state themselves // (such tests are exceedingly rare). $this->env->overrideEnvConfig( 'phabricator.uninstalled-applications', array()); $this->env->overrideEnvConfig( 'phabricator.show-beta-applications', true); + // Reset application settings to defaults, particularly policies. + $this->env->overrideEnvConfig( + 'phabricator.application-settings', + array()); + // TODO: Remove this when we remove "releeph.installed". $this->env->overrideEnvConfig('releeph.installed', true); } protected function didRunTests() { $config = $this->getComputedConfiguration(); if ($config[self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK]) { LiskDAO::endIsolateAllLiskEffectsToCurrentProcess(); } try { if (phutil_is_hiphop_runtime()) { $this->env->__destruct(); } unset($this->env); } catch (Exception $ex) { throw new Exception( "Some test called PhabricatorEnv::beginScopedEnv(), but is still ". "holding a reference to the scoped environment!"); } } protected function willRunOneTest($test) { $config = $this->getComputedConfiguration(); if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) { LiskDAO::beginIsolateAllLiskEffectsToTransactions(); } } protected function didRunOneTest($test) { $config = $this->getComputedConfiguration(); if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) { LiskDAO::endIsolateAllLiskEffectsToTransactions(); } } protected function newStorageFixture() { $bytes = Filesystem::readRandomCharacters(24); $name = self::NAMESPACE_PREFIX.$bytes; return new PhabricatorStorageFixtureScopeGuard($name); } protected function getLink($method) { $phabricator_project = 'PHID-APRJ-3f1fc779edeab89b2171'; return 'https://secure.phabricator.com/diffusion/symbol/'.$method. '/?lang=php&projects='.$phabricator_project. '&jump=true&context='.get_class($this); } /** * Returns an integer seed to use when building unique identifiers (e.g., * non-colliding usernames). The seed is unstable and its value will change * between test runs, so your tests must not rely on it. * * @return int A unique integer. */ protected function getNextObjectSeed() { self::$storageFixtureObjectSeed += mt_rand(1, 100); return self::$storageFixtureObjectSeed; } protected function generateNewTestUser() { $seed = $this->getNextObjectSeed(); $user = id(new PhabricatorUser()) ->setRealName("Test User {$seed}}") ->setUserName("test{$seed}"); $email = id(new PhabricatorUserEmail()) ->setAddress("testuser{$seed}@example.com") ->setIsVerified(1); $editor = new PhabricatorUserEditor(); $editor->setActor($user); $editor->createNewUser($user, $email); return $user; } /** * Throws unless tests are currently executing. This method can be used to * guard code which is specific to unit tests and should not normally be * reachable. * * If tests aren't currently being executed, throws an exception. */ public static function assertExecutingUnitTests() { if (!self::$testsAreRunning) { throw new Exception( "Executing test code outside of test execution! This code path can ". "only be run during unit tests."); } } } diff --git a/src/view/AphrontDialogView.php b/src/view/AphrontDialogView.php index 1a590c4b6..4076106c9 100644 --- a/src/view/AphrontDialogView.php +++ b/src/view/AphrontDialogView.php @@ -1,253 +1,263 @@ method = $method; return $this; } public function setIsStandalone($is_standalone) { $this->isStandalone = $is_standalone; return $this; } public function getIsStandalone() { return $this->isStandalone; } private $width = 'default'; const WIDTH_DEFAULT = 'default'; const WIDTH_FORM = 'form'; const WIDTH_FULL = 'full'; public function setSubmitURI($uri) { $this->submitURI = $uri; return $this; } public function setTitle($title) { $this->title = $title; return $this; } public function getTitle() { return $this->title; } public function addSubmitButton($text = null) { if (!$text) { $text = pht('Okay'); } $this->submitButton = $text; return $this; } public function addCancelButton($uri, $text = null) { if (!$text) { $text = pht('Cancel'); } $this->cancelURI = $uri; $this->cancelText = $text; return $this; } public function addFooter($footer) { $this->footers[] = $footer; return $this; } public function addHiddenInput($key, $value) { if (is_array($value)) { foreach ($value as $hidden_key => $hidden_value) { $this->hidden[] = array($key.'['.$hidden_key.']', $hidden_value); } } else { $this->hidden[] = array($key, $value); } return $this; } public function setClass($class) { $this->class = $class; return $this; } public function setRenderDialogAsDiv() { // TODO: This API is awkward. $this->renderAsForm = false; return $this; } public function setFormID($id) { $this->formID = $id; return $this; } public function setWidth($width) { $this->width = $width; return $this; } public function setHeaderColor($color) { $this->headerColor = $color; return $this; } + public function appendParagraph($paragraph) { + return $this->appendChild( + phutil_tag( + 'p', + array( + 'class' => 'aphront-dialog-view-paragraph', + ), + $paragraph)); + } + final public function render() { require_celerity_resource('aphront-dialog-view-css'); $buttons = array(); if ($this->submitButton) { $buttons[] = javelin_tag( 'button', array( 'name' => '__submit__', 'sigil' => '__default__', ), $this->submitButton); } if ($this->cancelURI) { $buttons[] = javelin_tag( 'a', array( 'href' => $this->cancelURI, 'class' => 'button grey', 'name' => '__cancel__', 'sigil' => 'jx-workflow-button', ), $this->cancelText); } if (!$this->user) { throw new Exception( pht("You must call setUser() when rendering an AphrontDialogView.")); } $more = $this->class; switch ($this->width) { case self::WIDTH_FORM: case self::WIDTH_FULL: $more .= ' aphront-dialog-view-width-'.$this->width; break; case self::WIDTH_DEFAULT: break; default: throw new Exception("Unknown dialog width '{$this->width}'!"); } if ($this->isStandalone) { $more .= ' aphront-dialog-view-standalone'; } $attributes = array( 'class' => 'aphront-dialog-view '.$more, 'sigil' => 'jx-dialog', ); $form_attributes = array( 'action' => $this->submitURI, 'method' => $this->method, 'id' => $this->formID, ); $hidden_inputs = array(); $hidden_inputs[] = phutil_tag( 'input', array( 'type' => 'hidden', 'name' => '__dialog__', 'value' => '1', )); foreach ($this->hidden as $desc) { list($key, $value) = $desc; $hidden_inputs[] = javelin_tag( 'input', array( 'type' => 'hidden', 'name' => $key, 'value' => $value, 'sigil' => 'aphront-dialog-application-input' )); } if (!$this->renderAsForm) { $buttons = array(phabricator_form( $this->user, $form_attributes, array_merge($hidden_inputs, $buttons))); } $children = $this->renderChildren(); $header = new PhabricatorActionHeaderView(); $header->setHeaderTitle($this->title); $header->setHeaderColor($this->headerColor); $footer = null; if ($this->footers) { $footer = phutil_tag( 'div', array( 'class' => 'aphront-dialog-foot', ), $this->footers); } $content = array( phutil_tag( 'div', array( 'class' => 'aphront-dialog-head', ), $header), phutil_tag('div', array( 'class' => 'aphront-dialog-body grouped', ), $children), phutil_tag( 'div', array( 'class' => 'aphront-dialog-tail grouped', ), array( $buttons, $footer, )), ); if ($this->renderAsForm) { return phabricator_form( $this->user, $form_attributes + $attributes, array($hidden_inputs, $content)); } else { return javelin_tag( 'div', $attributes, $content); } } } diff --git a/webroot/rsrc/css/aphront/dialog-view.css b/webroot/rsrc/css/aphront/dialog-view.css index 0462c8301..ea77b9852 100644 --- a/webroot/rsrc/css/aphront/dialog-view.css +++ b/webroot/rsrc/css/aphront/dialog-view.css @@ -1,126 +1,130 @@ /** * @provides aphront-dialog-view-css */ .aphront-dialog-view { width: 540px; margin: 32px auto 16px; border: 1px solid {$lightblueborder}; border-bottom: 1px solid {$blueborder}; box-shadow: 0 1px 5px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.5); } .device-phone .aphront-dialog-view { margin: 16px; width: auto; } .aphront-dialog-view-standalone { margin: auto; } .aphront-dialog-head .phabricator-action-header { border-bottom: 1px solid {$lightblueborder}; padding: 4px 16px; white-space: nowrap; } .aphront-dialog-head .phabricator-action-header .phabricator-action-header-title { font-size: 15px; color: {$bluetext}; text-shadow: 0 1px 2px #fff; } .aphront-dialog-view-width-form { width: 600px; } .aphront-dialog-view-width-full { width: 90%; } .aphront-dialog-body { background: #ffffff; padding: 16px; border: none; } .aphront-dialog-tail { border: none; background: {$lightgreybackground}; padding: 8px 16px; border-top: 1px solid #d4dadf; } .aphront-dialog-foot { padding: 6px 0; float: left; } .aphront-dialog-tail button, .aphront-dialog-tail a.button { float: right; margin-left: 8px; } .jx-client-dialog { position: absolute; width: 100%; } .jx-mask { opacity: .75; background: #fff; position: fixed; top: 0; left: 0; right: 0; bottom: 0; } .jx-dark-mask { background: #000000; opacity: 0.9; } .aphront-exception-dialog { width: 95%; } .aphront-exception-dialog .exception-message { font-size: 14px; background: #efefef; padding: 1em; white-space: pre-wrap; } .aphront-exception-dialog .exception-trace { margin-top: 15px; } .aphront-exception-dialog .exception-trace-header { font-size: 11px; color: {$greytext}; border-bottom: 1px solid #aaaaaa; padding-bottom: .5em; margin-bottom: .5em; } .aphront-access-dialog { width: 50%; } .aphront-access-dialog ul { margin: 12px 24px; list-style: circle; } .aphront-policy-rejection { font-weight: bold; } .aphront-capability-details { margin: 20px 0 4px; } + +.aphront-dialog-view-paragraph + .aphront-dialog-view-paragraph { + margin-top: 16px; +}