diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c8c97ed5ec..7f24c77fc2 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,327 +1,331 @@ array( 'Aphront400Response' => 'aphront/response/400', 'Aphront404Response' => 'aphront/response/404', 'AphrontAjaxResponse' => 'aphront/response/ajax', 'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration', 'AphrontController' => 'aphront/controller', 'AphrontDatabaseConnection' => 'storage/connection/base', 'AphrontDefaultApplicationConfiguration' => 'aphront/default/configuration', 'AphrontDefaultApplicationController' => 'aphront/default/controller', 'AphrontDialogResponse' => 'aphront/response/dialog', 'AphrontDialogView' => 'view/dialog', 'AphrontErrorView' => 'view/form/error', 'AphrontException' => 'aphront/exception/base', 'AphrontFileResponse' => 'aphront/response/file', 'AphrontFormCheckboxControl' => 'view/form/control/checkbox', 'AphrontFormControl' => 'view/form/control/base', 'AphrontFormFileControl' => 'view/form/control/file', 'AphrontFormMarkupControl' => 'view/form/control/markup', 'AphrontFormSelectControl' => 'view/form/control/select', 'AphrontFormStaticControl' => 'view/form/control/static', 'AphrontFormSubmitControl' => 'view/form/control/submit', 'AphrontFormTextAreaControl' => 'view/form/control/textarea', 'AphrontFormTextControl' => 'view/form/control/text', 'AphrontFormTokenizerControl' => 'view/form/control/tokenizer', 'AphrontFormView' => 'view/form/base', 'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql', 'AphrontNullView' => 'view/null', 'AphrontPageView' => 'view/page/base', 'AphrontPanelView' => 'view/layout/panel', 'AphrontQueryConnectionException' => 'storage/exception/connection', 'AphrontQueryConnectionLostException' => 'storage/exception/connectionlost', 'AphrontQueryCountException' => 'storage/exception/count', + 'AphrontQueryDuplicateKeyException' => 'storage/exception/duplicatekey', 'AphrontQueryException' => 'storage/exception/base', 'AphrontQueryObjectMissingException' => 'storage/exception/objectmissing', 'AphrontQueryParameterException' => 'storage/exception/parameter', 'AphrontQueryRecoverableException' => 'storage/exception/recoverable', 'AphrontRedirectException' => 'aphront/exception/redirect', 'AphrontRedirectResponse' => 'aphront/response/redirect', 'AphrontRequest' => 'aphront/request', 'AphrontRequestFailureView' => 'view/page/failure', 'AphrontResponse' => 'aphront/response/base', 'AphrontSideNavView' => 'view/layout/sidenav', 'AphrontTableView' => 'view/control/table', 'AphrontURIMapper' => 'aphront/mapper', 'AphrontView' => 'view/base', 'AphrontWebpageResponse' => 'aphront/response/webpage', 'CelerityAPI' => 'infratructure/celerity/api', 'CelerityResourceController' => 'infratructure/celerity/controller', 'CelerityResourceMap' => 'infratructure/celerity/map', 'CelerityStaticResourceResponse' => 'infratructure/celerity/response', 'ConduitAPIMethod' => 'applications/conduit/method/base', 'ConduitAPIRequest' => 'applications/conduit/protocol/request', 'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect', 'ConduitAPI_differential_creatediff_Method' => 'applications/conduit/method/differential/creatediff', 'ConduitAPI_differential_setdiffproperty_Method' => 'applications/conduit/method/differential/setdiffproperty', 'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload', 'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find', 'ConduitException' => 'applications/conduit/protocol/exception', 'DifferentialAction' => 'applications/differential/constants/action', 'DifferentialAddCommentView' => 'applications/differential/view/addcomment', 'DifferentialCCWelcomeMail' => 'applications/differential/mail/ccwelcome', 'DifferentialChangeType' => 'applications/differential/constants/changetype', 'DifferentialChangeset' => 'applications/differential/storage/changeset', 'DifferentialChangesetDetailView' => 'applications/differential/view/changesetdetailview', 'DifferentialChangesetListView' => 'applications/differential/view/changesetlistview', 'DifferentialChangesetParser' => 'applications/differential/parser/changeset', 'DifferentialChangesetViewController' => 'applications/differential/controller/changesetview', 'DifferentialComment' => 'applications/differential/storage/comment', 'DifferentialCommentEditor' => 'applications/differential/editor/comment', 'DifferentialCommentMail' => 'applications/differential/mail/comment', 'DifferentialCommentSaveController' => 'applications/differential/controller/commentsave', 'DifferentialController' => 'applications/differential/controller/base', 'DifferentialDAO' => 'applications/differential/storage/base', 'DifferentialDiff' => 'applications/differential/storage/diff', 'DifferentialDiffContentMail' => 'applications/differential/mail/diffcontent', 'DifferentialDiffProperty' => 'applications/differential/storage/diffproperty', 'DifferentialDiffTableOfContentsView' => 'applications/differential/view/difftableofcontents', 'DifferentialDiffViewController' => 'applications/differential/controller/diffview', 'DifferentialHunk' => 'applications/differential/storage/hunk', 'DifferentialLintStatus' => 'applications/differential/constants/lintstatus', 'DifferentialMail' => 'applications/differential/mail/base', 'DifferentialMarkupEngineFactory' => 'applications/differential/parser/markup', 'DifferentialNewDiffMail' => 'applications/differential/mail/newdiff', 'DifferentialReviewRequestMail' => 'applications/differential/mail/reviewrequest', 'DifferentialRevision' => 'applications/differential/storage/revision', 'DifferentialRevisionCommentListView' => 'applications/differential/view/revisioncommentlist', 'DifferentialRevisionCommentView' => 'applications/differential/view/revisioncomment', 'DifferentialRevisionControlSystem' => 'applications/differential/constants/revisioncontrolsystem', 'DifferentialRevisionDetailView' => 'applications/differential/view/revisiondetail', 'DifferentialRevisionEditController' => 'applications/differential/controller/revisionedit', 'DifferentialRevisionEditor' => 'applications/differential/editor/revision', 'DifferentialRevisionListController' => 'applications/differential/controller/revisionlist', 'DifferentialRevisionListData' => 'applications/differential/data/revisionlist', 'DifferentialRevisionStatus' => 'applications/differential/constants/revisionstatus', 'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/revisionupdatehistory', 'DifferentialRevisionViewController' => 'applications/differential/controller/revisionview', 'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus', 'Javelin' => 'infratructure/javelin/api', 'LiskDAO' => 'storage/lisk/dao', 'Phabricator404Controller' => 'applications/base/controller/404', - 'PhabricatorAuthController' => 'applications/auth/controlller/base', + 'PhabricatorAuthController' => 'applications/auth/controller/base', 'PhabricatorConduitAPIController' => 'applications/conduit/controller/api', 'PhabricatorConduitConnectionLog' => 'applications/conduit/storage/connectionlog', 'PhabricatorConduitConsoleController' => 'applications/conduit/controller/console', 'PhabricatorConduitController' => 'applications/conduit/controller/base', 'PhabricatorConduitDAO' => 'applications/conduit/storage/base', 'PhabricatorConduitLogController' => 'applications/conduit/controller/log', 'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/methodcalllog', 'PhabricatorController' => 'applications/base/controller/base', 'PhabricatorDirectoryCategory' => 'applications/directory/storage/category', 'PhabricatorDirectoryCategoryDeleteController' => 'applications/directory/controller/categorydelete', 'PhabricatorDirectoryCategoryEditController' => 'applications/directory/controller/categoryedit', 'PhabricatorDirectoryCategoryListController' => 'applications/directory/controller/categorylist', 'PhabricatorDirectoryController' => 'applications/directory/controller/base', 'PhabricatorDirectoryDAO' => 'applications/directory/storage/base', 'PhabricatorDirectoryItem' => 'applications/directory/storage/item', 'PhabricatorDirectoryItemDeleteController' => 'applications/directory/controller/itemdelete', 'PhabricatorDirectoryItemEditController' => 'applications/directory/controller/itemedit', 'PhabricatorDirectoryItemListController' => 'applications/directory/controller/itemlist', 'PhabricatorDirectoryMainController' => 'applications/directory/controller/main', + 'PhabricatorFacebookConnectController' => 'applications/auth/controller/facebookconnect', 'PhabricatorFile' => 'applications/files/storage/file', 'PhabricatorFileController' => 'applications/files/controller/base', 'PhabricatorFileDAO' => 'applications/files/storage/base', 'PhabricatorFileListController' => 'applications/files/controller/list', 'PhabricatorFileStorageBlob' => 'applications/files/storage/storageblob', 'PhabricatorFileURI' => 'applications/files/uri', 'PhabricatorFileUploadController' => 'applications/files/controller/upload', 'PhabricatorFileViewController' => 'applications/files/controller/view', 'PhabricatorLiskDAO' => 'applications/base/storage/lisk', - 'PhabricatorLoginController' => 'applications/auth/controlller/login', - 'PhabricatorLogoutController' => 'applications/auth/controlller/logout', + 'PhabricatorLoginController' => 'applications/auth/controller/login', + 'PhabricatorLogoutController' => 'applications/auth/controller/logout', 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/base', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/phpmailerlite', 'PhabricatorMetaMTAController' => 'applications/metamta/controller/base', 'PhabricatorMetaMTADAO' => 'applications/metamta/storage/base', 'PhabricatorMetaMTAListController' => 'applications/metamta/controller/list', 'PhabricatorMetaMTAMail' => 'applications/metamta/storage/mail', 'PhabricatorMetaMTAMailingList' => 'applications/metamta/storage/mailinglist', 'PhabricatorMetaMTAMailingListEditController' => 'applications/metamta/controller/mailinglistedit', 'PhabricatorMetaMTAMailingListsController' => 'applications/metamta/controller/mailinglists', 'PhabricatorMetaMTASendController' => 'applications/metamta/controller/send', 'PhabricatorMetaMTAViewController' => 'applications/metamta/controller/view', 'PhabricatorObjectHandle' => 'applications/phid/handle', 'PhabricatorObjectHandleData' => 'applications/phid/handle/data', 'PhabricatorPHID' => 'applications/phid/storage/phid', 'PhabricatorPHIDAllocateController' => 'applications/phid/controller/allocate', 'PhabricatorPHIDController' => 'applications/phid/controller/base', 'PhabricatorPHIDDAO' => 'applications/phid/storage/base', 'PhabricatorPHIDListController' => 'applications/phid/controller/list', 'PhabricatorPHIDLookupController' => 'applications/phid/controller/lookup', 'PhabricatorPHIDType' => 'applications/phid/storage/type', 'PhabricatorPHIDTypeEditController' => 'applications/phid/controller/typeedit', 'PhabricatorPHIDTypeListController' => 'applications/phid/controller/typelist', 'PhabricatorPeopleController' => 'applications/people/controller/base', 'PhabricatorPeopleEditController' => 'applications/people/controller/edit', 'PhabricatorPeopleListController' => 'applications/people/controller/list', 'PhabricatorPeopleProfileController' => 'applications/people/controller/profile', 'PhabricatorStandardPageView' => 'view/page/standard', 'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/common', 'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base', 'PhabricatorUser' => 'applications/people/storage/user', 'PhabricatorUserDAO' => 'applications/people/storage/base', ), 'function' => array( '_qsprintf_check_scalar_type' => 'storage/qsprintf', '_qsprintf_check_type' => 'storage/qsprintf', 'celerity_generate_unique_node_id' => 'infratructure/celerity/api', 'celerity_register_resource_map' => 'infratructure/celerity/map', 'javelin_render_tag' => 'infratructure/javelin/markup', 'phabricator_format_relative_time' => 'view/utils', 'phabricator_format_timestamp' => 'view/utils', 'phabricator_format_units_generic' => 'view/utils', 'qsprintf' => 'storage/qsprintf', 'queryfx' => 'storage/queryfx', 'queryfx_all' => 'storage/queryfx', 'queryfx_one' => 'storage/queryfx', 'require_celerity_resource' => 'infratructure/celerity/api', 'vqsprintf' => 'storage/qsprintf', 'vqueryfx' => 'storage/queryfx', 'vqueryfx_all' => 'storage/queryfx', 'xsprintf_query' => 'storage/qsprintf', ), 'requires_class' => array( 'Aphront400Response' => 'AphrontResponse', 'Aphront404Response' => 'AphrontResponse', 'AphrontAjaxResponse' => 'AphrontResponse', 'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration', 'AphrontDefaultApplicationController' => 'AphrontController', 'AphrontDialogResponse' => 'AphrontResponse', 'AphrontDialogView' => 'AphrontView', 'AphrontErrorView' => 'AphrontView', 'AphrontFileResponse' => 'AphrontResponse', 'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormControl' => 'AphrontView', 'AphrontFormFileControl' => 'AphrontFormControl', 'AphrontFormMarkupControl' => 'AphrontFormControl', 'AphrontFormSelectControl' => 'AphrontFormControl', 'AphrontFormStaticControl' => 'AphrontFormControl', 'AphrontFormSubmitControl' => 'AphrontFormControl', 'AphrontFormTextAreaControl' => 'AphrontFormControl', 'AphrontFormTextControl' => 'AphrontFormControl', 'AphrontFormTokenizerControl' => 'AphrontFormControl', 'AphrontFormView' => 'AphrontView', 'AphrontMySQLDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontNullView' => 'AphrontView', 'AphrontPageView' => 'AphrontView', 'AphrontPanelView' => 'AphrontView', 'AphrontQueryConnectionException' => 'AphrontQueryException', 'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException', 'AphrontQueryCountException' => 'AphrontQueryException', + 'AphrontQueryDuplicateKeyException' => 'AphrontQueryException', 'AphrontQueryObjectMissingException' => 'AphrontQueryException', 'AphrontQueryParameterException' => 'AphrontQueryException', 'AphrontQueryRecoverableException' => 'AphrontQueryException', 'AphrontRedirectException' => 'AphrontException', 'AphrontRedirectResponse' => 'AphrontResponse', 'AphrontRequestFailureView' => 'AphrontView', 'AphrontSideNavView' => 'AphrontView', 'AphrontTableView' => 'AphrontView', 'AphrontWebpageResponse' => 'AphrontResponse', 'CelerityResourceController' => 'AphrontController', 'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod', 'ConduitAPI_user_find_Method' => 'ConduitAPIMethod', 'DifferentialAddCommentView' => 'AphrontView', 'DifferentialCCWelcomeMail' => 'DifferentialReviewRequestMail', 'DifferentialChangeset' => 'DifferentialDAO', 'DifferentialChangesetDetailView' => 'AphrontView', 'DifferentialChangesetListView' => 'AphrontView', 'DifferentialChangesetViewController' => 'DifferentialController', 'DifferentialComment' => 'DifferentialDAO', 'DifferentialCommentMail' => 'DifferentialMail', 'DifferentialCommentSaveController' => 'DifferentialController', 'DifferentialController' => 'PhabricatorController', 'DifferentialDAO' => 'PhabricatorLiskDAO', 'DifferentialDiff' => 'DifferentialDAO', 'DifferentialDiffContentMail' => 'DifferentialMail', 'DifferentialDiffProperty' => 'DifferentialDAO', 'DifferentialDiffTableOfContentsView' => 'AphrontView', 'DifferentialDiffViewController' => 'DifferentialController', 'DifferentialHunk' => 'DifferentialDAO', 'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail', 'DifferentialReviewRequestMail' => 'DifferentialMail', 'DifferentialRevision' => 'DifferentialDAO', 'DifferentialRevisionCommentListView' => 'AphrontView', 'DifferentialRevisionCommentView' => 'AphrontView', 'DifferentialRevisionDetailView' => 'AphrontView', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionUpdateHistoryView' => 'AphrontView', 'DifferentialRevisionViewController' => 'DifferentialController', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAuthController' => 'PhabricatorController', 'PhabricatorConduitAPIController' => 'PhabricatorConduitController', 'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitConsoleController' => 'PhabricatorConduitController', 'PhabricatorConduitController' => 'PhabricatorController', 'PhabricatorConduitDAO' => 'PhabricatorLiskDAO', 'PhabricatorConduitLogController' => 'PhabricatorConduitController', 'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO', 'PhabricatorController' => 'AphrontController', 'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryController' => 'PhabricatorController', 'PhabricatorDirectoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorDirectoryItem' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryItemDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController', + 'PhabricatorFacebookConnectController' => 'PhabricatorAuthController', 'PhabricatorFile' => 'PhabricatorFileDAO', 'PhabricatorFileController' => 'PhabricatorController', 'PhabricatorFileDAO' => 'PhabricatorLiskDAO', 'PhabricatorFileListController' => 'PhabricatorFileController', 'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO', 'PhabricatorFileUploadController' => 'PhabricatorFileController', 'PhabricatorFileViewController' => 'PhabricatorFileController', 'PhabricatorLiskDAO' => 'LiskDAO', 'PhabricatorLoginController' => 'PhabricatorAuthController', 'PhabricatorLogoutController' => 'PhabricatorAuthController', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMetaMTAController' => 'PhabricatorController', 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO', 'PhabricatorMetaMTAListController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingList' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingListEditController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMailingListsController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTASendController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController', 'PhabricatorPHID' => 'PhabricatorPHIDDAO', 'PhabricatorPHIDAllocateController' => 'PhabricatorPHIDController', 'PhabricatorPHIDController' => 'PhabricatorController', 'PhabricatorPHIDDAO' => 'PhabricatorLiskDAO', 'PhabricatorPHIDListController' => 'PhabricatorPHIDController', 'PhabricatorPHIDLookupController' => 'PhabricatorPHIDController', 'PhabricatorPHIDType' => 'PhabricatorPHIDDAO', 'PhabricatorPHIDTypeEditController' => 'PhabricatorPHIDController', 'PhabricatorPHIDTypeListController' => 'PhabricatorPHIDController', 'PhabricatorPeopleController' => 'PhabricatorController', 'PhabricatorPeopleEditController' => 'PhabricatorPeopleController', 'PhabricatorPeopleListController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController', 'PhabricatorStandardPageView' => 'AphrontPageView', 'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController', 'PhabricatorUser' => 'PhabricatorUserDAO', 'PhabricatorUserDAO' => 'PhabricatorLiskDAO', ), 'requires_interface' => array( ), )); diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index 5082398cd4..ef0619b0cc 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -1,187 +1,188 @@ array( '$' => 'RepositoryListController', 'new/$' => 'RepositoryEditController', 'edit/(?\d+)/$' => 'RepositoryEditController', 'delete/(?\d+)/$' => 'RepositoryDeleteController', ), '/' => array( '$' => 'PhabricatorDirectoryMainController', ), '/directory/' => array( 'item/$' => 'PhabricatorDirectoryItemListController', 'item/edit/(?:(?\d+)/)?$' => 'PhabricatorDirectoryItemEditController', 'item/delete/(?\d+)/' => 'PhabricatorDirectoryItemDeleteController', 'category/$' => 'PhabricatorDirectoryCategoryListController', 'category/edit/(?:(?\d+)/)?$' => 'PhabricatorDirectoryCategoryEditController', 'category/delete/(?\d+)/' => 'PhabricatorDirectoryCategoryDeleteController', ), '/file/' => array( '$' => 'PhabricatorFileListController', 'upload/$' => 'PhabricatorFileUploadController', '(?info)/(?[^/]+)/' => 'PhabricatorFileViewController', '(?view)/(?[^/]+)/' => 'PhabricatorFileViewController', '(?download)/(?[^/]+)/' => 'PhabricatorFileViewController', ), '/phid/' => array( '$' => 'PhabricatorPHIDLookupController', 'list/$' => 'PhabricatorPHIDListController', 'type/$' => 'PhabricatorPHIDTypeListController', 'type/edit/(?:(?\d+)/)?$' => 'PhabricatorPHIDTypeEditController', 'new/$' => 'PhabricatorPHIDAllocateController', ), '/people/' => array( '$' => 'PhabricatorPeopleListController', 'edit/(?:(?\w+)/)?$' => 'PhabricatorPeopleEditController', ), '/p/(?\w+)/$' => 'PhabricatorPeopleProfileController', '/conduit/' => array( '$' => 'PhabricatorConduitConsoleController', 'method/(?[^/]+)$' => 'PhabricatorConduitConsoleController', 'log/$' => 'PhabricatorConduitLogController', ), '/api/(?[^/]+)$' => 'PhabricatorConduitAPIController', '/D(?\d+)' => 'DifferentialRevisionViewController', '/differential/' => array( '$' => 'DifferentialRevisionListController', 'filter/(?\w+)/$' => 'DifferentialRevisionListController', 'diff/(?\d+)/$' => 'DifferentialDiffViewController', 'changeset/(?\d+)/$' => 'DifferentialChangesetViewController', 'revision/edit/(?:(?\d+)/)?$' => 'DifferentialRevisionEditController', 'comment/' => array( 'preview/$' => 'DifferentialCommentPreviewController', 'save/$' => 'DifferentialCommentSaveController', 'inline/' => array( 'preview/$' => 'DifferentialInlineCommentPreviewController', 'edit/$' => 'DifferentialInlineCommentEditController', ), ), ), '/res/' => array( '(?pkg/)?(?[a-f0-9]{8})/(?.+\.(?:css|js))$' => 'CelerityResourceController', ), '/typeahead/' => array( 'common/(?\w+)/$' => 'PhabricatorTypeaheadCommonDatasourceController', ), '/mail/' => array( '$' => 'PhabricatorMetaMTAListController', 'send/$' => 'PhabricatorMetaMTASendController', 'view/(?\d+)/$' => 'PhabricatorMetaMTAViewController', 'lists/$' => 'PhabricatorMetaMTAMailingListsController', 'lists/edit/(?:(?\d+)/)?$' => 'PhabricatorMetaMTAMailingListEditController', ), - '/login/' => 'PhabricatorLoginController', - '/logout/' => 'PhabricatorLogoutController', + '/login/$' => 'PhabricatorLoginController', + '/logout/$' => 'PhabricatorLogoutController', + '/facebook-connect/$' => 'PhabricatorFacebookConnectController', ); } public function buildRequest() { $request = new AphrontRequest($this->getHost(), $this->getPath()); $request->setRequestData($_GET + $_POST); return $request; } public function handleException(Exception $ex) { $class = phutil_escape_html(get_class($ex)); $message = phutil_escape_html($ex->getMessage()); $content = '
'. '

Unhandled Exception "'.$class.'": '.$message.'

'. ''.phutil_escape_html((string)$ex).''. '
'; $view = new PhabricatorStandardPageView(); $view->appendChild($content); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } public function willSendResponse(AphrontResponse $response) { $request = $this->getRequest(); if ($response instanceof AphrontDialogResponse) { if (!$request->isAjax()) { $view = new PhabricatorStandardPageView(); $view->appendChild( '
'. $response->buildResponseString(). '
'); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } } else if ($response instanceof Aphront404Response) { $failure = new AphrontRequestFailureView(); $failure->setHeader('404 Not Found'); $failure->appendChild( '

The page you requested was not found.

'); $view = new PhabricatorStandardPageView(); $view->setTitle('404 Not Found'); $view->appendChild($failure); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); $response->setHTTPResponseCode(404); return $response; } return $response; } public function build404Controller() { return array(new Phabricator404Controller($this->getRequest()), array()); } } diff --git a/src/applications/auth/controlller/base/PhabricatorAuthController.php b/src/applications/auth/controller/base/PhabricatorAuthController.php similarity index 100% rename from src/applications/auth/controlller/base/PhabricatorAuthController.php rename to src/applications/auth/controller/base/PhabricatorAuthController.php diff --git a/src/applications/auth/controlller/base/__init__.php b/src/applications/auth/controller/base/__init__.php similarity index 100% rename from src/applications/auth/controlller/base/__init__.php rename to src/applications/auth/controller/base/__init__.php diff --git a/src/applications/auth/controller/facebookconnect/PhabricatorFacebookConnectController.php b/src/applications/auth/controller/facebookconnect/PhabricatorFacebookConnectController.php new file mode 100644 index 0000000000..34b2116ea2 --- /dev/null +++ b/src/applications/auth/controller/facebookconnect/PhabricatorFacebookConnectController.php @@ -0,0 +1,207 @@ +getRequest(); + + if ($request->getStr('error')) { + die("OMG ERROR"); + } + + $token = $request->getStr('token'); + if (!$token) { + $code = $request->getStr('code'); + $auth_uri = 'https://graph.facebook.com/oauth/access_token'. + '?client_id=184510521580034'. + '&redirect_uri=http://local.aphront.com/facebook-connect/'. + '&client_secret=OMGSECRETS'. + '&code='.$code; + + $response = @file_get_contents($auth_uri); + if ($response === false) { + throw new Exception('failed to open oauth thing'); + } + + $data = array(); + parse_str($response, $data); + + $token = $data['access_token']; + } + + $user_json = @file_get_contents('https://graph.facebook.com/me?access_token='.$token); + $user_data = json_decode($user_json, true); + + $user_id = $user_data['id']; + + $known_user = id(new PhabricatorUser()) + ->loadOneWhere('facebookUID = %d', $user_id); + if ($known_user) { + $session_key = $known_user->establishSession('web'); + $request->setCookie('phusr', $known_user->getUsername()); + $request->setCookie('phsid', $session_key); + return id(new AphrontRedirectResponse()) + ->setURI('/'); + } + + $current_user = $this->getRequest()->getUser(); + if ($current_user->getPHID()) { + if ($current_user->getFacebookUID() && + $current_user->getFacebookUID() != $user_id) { + throw new Exception( + "Your account is already associated with a Facebook user ID other ". + "than the one you just logged in with...?"); + } + + if ($request->isFormPost()) { + $current_user->setFacebookUID($user_id); + $current_user->save(); + + // TODO: ship them back to the 'account' page or whatever? + return id(new AphrontRedirectResponse()) + ->setURI('/'); + } + + $ph_account = $current_user->getUsername(); + $fb_account = phutil_escape_html($user_data['name']); + + $form = new AphrontFormView(); + $form + ->addHiddenInput('token', $token) + ->setUser($request->getUser()) + ->setAction('/facebook-connect/') + ->appendChild( + '

Do you want to link your '. + "existing Phabricator account ({$ph_account}) ". + "with your Facebook account ({$fb_account}) so ". + "you can login with Facebook Connect?") + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Link Accounts') + ->addCancelButton('/login/')); + + $panel = new AphrontPanelView(); + $panel->setHeader('Link Facebook Account'); + $panel->setWidth(AphrontPanelView::WIDTH_FORM); + $panel->appendChild($form); + + return $this->buildStandardPageResponse( + $panel, + array( + 'title' => 'Link Facebook Account', + )); + } + + $errors = array(); + $e_username = true; + + $user = new PhabricatorUser(); + + $matches = null; + if (preg_match('@/([a-zA-Z0-9]+)$@', $user_data['link'], $matches)) { + $user->setUsername($matches[1]); + } + + if ($request->isFormPost()) { + + $username = $request->getStr('username'); + if (!strlen($username)) { + $e_username = 'Required'; + $errors[] = 'Username is required.'; + } else if (!preg_match('/^[a-zA-Z0-9]+$/', $username, $matches)) { + $e_username = 'Invalid'; + $errors[] = 'Username may only contain letters and numbers.'; + } + + $user->setUsername($username); + $user->setFacebookUID($user_id); + $user->setEmail($user_data['email']); + + if (!$errors) { + $image = @file_get_contents('https://graph.facebook.com/me/picture?access_token='.$token); + $file = PhabricatorFile::newFromFileData( + $image, + array( + 'name' => 'fbprofile.jpg' + )); + + $user->setProfileImagePHID($file->getPHID()); + $user->setRealName($user_data['name']); + + try { + $user->save(); + + $session_key = $user->establishSession('web'); + $request->setCookie('phusr', $user->getUsername()); + $request->setCookie('phsid', $session_key); + return id(new AphrontRedirectResponse())->setURI('/'); + } catch (AphrontQueryDuplicateKeyException $exception) { + $key = $exception->getDuplicateKey(); + if ($key == 'userName') { + $e_username = 'Duplicate'; + $errors[] = 'That username is not unique.'; + } else { + throw $exception; + } + } + } + } + + $error_view = null; + if ($errors) { + $error_view = new AphrontErrorView(); + $error_view->setTitle('Facebook Connect Failed'); + $error_view->setErrors($errors); + } + + $form = new AphrontFormView(); + $form + ->addHiddenInput('token', $token) + ->setUser($request->getUser()) + ->setAction('/facebook-connect/') + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Username') + ->setName('username') + ->setValue($user->getUsername()) + ->setError($e_username)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Create Account')); + + $panel = new AphrontPanelView(); + $panel->setHeader('Create New Account'); + $panel->setWidth(AphrontPanelView::WIDTH_FORM); + $panel->appendChild($form); + + return $this->buildStandardPageResponse( + array( + $error_view, + $panel, + ), + array( + 'title' => 'Create New Account', + )); + } + +} diff --git a/src/applications/auth/controlller/login/__init__.php b/src/applications/auth/controller/facebookconnect/__init__.php similarity index 75% copy from src/applications/auth/controlller/login/__init__.php copy to src/applications/auth/controller/facebookconnect/__init__.php index 9909a8d0d4..7c02260f35 100644 --- a/src/applications/auth/controlller/login/__init__.php +++ b/src/applications/auth/controller/facebookconnect/__init__.php @@ -1,21 +1,22 @@ getRequest(); $error = false; - $login_name = $request->getCookie('phusr'); + $username = $request->getCookie('phusr'); if ($request->isFormPost()) { - $login_name = $request->getStr('login'); + $username = $request->getStr('username'); $user = id(new PhabricatorUser())->loadOneWhere( 'username = %s', - $login_name); + $username); $user->setPassword('asdf'); $user->save(); $okay = false; if ($user) { if ($user->comparePassword($request->getStr('password'))) { - $conn_w = $user->establishConnection('w'); - - $urandom = fopen('/dev/urandom', 'r'); - if (!$urandom) { - throw new Exception("Failed to open /dev/urandom!"); - } - $entropy = fread($urandom, 20); - if (strlen($entropy) != 20) { - throw new Exception("Failed to read /dev/urandom!"); - } - - $session_key = sha1($entropy); - queryfx( - $conn_w, - 'INSERT INTO phabricator_session '. - '(userPHID, type, sessionKey, sessionStart)'. - ' VALUES '. - '(%s, %s, %s, UNIX_TIMESTAMP()) '. - 'ON DUPLICATE KEY UPDATE '. - 'sessionKey = VALUES(sessionKey), '. - 'sessionStart = VALUES(sessionStart)', - $user->getPHID(), - 'web', - $session_key); + + $session_key = $user->establishSession('web'); $request->setCookie('phusr', $user->getUsername()); $request->setCookie('phsid', $session_key); return id(new AphrontRedirectResponse()) ->setURI('/'); } } if (!$okay) { $request->clearCookie('phusr'); $request->clearCookie('phsid'); } $error = true; } $error_view = null; if ($error) { $error_view = new AphrontErrorView(); $error_view->setTitle('Bad username/password.'); } $form = new AphrontFormView(); $form ->setUser($request->getUser()) ->setAction('/login/') ->appendChild( id(new AphrontFormTextControl()) - ->setLabel('Login') - ->setName('login') - ->setValue($login_name)) + ->setLabel('Username') + ->setName('username') + ->setValue($username)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Password') ->setName('password')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Login')); $panel = new AphrontPanelView(); $panel->setHeader('Phabricator Login'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); + + // TODO: Hardcoded junk + $connect_uri = "https://www.facebook.com/dialog/oauth"; + + $user = $request->getUser(); + + $facebook_connect = new AphrontFormView(); + $facebook_connect + ->setAction($connect_uri) + ->addHiddenInput('client_id', 184510521580034) + ->addHiddenInput('redirect_uri', 'http://local.aphront.com/facebook-connect/') + ->addHiddenInput('scope', 'email') + ->addHiddenInput('state', $user->getCSRFToken()) + ->setUser($request->getUser()) + ->setMethod('GET') + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue("Login with Facebook Connect \xC2\xBB")); + + $panel->appendChild('

Login with Facebook

'); + $panel->appendChild($facebook_connect); + return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Login', )); } } diff --git a/src/applications/auth/controlller/login/__init__.php b/src/applications/auth/controller/login/__init__.php similarity index 90% rename from src/applications/auth/controlller/login/__init__.php rename to src/applications/auth/controller/login/__init__.php index 9909a8d0d4..c3c5e62c3b 100644 --- a/src/applications/auth/controlller/login/__init__.php +++ b/src/applications/auth/controller/login/__init__.php @@ -1,21 +1,20 @@ true, ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID(self::PHID_TYPE); } public function setPassword($password) { $this->setPasswordSalt(md5(mt_rand())); $hash = $this->hashPassword($password); $this->setPasswordHash($hash); return $this; } public function comparePassword($password) { $password = $this->hashPassword($password); return ($password === $this->getPasswordHash()); } private function hashPassword($password) { $password = $this->getUsername(). $password. $this->getPHID(). $this->getPasswordSalt(); for ($ii = 0; $ii < 1000; $ii++) { $password = md5($password); } return $password; } const CSRF_CYCLE_FREQUENCY = 3600; public function getCSRFToken() { return $this->generateCSRFToken(time()); } public function validateCSRFToken($token) { for ($ii = -1; $ii <= 1; $ii++) { $time = time() + (self::CSRF_CYCLE_FREQUENCY * $ii); $valid = $this->generateCSRFToken($time); if ($token == $valid) { return true; } } return false; } private function generateCSRFToken($epoch) { $time_block = floor($epoch / (60 * 60)); // TODO: this should be a secret lolol $key = '0b7ec0592e0a2829d8b71df2fa269b2c6172eca3'; $vec = $this->getPHID().$this->passwordHash.$key.$time_block; return substr(md5($vec), 0, 16); } + public function establishSession($session_type) { + $conn_w = $this->establishConnection('w'); + + $urandom = fopen('/dev/urandom', 'r'); + if (!$urandom) { + throw new Exception("Failed to open /dev/urandom!"); + } + $entropy = fread($urandom, 20); + if (strlen($entropy) != 20) { + throw new Exception("Failed to read /dev/urandom!"); + } + + $session_key = sha1($entropy); + queryfx( + $conn_w, + 'INSERT INTO phabricator_session '. + '(userPHID, type, sessionKey, sessionStart)'. + ' VALUES '. + '(%s, %s, %s, UNIX_TIMESTAMP()) '. + 'ON DUPLICATE KEY UPDATE '. + 'sessionKey = VALUES(sessionKey), '. + 'sessionStart = VALUES(sessionStart)', + $this->getPHID(), + $session_type, + $session_key); + + return $session_key; + } + } diff --git a/src/applications/people/storage/user/__init__.php b/src/applications/people/storage/user/__init__.php index a6ff0d55ea..6b89e2b125 100644 --- a/src/applications/people/storage/user/__init__.php +++ b/src/applications/people/storage/user/__init__.php @@ -1,13 +1,14 @@ configuration = $configuration; } public function escapeString($string) { if (!$this->connection) { $this->establishConnection(); } return mysql_real_escape_string($string, $this->connection); } public function escapeColumnName($name) { return '`'.str_replace('`', '\\`', $name).'`'; } public function escapeMultilineComment($comment) { // These can either terminate a comment, confuse the hell out of the parser, // make MySQL execute the comment as a query, or, in the case of semicolon, // are quasi-dangerous because the semicolon could turn a broken query into // a working query plus an ignored query. static $map = array( '--' => '(DOUBLEDASH)', '*/' => '(STARSLASH)', '//' => '(SLASHSLASH)', '#' => '(HASH)', '!' => '(BANG)', ';' => '(SEMICOLON)', ); $comment = str_replace( array_keys($map), array_values($map), $comment); // For good measure, kill anything else that isn't a nice printable // character. $comment = preg_replace('/[^\x20-\x7F]+/', ' ', $comment); return '/* '.$comment.' */'; } public function escapeStringForLikeClause($value) { $value = $this->escapeString($value); // Ideally the query shouldn't be modified after safely escaping it, // but we need to escape _ and % within LIKE terms. $value = str_replace( // Even though we've already escaped, we need to replace \ with \\ // because MYSQL unescapes twice inside a LIKE clause. See note // at mysql.com. However, if the \ is being used to escape a single // quote ('), then the \ should not be escaped. Thus, after all \ // are replaced with \\, we need to revert instances of \\' back to // \'. array('\\', '\\\\\'', '_', '%'), array('\\\\', '\\\'', '\_', '\%'), $value); return $value; } private function getConfiguration($key, $default = null) { return idx($this->configuration, $key, $default); } private function establishConnection() { $this->connection = null; $user = $this->getConfiguration('user'); $host = $this->getConfiguration('host'); $conn = @mysql_connect( $host, $user, $this->getConfiguration('pass'), $new_link = true, $flags = 0); if (!$conn) { $errno = mysql_errno(); $error = mysql_error(); throw new AphrontQueryConnectionException( "Attempt to connect to {$user}@{$host} failed with error #{$errno}: ". "{$error}."); } $ret = @mysql_select_db($this->getConfiguration('database'), $conn); if (!$ret) { $this->throwQueryException($conn); } $this->connection = $conn; } public function getInsertID() { return mysql_insert_id($this->requireConnection()); } public function getAffectedRows() { return mysql_affected_rows($this->requireConnection()); } public function getTransactionKey() { return (int)$this->requireConnection(); } private function requireConnection() { if (!$this->connection) { $this->establishConnection(); } return $this->connection; } public function selectAllResults() { $result = array(); $res = $this->lastResult; if ($res == null) { throw new Exception('No query result to fetch from!'); } while (($row = mysql_fetch_assoc($res)) !== false) { $result[] = $row; } return $result; } public function executeRawQuery($raw_query) { $this->lastResult = null; $retries = 3; while ($retries--) { try { if (!$this->connection) { $this->establishConnection(); } $result = mysql_query($raw_query, $this->connection); if ($result) { $this->lastResult = $result; break; } $this->throwQueryException($this->connection); } catch (AphrontQueryConnectionLostException $ex) { if (!$retries) { throw $ex; } if ($this->isInsideTransaction()) { throw $ex; } $this->connection = null; } } } private function throwQueryException($connection) { $errno = mysql_errno($connection); $error = mysql_error($connection); switch ($errno) { case 2013: // Connection Dropped case 2006: // Gone Away throw new AphrontQueryConnectionLostException("#{$errno}: {$error}"); - break; case 1213: // Deadlock case 1205: // Lock wait timeout exceeded throw new AphrontQueryRecoverableException("#{$errno}: {$error}"); - break; + case 1062: // Duplicate Key + $matches = null; + $key = null; + if (preg_match('/for key \'(.*)\'$/', $error, $matches)) { + $key = $matches[1]; + } + throw new AphrontQueryDuplicateKeyException($key, "{$errno}: {$error}"); default: - // TODO: 1062 is syntax error, and quite terrible in production. + // TODO: 1064 is syntax error, and quite terrible in production. throw new AphrontQueryException("#{$errno}: {$error}"); } } } diff --git a/src/storage/connection/mysql/__init__.php b/src/storage/connection/mysql/__init__.php index c826a4068d..a92470889b 100644 --- a/src/storage/connection/mysql/__init__.php +++ b/src/storage/connection/mysql/__init__.php @@ -1,18 +1,19 @@ getRequest(); +/** + * @group storage + */ +class AphrontQueryDuplicateKeyException extends AphrontQueryException { - if ($request->isFormPost()) { - $request->clearCookie('phsid'); - return id(new AphrontRedirectResponse()) - ->setURI('/login/'); - } + private $duplicateKey; - return id(new AphrontRedirectResponse())->setURI('/'); + public function getDuplicateKey() { + return $this->duplicateKey; } + public function __construct($duplicate_key, $message) { + $this->duplicateKey = $duplicate_key; + parent::__construct($message); + } } diff --git a/src/storage/exception/duplicatekey/__init__.php b/src/storage/exception/duplicatekey/__init__.php new file mode 100644 index 0000000000..3f4e7d02d8 --- /dev/null +++ b/src/storage/exception/duplicatekey/__init__.php @@ -0,0 +1,12 @@ +