diff --git a/resources/sql/patches/015.preferences.sql b/resources/sql/patches/015.preferences.sql new file mode 100644 index 0000000000..08a6c71d8a --- /dev/null +++ b/resources/sql/patches/015.preferences.sql @@ -0,0 +1,6 @@ +CREATE TABLE phabricator_user.user_preferences ( + id int unsigned not null auto_increment primary key, + userPHID varchar(64) binary not null, + preferences longblob not null, + unique key (userPHID) +); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 49289e0c21..7e96b82ef7 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,735 +1,741 @@ array( 'Aphront400Response' => 'aphront/response/400', 'Aphront404Response' => 'aphront/response/404', 'AphrontAjaxResponse' => 'aphront/response/ajax', 'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration', 'AphrontController' => 'aphront/controller', 'AphrontCrumbsView' => 'view/layout/crumbs', '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', 'AphrontFormDividerControl' => 'view/form/control/divider', 'AphrontFormFileControl' => 'view/form/control/file', 'AphrontFormMarkupControl' => 'view/form/control/markup', 'AphrontFormPasswordControl' => 'view/form/control/password', 'AphrontFormRecaptchaControl' => 'view/form/control/recaptcha', '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', 'AphrontHeadsupActionListView' => 'view/layout/headsup/actionlist', 'AphrontHeadsupActionView' => 'view/layout/headsup/action', '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', 'AphrontTokenizerTemplateView' => 'view/control/tokenizer', 'AphrontURIMapper' => 'aphront/mapper', 'AphrontView' => 'view/base', 'AphrontWebpageResponse' => 'aphront/response/webpage', 'CelerityAPI' => 'infrastructure/celerity/api', 'CelerityResourceController' => 'infrastructure/celerity/controller', 'CelerityResourceMap' => 'infrastructure/celerity/map', 'CelerityStaticResourceResponse' => 'infrastructure/celerity/response', 'ConduitAPIMethod' => 'applications/conduit/method/base', 'ConduitAPIRequest' => 'applications/conduit/protocol/request', 'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect', 'ConduitAPI_conduit_ping_Method' => 'applications/conduit/method/conduit/ping', 'ConduitAPI_daemon_launched_Method' => 'applications/conduit/method/daemon/launched', 'ConduitAPI_daemon_log_Method' => 'applications/conduit/method/daemon/log', 'ConduitAPI_differential_creatediff_Method' => 'applications/conduit/method/differential/creatediff', 'ConduitAPI_differential_createrevision_Method' => 'applications/conduit/method/differential/createrevision', 'ConduitAPI_differential_find_Method' => 'applications/conduit/method/differential/find', 'ConduitAPI_differential_getcommitmessage_Method' => 'applications/conduit/method/differential/getcommitmessage', 'ConduitAPI_differential_getcommitpaths_Method' => 'applications/conduit/method/differential/getcommitpaths', 'ConduitAPI_differential_getdiff_Method' => 'applications/conduit/method/differential/getdiff', 'ConduitAPI_differential_markcommitted_Method' => 'applications/conduit/method/differential/markcommitted', 'ConduitAPI_differential_parsecommitmessage_Method' => 'applications/conduit/method/differential/parsecommitmessage', 'ConduitAPI_differential_setdiffproperty_Method' => 'applications/conduit/method/differential/setdiffproperty', 'ConduitAPI_differential_updaterevision_Method' => 'applications/conduit/method/differential/updaterevision', 'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload', 'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find', 'ConduitException' => 'applications/conduit/protocol/exception', 'DarkConsole' => 'aphront/console/api', 'DarkConsoleConfigPlugin' => 'aphront/console/plugin/config', 'DarkConsoleController' => 'aphront/console/controller', 'DarkConsoleCore' => 'aphront/console/core', 'DarkConsoleErrorLogPlugin' => 'aphront/console/plugin/errorlog', 'DarkConsoleErrorLogPluginAPI' => 'aphront/console/plugin/errorlog/api', 'DarkConsolePlugin' => 'aphront/console/plugin/base', 'DarkConsoleRequestPlugin' => 'aphront/console/plugin/request', 'DarkConsoleServicesPlugin' => 'aphront/console/plugin/services', 'DarkConsoleServicesPluginAPI' => 'aphront/console/plugin/services/api', 'DarkConsoleXHProfPlugin' => 'aphront/console/plugin/xhprof', 'DarkConsoleXHProfPluginAPI' => 'aphront/console/plugin/xhprof/api', 'DifferentialAction' => 'applications/differential/constants/action', 'DifferentialAddCommentView' => 'applications/differential/view/addcomment', 'DifferentialAttachController' => 'applications/differential/controller/attach', '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', 'DifferentialCommentPreviewController' => 'applications/differential/controller/commentpreview', 'DifferentialCommentSaveController' => 'applications/differential/controller/commentsave', 'DifferentialCommitMessage' => 'applications/differential/parser/commitmessage', 'DifferentialCommitMessageData' => 'applications/differential/data/commitmessage', 'DifferentialCommitMessageParserException' => 'applications/differential/parser/commitmessage/exception', 'DifferentialController' => 'applications/differential/controller/base', 'DifferentialDAO' => 'applications/differential/storage/base', 'DifferentialDiff' => 'applications/differential/storage/diff', 'DifferentialDiffContentMail' => 'applications/differential/mail/diffcontent', 'DifferentialDiffCreateController' => 'applications/differential/controller/diffcreate', 'DifferentialDiffProperty' => 'applications/differential/storage/diffproperty', 'DifferentialDiffTableOfContentsView' => 'applications/differential/view/difftableofcontents', 'DifferentialDiffViewController' => 'applications/differential/controller/diffview', 'DifferentialHunk' => 'applications/differential/storage/hunk', 'DifferentialInlineComment' => 'applications/differential/storage/inlinecomment', 'DifferentialInlineCommentEditController' => 'applications/differential/controller/inlinecommentedit', 'DifferentialInlineCommentPreviewController' => 'applications/differential/controller/inlinecommentpreview', 'DifferentialInlineCommentView' => 'applications/differential/view/inlinecomment', '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', 'DifferentialSubscribeController' => 'applications/differential/controller/subscribe', 'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus', 'DiffusionBranchInformation' => 'applications/diffusion/data/branch', 'DiffusionBranchQuery' => 'applications/diffusion/query/branch/base', 'DiffusionBranchTableView' => 'applications/diffusion/view/branchtable', 'DiffusionBrowseController' => 'applications/diffusion/controller/browse', 'DiffusionBrowseFileController' => 'applications/diffusion/controller/file', 'DiffusionBrowseQuery' => 'applications/diffusion/query/browse/base', 'DiffusionBrowseTableView' => 'applications/diffusion/view/browsetable', 'DiffusionChangeController' => 'applications/diffusion/controller/change', 'DiffusionCommitChangeTableView' => 'applications/diffusion/view/commitchangetable', 'DiffusionCommitController' => 'applications/diffusion/controller/commit', 'DiffusionController' => 'applications/diffusion/controller/base', 'DiffusionDiffController' => 'applications/diffusion/controller/diff', 'DiffusionDiffQuery' => 'applications/diffusion/query/diff/base', 'DiffusionFileContent' => 'applications/diffusion/data/filecontent', 'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/base', 'DiffusionGitBranchQuery' => 'applications/diffusion/query/branch/git', 'DiffusionGitBrowseQuery' => 'applications/diffusion/query/browse/git', 'DiffusionGitDiffQuery' => 'applications/diffusion/query/diff/git', 'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/git', 'DiffusionGitHistoryQuery' => 'applications/diffusion/query/history/git', 'DiffusionGitLastModifiedQuery' => 'applications/diffusion/query/lastmodified/git', 'DiffusionGitPathIDQuery' => 'applications/diffusion/query/pathid/base', 'DiffusionGitRequest' => 'applications/diffusion/request/git', 'DiffusionHistoryController' => 'applications/diffusion/controller/history', 'DiffusionHistoryQuery' => 'applications/diffusion/query/history/base', 'DiffusionHistoryTableView' => 'applications/diffusion/view/historytable', 'DiffusionHomeController' => 'applications/diffusion/controller/home', 'DiffusionLastModifiedController' => 'applications/diffusion/controller/lastmodified', 'DiffusionLastModifiedQuery' => 'applications/diffusion/query/lastmodified/base', 'DiffusionPathChange' => 'applications/diffusion/data/pathchange', 'DiffusionPathChangeQuery' => 'applications/diffusion/query/pathchange/base', 'DiffusionRepositoryController' => 'applications/diffusion/controller/repository', 'DiffusionRepositoryPath' => 'applications/diffusion/data/repositorypath', 'DiffusionRequest' => 'applications/diffusion/request/base', 'DiffusionSvnBrowseQuery' => 'applications/diffusion/query/browse/svn', 'DiffusionSvnDiffQuery' => 'applications/diffusion/query/diff/svn', 'DiffusionSvnFileContentQuery' => 'applications/diffusion/query/filecontent/svn', 'DiffusionSvnHistoryQuery' => 'applications/diffusion/query/history/svn', 'DiffusionSvnLastModifiedQuery' => 'applications/diffusion/query/lastmodified/svn', 'DiffusionSvnRequest' => 'applications/diffusion/request/svn', 'DiffusionView' => 'applications/diffusion/view/base', 'HeraldAction' => 'applications/herald/storage/action', 'HeraldActionConfig' => 'applications/herald/config/action', 'HeraldApplyTranscript' => 'applications/herald/storage/transcript/apply', 'HeraldCondition' => 'applications/herald/storage/condition', 'HeraldConditionConfig' => 'applications/herald/config/condition', 'HeraldConditionTranscript' => 'applications/herald/storage/transcript/condition', 'HeraldContentTypeConfig' => 'applications/herald/config/contenttype', 'HeraldController' => 'applications/herald/controller/base', 'HeraldDAO' => 'applications/herald/storage/base', 'HeraldDeleteController' => 'applications/herald/controller/delete', 'HeraldDifferentialRevisionAdapter' => 'applications/herald/adapter/differential', 'HeraldDryRunAdapter' => 'applications/herald/adapter/dryrun', 'HeraldEffect' => 'applications/herald/engine/effect', 'HeraldEngine' => 'applications/herald/engine/engine', 'HeraldFieldConfig' => 'applications/herald/config/field', 'HeraldHomeController' => 'applications/herald/controller/home', 'HeraldInvalidConditionException' => 'applications/herald/engine/engine/exception', 'HeraldInvalidFieldException' => 'applications/herald/engine/engine/exception', 'HeraldNewController' => 'applications/herald/controller/new', 'HeraldObjectAdapter' => 'applications/herald/adapter/base', 'HeraldObjectTranscript' => 'applications/herald/storage/transcript/object', 'HeraldRecursiveConditionsException' => 'applications/herald/engine/engine/exception', 'HeraldRule' => 'applications/herald/storage/rule', 'HeraldRuleController' => 'applications/herald/controller/rule', 'HeraldRuleTranscript' => 'applications/herald/storage/transcript/rule', 'HeraldTestConsoleController' => 'applications/herald/controller/test', 'HeraldTranscript' => 'applications/herald/storage/transcript/base', 'HeraldTranscriptController' => 'applications/herald/controller/transcript', 'HeraldTranscriptListController' => 'applications/herald/controller/transcriptlist', 'HeraldValueTypeConfig' => 'applications/herald/config/valuetype', 'Javelin' => 'infrastructure/javelin/api', 'LiskDAO' => 'storage/lisk/dao', 'ManiphestController' => 'applications/maniphest/controller/base', 'ManiphestDAO' => 'applications/maniphest/storage/base', 'ManiphestTask' => 'applications/maniphest/storage/task', 'ManiphestTaskDetailController' => 'applications/maniphest/controller/taskdetail', 'ManiphestTaskEditController' => 'applications/maniphest/controller/taskedit', 'ManiphestTaskListController' => 'applications/maniphest/controller/tasklist', 'ManiphestTaskListView' => 'applications/maniphest/view/tasklist', 'ManiphestTaskPriority' => 'applications/maniphest/constants/priority', 'ManiphestTaskSelectorSearchController' => 'applications/maniphest/controller/taskselectorsearch', 'ManiphestTaskStatus' => 'applications/maniphest/constants/status', 'ManiphestTaskSummaryView' => 'applications/maniphest/view/tasksummary', 'ManiphestTransaction' => 'applications/maniphest/storage/transaction', 'ManiphestTransactionDetailView' => 'applications/maniphest/view/transactiondetail', 'ManiphestTransactionEditor' => 'applications/maniphest/editor/transaction', 'ManiphestTransactionListView' => 'applications/maniphest/view/transactionlist', 'ManiphestTransactionSaveController' => 'applications/maniphest/controller/transactionsave', 'ManiphestTransactionType' => 'applications/maniphest/constants/transactiontype', 'Phabricator404Controller' => 'applications/base/controller/404', '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', 'PhabricatorDaemon' => 'infrastructure/daemon/base', 'PhabricatorDaemonConsoleController' => 'applications/daemon/controller/console', 'PhabricatorDaemonControl' => 'infrastructure/daemon/control', 'PhabricatorDaemonController' => 'applications/daemon/controller/base', 'PhabricatorDaemonDAO' => 'infrastructure/daemon/storage/base', 'PhabricatorDaemonLog' => 'infrastructure/daemon/storage/log', 'PhabricatorDaemonLogEvent' => 'infrastructure/daemon/storage/event', 'PhabricatorDaemonLogViewController' => 'applications/daemon/controller/logview', 'PhabricatorDaemonReference' => 'infrastructure/daemon/control/reference', 'PhabricatorDaemonTimelineConsoleController' => 'applications/daemon/controller/timeline', 'PhabricatorDaemonTimelineEventController' => 'applications/daemon/controller/timelineevent', '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', 'PhabricatorDraft' => 'applications/draft/storage/draft', 'PhabricatorDraftDAO' => 'applications/draft/storage/base', + 'PhabricatorEditPreferencesController' => 'applications/preferences/controller/edit', 'PhabricatorEmailLoginController' => 'applications/auth/controller/email', 'PhabricatorEmailTokenController' => 'applications/auth/controller/emailtoken', 'PhabricatorEnv' => 'infrastructure/env', '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', 'PhabricatorGoodForNothingWorker' => 'infrastructure/daemon/workers/worker/goodfornothing', 'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/selector', 'PhabricatorLiskDAO' => 'applications/base/storage/lisk', 'PhabricatorLoginController' => 'applications/auth/controller/login', 'PhabricatorLogoutController' => 'applications/auth/controller/logout', 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/base', 'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/amazonses', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/phpmailerlite', 'PhabricatorMetaMTAController' => 'applications/metamta/controller/base', 'PhabricatorMetaMTADAO' => 'applications/metamta/storage/base', 'PhabricatorMetaMTADaemon' => 'applications/metamta/daemon/mta', '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', 'PhabricatorOAuthDefaultRegistrationController' => 'applications/auth/controller/oauthregistration/default', 'PhabricatorOAuthDiagnosticsController' => 'applications/auth/controller/oauthdiagnostics', 'PhabricatorOAuthFailureView' => 'applications/auth/view/oauthfailure', 'PhabricatorOAuthLoginController' => 'applications/auth/controller/oauth', 'PhabricatorOAuthProvider' => 'applications/auth/oauth/provider/base', 'PhabricatorOAuthProviderFacebook' => 'applications/auth/oauth/provider/facebook', 'PhabricatorOAuthProviderGithub' => 'applications/auth/oauth/provider/github', 'PhabricatorOAuthRegistrationController' => 'applications/auth/controller/oauthregistration/base', 'PhabricatorOAuthUnlinkController' => 'applications/auth/controller/unlink', 'PhabricatorObjectHandle' => 'applications/phid/handle', 'PhabricatorObjectHandleData' => 'applications/phid/handle/data', 'PhabricatorObjectSelectorDialog' => 'view/control/objectselector', 'PhabricatorPHID' => 'applications/phid/storage/phid', 'PhabricatorPHIDAllocateController' => 'applications/phid/controller/allocate', 'PhabricatorPHIDConstants' => 'applications/phid/constants', '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', 'PhabricatorPeopleProfileEditController' => 'applications/people/controller/profileedit', + 'PhabricatorPreferencesController' => 'applications/preferences/controller/base', 'PhabricatorProject' => 'applications/project/storage/project', 'PhabricatorProjectAffiliation' => 'applications/project/storage/affiliation', 'PhabricatorProjectAffiliationEditController' => 'applications/project/controller/editaffiliation', 'PhabricatorProjectController' => 'applications/project/controller/base', 'PhabricatorProjectDAO' => 'applications/project/storage/base', 'PhabricatorProjectEditController' => 'applications/project/controller/edit', 'PhabricatorProjectListController' => 'applications/project/controller/list', 'PhabricatorProjectProfile' => 'applications/project/storage/profile', 'PhabricatorProjectProfileController' => 'applications/project/controller/profile', 'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential', 'PhabricatorRemarkupRuleManiphest' => 'infrastructure/markup/remarkup/markuprule/maniphest', 'PhabricatorRepository' => 'applications/repository/storage/repository', 'PhabricatorRepositoryCommit' => 'applications/repository/storage/commit', 'PhabricatorRepositoryCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/base', 'PhabricatorRepositoryCommitData' => 'applications/repository/storage/commitdata', 'PhabricatorRepositoryCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/base', 'PhabricatorRepositoryCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/base', 'PhabricatorRepositoryCommitParserWorker' => 'applications/repository/worker/base', 'PhabricatorRepositoryCommitTaskDaemon' => 'applications/repository/daemon/committask', 'PhabricatorRepositoryController' => 'applications/repository/controller/base', 'PhabricatorRepositoryCreateController' => 'applications/repository/controller/create', 'PhabricatorRepositoryDAO' => 'applications/repository/storage/base', 'PhabricatorRepositoryDaemon' => 'applications/repository/daemon/base', 'PhabricatorRepositoryEditController' => 'applications/repository/controller/edit', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/git', 'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/git', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/git', 'PhabricatorRepositoryGitFetchDaemon' => 'applications/repository/daemon/gitfetch', 'PhabricatorRepositoryGitHubNotification' => 'applications/repository/storage/githubnotification', 'PhabricatorRepositoryGitHubPostReceiveController' => 'applications/repository/controller/github-post-receive', 'PhabricatorRepositoryListController' => 'applications/repository/controller/list', 'PhabricatorRepositoryShortcut' => 'applications/repository/storage/shortcut', 'PhabricatorRepositorySvnCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/svn', 'PhabricatorRepositorySvnCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/svn', 'PhabricatorRepositorySvnCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/svn', 'PhabricatorRepositoryType' => 'applications/repository/constants/repositorytype', 'PhabricatorSearchAbstractDocument' => 'applications/search/index/abstractdocument', 'PhabricatorSearchBaseController' => 'applications/search/controller/base', 'PhabricatorSearchController' => 'applications/search/controller/search', 'PhabricatorSearchDAO' => 'applications/search/storage/base', 'PhabricatorSearchDifferentialIndexer' => 'applications/search/index/indexer/differential', 'PhabricatorSearchDocument' => 'applications/search/storage/document/document', 'PhabricatorSearchDocumentField' => 'applications/search/storage/document/field', 'PhabricatorSearchDocumentIndexer' => 'applications/search/index/indexer/base', 'PhabricatorSearchDocumentRelationship' => 'applications/search/storage/document/relationship', 'PhabricatorSearchExecutor' => 'applications/search/execute/base', 'PhabricatorSearchField' => 'applications/search/constants/field', 'PhabricatorSearchManiphestIndexer' => 'applications/search/index/indexer/maniphest', 'PhabricatorSearchMySQLExecutor' => 'applications/search/execute/mysql', 'PhabricatorSearchQuery' => 'applications/search/storage/query', 'PhabricatorSearchRelationship' => 'applications/search/constants/relationship', 'PhabricatorStandardPageView' => 'view/page/standard', 'PhabricatorTaskmasterDaemon' => 'infrastructure/daemon/workers/taskmaster', 'PhabricatorTimelineCursor' => 'infrastructure/daemon/timeline/storage/cursor', 'PhabricatorTimelineDAO' => 'infrastructure/daemon/timeline/storage/base', 'PhabricatorTimelineEvent' => 'infrastructure/daemon/timeline/storage/event', 'PhabricatorTimelineEventData' => 'infrastructure/daemon/timeline/storage/eventdata', 'PhabricatorTimelineIterator' => 'infrastructure/daemon/timeline/cursor/iterator', 'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/common', 'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base', 'PhabricatorUser' => 'applications/people/storage/user', 'PhabricatorUserDAO' => 'applications/people/storage/base', 'PhabricatorUserOAuthInfo' => 'applications/people/storage/useroauthinfo', + 'PhabricatorUserPreferences' => 'applications/people/storage/preferences', 'PhabricatorUserProfile' => 'applications/people/storage/profile', 'PhabricatorUserSettingsController' => 'applications/people/controller/settings', 'PhabricatorWorker' => 'infrastructure/daemon/workers/worker', 'PhabricatorWorkerDAO' => 'infrastructure/daemon/workers/storage/base', 'PhabricatorWorkerTask' => 'infrastructure/daemon/workers/storage/task', 'PhabricatorWorkerTaskData' => 'infrastructure/daemon/workers/storage/taskdata', 'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/workertaskdetail', 'PhabricatorXHProfController' => 'applications/xhprof/controller/base', 'PhabricatorXHProfProfileController' => 'applications/xhprof/controller/profile', 'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/symbol', 'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/toplevel', ), 'function' => array( '_qsprintf_check_scalar_type' => 'storage/qsprintf', '_qsprintf_check_type' => 'storage/qsprintf', 'celerity_generate_unique_node_id' => 'infrastructure/celerity/api', 'celerity_register_resource_map' => 'infrastructure/celerity/map', 'javelin_render_tag' => 'infrastructure/javelin/markup', 'phabricator_format_relative_time' => 'view/utils', 'phabricator_format_timestamp' => 'view/utils', 'phabricator_format_units_generic' => 'view/utils', 'phabricator_render_form' => 'infrastructure/javelin/markup', 'qsprintf' => 'storage/qsprintf', 'queryfx' => 'storage/queryfx', 'queryfx_all' => 'storage/queryfx', 'queryfx_one' => 'storage/queryfx', 'require_celerity_resource' => 'infrastructure/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', 'AphrontCrumbsView' => 'AphrontView', 'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration', 'AphrontDefaultApplicationController' => 'AphrontController', 'AphrontDialogResponse' => 'AphrontResponse', 'AphrontDialogView' => 'AphrontView', 'AphrontErrorView' => 'AphrontView', 'AphrontFileResponse' => 'AphrontResponse', 'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormControl' => 'AphrontView', 'AphrontFormDividerControl' => 'AphrontFormControl', 'AphrontFormFileControl' => 'AphrontFormControl', 'AphrontFormMarkupControl' => 'AphrontFormControl', 'AphrontFormPasswordControl' => 'AphrontFormControl', 'AphrontFormRecaptchaControl' => 'AphrontFormControl', 'AphrontFormSelectControl' => 'AphrontFormControl', 'AphrontFormStaticControl' => 'AphrontFormControl', 'AphrontFormSubmitControl' => 'AphrontFormControl', 'AphrontFormTextAreaControl' => 'AphrontFormControl', 'AphrontFormTextControl' => 'AphrontFormControl', 'AphrontFormTokenizerControl' => 'AphrontFormControl', 'AphrontFormView' => 'AphrontView', 'AphrontHeadsupActionListView' => 'AphrontView', 'AphrontHeadsupActionView' => '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', 'AphrontTokenizerTemplateView' => 'AphrontView', 'AphrontWebpageResponse' => 'AphrontResponse', 'CelerityResourceController' => 'AphrontController', 'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod', 'ConduitAPI_conduit_ping_Method' => 'ConduitAPIMethod', 'ConduitAPI_daemon_launched_Method' => 'ConduitAPIMethod', 'ConduitAPI_daemon_log_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_createrevision_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_find_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getcommitmessage_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getcommitpaths_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getdiff_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_markcommitted_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_parsecommitmessage_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_updaterevision_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod', 'ConduitAPI_user_find_Method' => 'ConduitAPIMethod', 'DarkConsoleConfigPlugin' => 'DarkConsolePlugin', 'DarkConsoleController' => 'PhabricatorController', 'DarkConsoleErrorLogPlugin' => 'DarkConsolePlugin', 'DarkConsoleRequestPlugin' => 'DarkConsolePlugin', 'DarkConsoleServicesPlugin' => 'DarkConsolePlugin', 'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin', 'DifferentialAddCommentView' => 'AphrontView', 'DifferentialAttachController' => 'DifferentialController', 'DifferentialCCWelcomeMail' => 'DifferentialReviewRequestMail', 'DifferentialChangeset' => 'DifferentialDAO', 'DifferentialChangesetDetailView' => 'AphrontView', 'DifferentialChangesetListView' => 'AphrontView', 'DifferentialChangesetViewController' => 'DifferentialController', 'DifferentialComment' => 'DifferentialDAO', 'DifferentialCommentMail' => 'DifferentialMail', 'DifferentialCommentPreviewController' => 'DifferentialController', 'DifferentialCommentSaveController' => 'DifferentialController', 'DifferentialController' => 'PhabricatorController', 'DifferentialDAO' => 'PhabricatorLiskDAO', 'DifferentialDiff' => 'DifferentialDAO', 'DifferentialDiffContentMail' => 'DifferentialMail', 'DifferentialDiffCreateController' => 'DifferentialController', 'DifferentialDiffProperty' => 'DifferentialDAO', 'DifferentialDiffTableOfContentsView' => 'AphrontView', 'DifferentialDiffViewController' => 'DifferentialController', 'DifferentialHunk' => 'DifferentialDAO', 'DifferentialInlineComment' => 'DifferentialDAO', 'DifferentialInlineCommentEditController' => 'DifferentialController', 'DifferentialInlineCommentPreviewController' => 'DifferentialController', 'DifferentialInlineCommentView' => 'AphrontView', 'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail', 'DifferentialReviewRequestMail' => 'DifferentialMail', 'DifferentialRevision' => 'DifferentialDAO', 'DifferentialRevisionCommentListView' => 'AphrontView', 'DifferentialRevisionCommentView' => 'AphrontView', 'DifferentialRevisionDetailView' => 'AphrontView', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionUpdateHistoryView' => 'AphrontView', 'DifferentialRevisionViewController' => 'DifferentialController', 'DifferentialSubscribeController' => 'DifferentialController', 'DiffusionBranchTableView' => 'DiffusionView', 'DiffusionBrowseController' => 'DiffusionController', 'DiffusionBrowseFileController' => 'DiffusionController', 'DiffusionBrowseTableView' => 'DiffusionView', 'DiffusionChangeController' => 'DiffusionController', 'DiffusionCommitChangeTableView' => 'DiffusionView', 'DiffusionCommitController' => 'DiffusionController', 'DiffusionController' => 'PhabricatorController', 'DiffusionDiffController' => 'DiffusionController', 'DiffusionGitBranchQuery' => 'DiffusionBranchQuery', 'DiffusionGitBrowseQuery' => 'DiffusionBrowseQuery', 'DiffusionGitDiffQuery' => 'DiffusionDiffQuery', 'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionGitHistoryQuery' => 'DiffusionHistoryQuery', 'DiffusionGitLastModifiedQuery' => 'DiffusionLastModifiedQuery', 'DiffusionGitRequest' => 'DiffusionRequest', 'DiffusionHistoryController' => 'DiffusionController', 'DiffusionHistoryTableView' => 'DiffusionView', 'DiffusionHomeController' => 'DiffusionController', 'DiffusionLastModifiedController' => 'DiffusionController', 'DiffusionRepositoryController' => 'DiffusionController', 'DiffusionSvnBrowseQuery' => 'DiffusionBrowseQuery', 'DiffusionSvnDiffQuery' => 'DiffusionDiffQuery', 'DiffusionSvnFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionSvnHistoryQuery' => 'DiffusionHistoryQuery', 'DiffusionSvnLastModifiedQuery' => 'DiffusionLastModifiedQuery', 'DiffusionSvnRequest' => 'DiffusionRequest', 'DiffusionView' => 'AphrontView', 'HeraldAction' => 'HeraldDAO', 'HeraldApplyTranscript' => 'HeraldDAO', 'HeraldCondition' => 'HeraldDAO', 'HeraldController' => 'PhabricatorController', 'HeraldDAO' => 'PhabricatorLiskDAO', 'HeraldDeleteController' => 'HeraldController', 'HeraldDifferentialRevisionAdapter' => 'HeraldObjectAdapter', 'HeraldDryRunAdapter' => 'HeraldObjectAdapter', 'HeraldHomeController' => 'HeraldController', 'HeraldNewController' => 'HeraldController', 'HeraldRule' => 'HeraldDAO', 'HeraldRuleController' => 'HeraldController', 'HeraldTestConsoleController' => 'HeraldController', 'HeraldTranscript' => 'HeraldDAO', 'HeraldTranscriptController' => 'HeraldController', 'HeraldTranscriptListController' => 'HeraldController', 'ManiphestController' => 'PhabricatorController', 'ManiphestDAO' => 'PhabricatorLiskDAO', 'ManiphestTask' => 'ManiphestDAO', 'ManiphestTaskDetailController' => 'ManiphestController', 'ManiphestTaskEditController' => 'ManiphestController', 'ManiphestTaskListController' => 'ManiphestController', 'ManiphestTaskListView' => 'AphrontView', 'ManiphestTaskSelectorSearchController' => 'ManiphestController', 'ManiphestTaskSummaryView' => 'AphrontView', 'ManiphestTransaction' => 'ManiphestDAO', 'ManiphestTransactionDetailView' => 'AphrontView', 'ManiphestTransactionListView' => 'AphrontView', 'ManiphestTransactionSaveController' => 'ManiphestController', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAuthController' => 'PhabricatorController', 'PhabricatorConduitAPIController' => 'PhabricatorConduitController', 'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitConsoleController' => 'PhabricatorConduitController', 'PhabricatorConduitController' => 'PhabricatorController', 'PhabricatorConduitDAO' => 'PhabricatorLiskDAO', 'PhabricatorConduitLogController' => 'PhabricatorConduitController', 'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO', 'PhabricatorController' => 'AphrontController', 'PhabricatorDaemon' => 'PhutilDaemon', 'PhabricatorDaemonConsoleController' => 'PhabricatorDaemonController', 'PhabricatorDaemonController' => 'PhabricatorController', 'PhabricatorDaemonDAO' => 'PhabricatorLiskDAO', 'PhabricatorDaemonLog' => 'PhabricatorDaemonDAO', 'PhabricatorDaemonLogEvent' => 'PhabricatorDaemonDAO', 'PhabricatorDaemonLogViewController' => 'PhabricatorDaemonController', 'PhabricatorDaemonTimelineConsoleController' => 'PhabricatorDaemonController', 'PhabricatorDaemonTimelineEventController' => 'PhabricatorDaemonController', 'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryController' => 'PhabricatorController', 'PhabricatorDirectoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorDirectoryItem' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryItemDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController', 'PhabricatorDraft' => 'PhabricatorDraftDAO', 'PhabricatorDraftDAO' => 'PhabricatorLiskDAO', + 'PhabricatorEditPreferencesController' => 'PhabricatorPreferencesController', 'PhabricatorEmailLoginController' => 'PhabricatorAuthController', 'PhabricatorEmailTokenController' => 'PhabricatorAuthController', 'PhabricatorFile' => 'PhabricatorFileDAO', 'PhabricatorFileController' => 'PhabricatorController', 'PhabricatorFileDAO' => 'PhabricatorLiskDAO', 'PhabricatorFileListController' => 'PhabricatorFileController', 'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO', 'PhabricatorFileUploadController' => 'PhabricatorFileController', 'PhabricatorFileViewController' => 'PhabricatorFileController', 'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker', 'PhabricatorLiskDAO' => 'LiskDAO', 'PhabricatorLoginController' => 'PhabricatorAuthController', 'PhabricatorLogoutController' => 'PhabricatorAuthController', 'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMetaMTAController' => 'PhabricatorController', 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO', 'PhabricatorMetaMTADaemon' => 'PhabricatorDaemon', 'PhabricatorMetaMTAListController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingList' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingListEditController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMailingListsController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTASendController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController', 'PhabricatorOAuthDefaultRegistrationController' => 'PhabricatorOAuthRegistrationController', 'PhabricatorOAuthDiagnosticsController' => 'PhabricatorAuthController', 'PhabricatorOAuthFailureView' => 'AphrontView', 'PhabricatorOAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorOAuthProviderFacebook' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthProviderGithub' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthRegistrationController' => 'PhabricatorAuthController', 'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController', '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', 'PhabricatorPeopleProfileEditController' => 'PhabricatorPeopleController', + 'PhabricatorPreferencesController' => 'PhabricatorController', 'PhabricatorProject' => 'PhabricatorProjectDAO', 'PhabricatorProjectAffiliation' => 'PhabricatorProjectDAO', 'PhabricatorProjectAffiliationEditController' => 'PhabricatorProjectController', 'PhabricatorProjectController' => 'PhabricatorController', 'PhabricatorProjectDAO' => 'PhabricatorLiskDAO', 'PhabricatorProjectEditController' => 'PhabricatorProjectController', 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectProfile' => 'PhabricatorProjectDAO', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController', 'PhabricatorRemarkupRuleDifferential' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleManiphest' => 'PhutilRemarkupRule', 'PhabricatorRepository' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryCommit' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryCommitChangeParserWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitData' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryCommitDiscoveryDaemon' => 'PhabricatorRepositoryDaemon', 'PhabricatorRepositoryCommitMessageParserWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitParserWorker' => 'PhabricatorWorker', 'PhabricatorRepositoryCommitTaskDaemon' => 'PhabricatorRepositoryDaemon', 'PhabricatorRepositoryController' => 'PhabricatorController', 'PhabricatorRepositoryCreateController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorRepositoryDaemon' => 'PhabricatorDaemon', 'PhabricatorRepositoryEditController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorRepositoryGitFetchDaemon' => 'PhabricatorRepositoryDaemon', 'PhabricatorRepositoryGitHubNotification' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryGitHubPostReceiveController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryListController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryShortcut' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositorySvnCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositorySvnCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon', 'PhabricatorRepositorySvnCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorSearchBaseController' => 'PhabricatorController', 'PhabricatorSearchController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO', 'PhabricatorSearchDifferentialIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO', 'PhabricatorSearchManiphestIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchMySQLExecutor' => 'PhabricatorSearchExecutor', 'PhabricatorSearchQuery' => 'PhabricatorSearchDAO', 'PhabricatorStandardPageView' => 'AphrontPageView', 'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon', 'PhabricatorTimelineCursor' => 'PhabricatorTimelineDAO', 'PhabricatorTimelineDAO' => 'PhabricatorLiskDAO', 'PhabricatorTimelineEvent' => 'PhabricatorTimelineDAO', 'PhabricatorTimelineEventData' => 'PhabricatorTimelineDAO', 'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController', 'PhabricatorUser' => 'PhabricatorUserDAO', 'PhabricatorUserDAO' => 'PhabricatorLiskDAO', 'PhabricatorUserOAuthInfo' => 'PhabricatorUserDAO', + 'PhabricatorUserPreferences' => 'PhabricatorUserDAO', 'PhabricatorUserProfile' => 'PhabricatorUserDAO', 'PhabricatorUserSettingsController' => 'PhabricatorPeopleController', 'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO', 'PhabricatorWorkerTask' => 'PhabricatorWorkerDAO', 'PhabricatorWorkerTaskData' => 'PhabricatorWorkerDAO', 'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController', 'PhabricatorXHProfController' => 'PhabricatorController', 'PhabricatorXHProfProfileController' => 'PhabricatorXHProfController', 'PhabricatorXHProfProfileSymbolView' => 'AphrontView', 'PhabricatorXHProfProfileTopLevelView' => 'AphrontView', ), 'requires_interface' => array( ), )); diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index 0ddfdcf971..aed566c439 100755 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -1,350 +1,353 @@ getResourceURIMapRules() + array( '/' => array( '$' => 'PhabricatorDirectoryMainController', ), '/directory/' => array( 'item/$' => 'PhabricatorDirectoryItemListController', 'item/edit/(?:(?P\d+)/)?$' => 'PhabricatorDirectoryItemEditController', 'item/delete/(?P\d+)/' => 'PhabricatorDirectoryItemDeleteController', 'category/$' => 'PhabricatorDirectoryCategoryListController', 'category/edit/(?:(?P\d+)/)?$' => 'PhabricatorDirectoryCategoryEditController', 'category/delete/(?P\d+)/' => 'PhabricatorDirectoryCategoryDeleteController', ), '/file/' => array( '$' => 'PhabricatorFileListController', 'upload/$' => 'PhabricatorFileUploadController', '(?Pinfo)/(?P[^/]+)/' => 'PhabricatorFileViewController', '(?Pview)/(?P[^/]+)/' => 'PhabricatorFileViewController', '(?Pdownload)/(?P[^/]+)/' => 'PhabricatorFileViewController', ), '/phid/' => array( '$' => 'PhabricatorPHIDLookupController', 'list/$' => 'PhabricatorPHIDListController', 'type/$' => 'PhabricatorPHIDTypeListController', 'type/edit/(?:(?P\d+)/)?$' => 'PhabricatorPHIDTypeEditController', 'new/$' => 'PhabricatorPHIDAllocateController', ), '/people/' => array( '$' => 'PhabricatorPeopleListController', 'edit/(?:(?P\w+)/)?$' => 'PhabricatorPeopleEditController', ), '/p/(?P\w+)/$' => 'PhabricatorPeopleProfileController', '/profile/' => array( 'edit/$' => 'PhabricatorPeopleProfileEditController', ), '/conduit/' => array( '$' => 'PhabricatorConduitConsoleController', 'method/(?P[^/]+)$' => 'PhabricatorConduitConsoleController', 'log/$' => 'PhabricatorConduitLogController', ), '/api/(?P[^/]+)$' => 'PhabricatorConduitAPIController', '/D(?P\d+)' => 'DifferentialRevisionViewController', '/differential/' => array( '$' => 'DifferentialRevisionListController', 'filter/(?P\w+)/$' => 'DifferentialRevisionListController', 'diff/' => array( '(?P\d+)/$' => 'DifferentialDiffViewController', 'create/$' => 'DifferentialDiffCreateController', ), 'changeset/$' => 'DifferentialChangesetViewController', 'revision/edit/(?:(?P\d+)/)?$' => 'DifferentialRevisionEditController', 'comment/' => array( 'preview/(?P\d+)/$' => 'DifferentialCommentPreviewController', 'save/$' => 'DifferentialCommentSaveController', 'inline/' => array( 'preview/(?P\d+)/$' => 'DifferentialInlineCommentPreviewController', 'edit/(?P\d+)/$' => 'DifferentialInlineCommentEditController', ), ), 'attach/(?P\d+)/(?P\w+)/$' => 'DifferentialAttachController', 'subscribe/(?Padd|rem)/(?P\d+)/$' => 'DifferentialSubscribeController', ), '/typeahead/' => array( 'common/(?P\w+)/$' => 'PhabricatorTypeaheadCommonDatasourceController', ), '/mail/' => array( '$' => 'PhabricatorMetaMTAListController', 'send/$' => 'PhabricatorMetaMTASendController', 'view/(?P\d+)/$' => 'PhabricatorMetaMTAViewController', 'lists/$' => 'PhabricatorMetaMTAMailingListsController', 'lists/edit/(?:(?P\d+)/)?$' => 'PhabricatorMetaMTAMailingListEditController', ), '/login/' => array( '$' => 'PhabricatorLoginController', 'email/$' => 'PhabricatorEmailLoginController', 'etoken/(?P\w+)/$' => 'PhabricatorEmailTokenController', ), '/logout/$' => 'PhabricatorLogoutController', '/oauth/' => array( '(?Pgithub|facebook)/' => array( 'login/$' => 'PhabricatorOAuthLoginController', 'diagnose/$' => 'PhabricatorOAuthDiagnosticsController', 'unlink/$' => 'PhabricatorOAuthUnlinkController', ), ), '/xhprof/' => array( 'profile/(?P[^/]+)/$' => 'PhabricatorXHProfProfileController', ), '/~/' => 'DarkConsoleController', '/settings/' => array( '(?:page/(?P[^/]+)/)?$' => 'PhabricatorUserSettingsController', ), '/maniphest/' => array( '$' => 'ManiphestTaskListController', 'view/(?P\w+)/$' => 'ManiphestTaskListController', 'task/' => array( 'create/$' => 'ManiphestTaskEditController', 'edit/(?P\d+)/$' => 'ManiphestTaskEditController', ), 'transaction/' => array( 'save/' => 'ManiphestTransactionSaveController', ), 'select/search/$' => 'ManiphestTaskSelectorSearchController', ), '/T(?P\d+)$' => 'ManiphestTaskDetailController', '/github-post-receive/(?P\d+)/(?P[^/]+)/$' => 'PhabricatorRepositoryGitHubPostReceiveController', '/repository/' => array( '$' => 'PhabricatorRepositoryListController', 'create/$' => 'PhabricatorRepositoryCreateController', 'edit/(?P\d+)/(?:(?P\w+)?/)?$' => 'PhabricatorRepositoryEditController', 'delete/(?P\d+)/$' => 'PhabricatorRepositoryDeleteController', ), '/search/' => array( '$' => 'PhabricatorSearchController', '(?P\d+)/$' => 'PhabricatorSearchController', ), '/project/' => array( '$' => 'PhabricatorProjectListController', 'edit/(?:(?P\d+)/)?$' => 'PhabricatorProjectEditController', 'view/(?P\d+)/$' => 'PhabricatorProjectProfileController', 'affiliation/(?P\d+)/$' => 'PhabricatorProjectAffiliationEditController', ), '/r(?P[A-Z]+)(?P[a-z0-9]+)$' => 'DiffusionCommitController', '/diffusion/' => array( '$' => 'DiffusionHomeController', '(?P[A-Z]+)/' => array( '$' => 'DiffusionRepositoryController', 'repository/'. '(?P[^/]+)/'. '$' => 'DiffusionRepositoryController', 'change/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?'. '$' => 'DiffusionChangeController', 'history/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?'. '$' => 'DiffusionHistoryController', 'browse/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?'. '(?:[$](?P\d+))?'. '$' => 'DiffusionBrowseController', 'diff/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?'. '$' => 'DiffusionDiffController', 'lastmodified/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?'. '$' => 'DiffusionLastModifiedController', ), ), '/daemon/' => array( 'task/(?P\d+)/$' => 'PhabricatorWorkerTaskDetailController', 'log/' => array( '(?P\d+)/$' => 'PhabricatorDaemonLogViewController', ), 'timeline/$' => 'PhabricatorDaemonTimelineConsoleController', 'timeline/(?P\d+)/$' => 'PhabricatorDaemonTimelineEventController', '$' => 'PhabricatorDaemonConsoleController', ), '/herald/' => array( '$' => 'HeraldHomeController', 'view/(?P[^/]+)/$' => 'HeraldHomeController', 'new/(?:(?P[^/]+)/)?$' => 'HeraldNewController', 'rule/(?:(?\d+)/)?$' => 'HeraldRuleController', 'delete/(?P\d+)/$' => 'HeraldDeleteController', 'test/$' => 'HeraldTestConsoleController', 'transcript/$' => 'HeraldTranscriptListController', 'transcript/(?P\d+)/(?:(?P\w+)/)?$' => 'HeraldTranscriptController', ), + '/preferences/' => array( + '$' => 'PhabricatorEditPreferencesController' + ), ); } protected function getResourceURIMapRules() { return array( '/res/' => array( '(?Ppkg/)?(?P[a-f0-9]{8})/(?P.+\.(?:css|js))$' => 'CelerityResourceController', ), ); } public function buildRequest() { $request = new AphrontRequest($this->getHost(), $this->getPath()); $request->setRequestData($_GET + $_POST); $request->setApplicationConfiguration($this); 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).''. '
'; $user = $this->getRequest()->getUser(); if (!$user) { // If we hit an exception very early, we won't have a user. $user = new PhabricatorUser(); } $dialog = new AphrontDialogView(); $dialog ->setTitle('Exception!') ->setClass('aphront-exception-dialog') ->setUser($user) ->appendChild($content) ->addCancelButton('/'); $response = new AphrontDialogResponse(); $response->setDialog($dialog); return $response; } public function willSendResponse(AphrontResponse $response) { $request = $this->getRequest(); if ($response instanceof AphrontDialogResponse) { if (!$request->isAjax()) { $view = new PhabricatorStandardPageView(); $view->setRequest($request); $view->appendChild( '
'. $response->buildResponseString(). '
'); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } else { 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(), )); } } 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->setRequest($this->getRequest()); $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/differential/parser/changeset/DifferentialChangesetParser.php b/src/applications/differential/parser/changeset/DifferentialChangesetParser.php index 58ba4810e4..121347568f 100644 --- a/src/applications/differential/parser/changeset/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/changeset/DifferentialChangesetParser.php @@ -1,1347 +1,1347 @@ changeset = $changeset; $this->setFilename($changeset->getFilename()); $this->setChangesetID($changeset->getID()); return $this; } public function setWhitespaceMode($whitespace_mode) { $this->whitespaceMode = $whitespace_mode; return $this; } public function setOldChangesetID($old_changeset_id) { $this->oldChangesetID = $old_changeset_id; return $this; } public function setChangesetID($changeset_id) { $this->changesetID = $changeset_id; return $this; } public function getChangeset() { return $this->changeset; } public function getChangesetID() { return $this->changesetID; } public function setFilename($filename) { $this->filename = $filename; if (strpos($filename, '.', 1) !== false) { $parts = explode('.', $filename); $this->filetype = end($parts); } } public function setHandles(array $handles) { $this->handles = $handles; return $this; } public function setMarkupEngine(PhutilMarkupEngine $engine) { $this->markupEngine = $engine; return $this; } public function setUser(PhabricatorUser $user) { $this->user = $user; return $this; } public function parseHunk(DifferentialHunk $hunk) { $this->parsedHunk = true; $lines = $hunk->getChanges(); // Flatten UTF-8 into "\0". We don't support UTF-8 because the diffing // algorithms are byte-oriented (not character oriented) and everyone seems // to be in agreement that it's fairly reasonable not to allow UTF-8 in // source files. These bytes will later be replaced with a "?" glyph, but // in the meantime we replace them with "\0" since Pygments is happy to // deal with that. $lines = preg_replace('/[\x80-\xFF]/', "\0", $lines); $lines = str_replace( array("\t", "\r\n", "\r"), array(' ', "\n", "\n"), $lines); $lines = explode("\n", $lines); $types = array(); foreach ($lines as $line_index => $line) { $lines[$line_index] = $line; if (isset($line[0])) { $char = $line[0]; if ($char == ' ') { $types[$line_index] = null; } else if ($char == '\\' && $line_index > 0) { $types[$line_index] = $types[$line_index - 1]; } else { $types[$line_index] = $char; } } else { $types[$line_index] = null; } } $old_line = $hunk->getOldOffset(); $new_line = $hunk->getNewOffset(); $num_lines = count($lines); if ($old_line > 1) { $this->missingOld[$old_line] = true; } else if ($new_line > 1) { $this->missingNew[$new_line] = true; } for ($cursor = 0; $cursor < $num_lines; $cursor++) { $type = $types[$cursor]; $data = array( 'type' => $type, 'text' => (string)substr($lines[$cursor], 1), 'line' => $new_line, ); switch ($type) { case '+': $this->new[] = $data; ++$new_line; break; case '-': $data['line'] = $old_line; $this->old[] = $data; ++$old_line; break; default: $this->new[] = $data; $data['line'] = $old_line; $this->old[] = $data; ++$new_line; ++$old_line; break; } } } public function getDisplayLine($offset, $length) { $start = 1; for ($ii = $offset; $ii > 0; $ii--) { if ($this->new[$ii] && $this->new[$ii]['line']) { $start = $this->new[$ii]['line']; break; } } $end = $start; for ($ii = $offset + $length; $ii < count($this->new); $ii++) { if ($this->new[$ii] && $this->new[$ii]['line']) { $end = $this->new[$ii]['line']; break; } } return "{$start},{$end}"; } public function parseInlineComment(DifferentialInlineComment $comment) { $this->comments[] = $comment; return $this; } public function process() { $old = array(); $new = array(); $n = 0; $this->old = array_reverse($this->old); $this->new = array_reverse($this->new); $whitelines = false; $changed = false; $skip_intra = array(); while (count($this->old) || count($this->new)) { $o_desc = array_pop($this->old); $n_desc = array_pop($this->new); $oend = end($this->old); if ($oend) { $o_next = $oend['type']; } else { $o_next = null; } $nend = end($this->new); if ($nend) { $n_next = $nend['type']; } else { $n_next = null; } if ($o_desc) { $o_type = $o_desc['type']; } else { $o_type = null; } if ($n_desc) { $n_type = $n_desc['type']; } else { $n_type = null; } if (($o_type != null) && ($n_type == null)) { $old[] = $o_desc; $new[] = null; if ($n_desc) { array_push($this->new, $n_desc); } $changed = true; continue; } if (($n_type != null) && ($o_type == null)) { $old[] = null; $new[] = $n_desc; if ($o_desc) { array_push($this->old, $o_desc); } $changed = true; continue; } if ($this->whitespaceMode != self::WHITESPACE_SHOW_ALL) { $similar = false; switch ($this->whitespaceMode) { case self::WHITESPACE_IGNORE_TRAILING: if (rtrim($o_desc['text']) == rtrim($n_desc['text'])) { $similar = true; } break; } if ($similar) { $o_desc['type'] = null; $n_desc['type'] = null; $skip_intra[count($old)] = true; $whitelines = true; } else { $changed = true; } } else { $changed = true; } $old[] = $o_desc; $new[] = $n_desc; } $this->old = $old; $this->new = $new; if ($this->subparser && false) { // TODO: This is bugged // Use the subparser's side-by-side line information -- notably, the // change types -- but replace all the line text with ours. This lets us // render whitespace-only changes without marking them as different. $old = $this->subparser->old; $new = $this->subparser->new; $old_text = ipull($this->old, 'text', 'line'); $new_text = ipull($this->new, 'text', 'line'); foreach ($old as $k => $desc) { if (empty($desc)) { continue; } $old[$k]['text'] = idx($old_text, $desc['line']); } foreach ($new as $k => $desc) { if (empty($desc)) { continue; } $new[$k]['text'] = idx($new_text, $desc['line']); } $this->old = $old; $this->new = $new; } $min_length = min(count($this->old), count($this->new)); for ($ii = 0; $ii < $min_length; $ii++) { if ($this->old[$ii] || $this->new[$ii]) { if (isset($this->old[$ii]['text'])) { $otext = $this->old[$ii]['text']; } else { $otext = ''; } if (isset($this->new[$ii]['text'])) { $ntext = $this->new[$ii]['text']; } else { $ntext = ''; } if ($otext != $ntext && empty($skip_intra[$ii])) { $this->intra[$ii] = ArcanistDiffUtils::generateIntralineDiff( $otext, $ntext); } } } $lines_context = self::LINES_CONTEXT; $max_length = max(count($this->old), count($this->new)); $old = $this->old; $new = $this->new; $visible = false; $last = 0; for ($cursor = -$lines_context; $cursor < $max_length; $cursor++) { $offset = $cursor + $lines_context; if ((isset($old[$offset]) && $old[$offset]['type']) || (isset($new[$offset]) && $new[$offset]['type'])) { $visible = true; $last = $offset; } else if ($cursor > $last + $lines_context) { $visible = false; } if ($visible && $cursor > 0) { $this->visible[$cursor] = 1; } } $old_corpus = ipull($this->old, 'text'); $old_corpus_block = implode("\n", $old_corpus); $new_corpus = ipull($this->new, 'text'); $new_corpus_block = implode("\n", $new_corpus); if ($this->noHighlight) { $this->oldRender = explode("\n", phutil_escape_html($old_corpus_block)); $this->newRender = explode("\n", phutil_escape_html($new_corpus_block)); } else { $this->oldRender = $this->sourceHighlight($this->old, $old_corpus_block); $this->newRender = $this->sourceHighlight($this->new, $new_corpus_block); } $this->applyIntraline( $this->oldRender, ipull($this->intra, 0), $old_corpus); $this->applyIntraline( $this->newRender, ipull($this->intra, 1), $new_corpus); $this->tokenHighlight($this->oldRender); $this->tokenHighlight($this->newRender); $unchanged = false; if ($this->subparser && false) { $unchanged = $this->subparser->isUnchanged(); $whitelines = $this->subparser->isWhitespaceOnly(); } else if (!$changed) { $filetype = $this->changeset->getFileType(); if ($filetype == DifferentialChangeType::FILE_TEXT || $filetype == DifferentialChangeType::FILE_SYMLINK) { $unchanged = true; } } $generated = (strpos($new_corpus_block, '@'.'generated') !== false); $this->specialAttributes = array( self::ATTR_GENERATED => $generated, self::ATTR_UNCHANGED => $unchanged, self::ATTR_DELETED => array_filter($this->old) && !array_filter($this->new), self::ATTR_WHITELINES => $whitelines ); } public function loadCache() { if (!$this->changesetID) { return false; } $data = null; $changeset = new DifferentialChangeset(); $conn_r = $changeset->establishConnection('r'); $data = queryfx_one( $conn_r, 'SELECT * FROM %T WHERE id = %d', $changeset->getTableName().'_parse_cache', $this->changesetID); if (!$data) { return false; } $data = json_decode($data['cache'], true); if (!is_array($data) || !$data) { return false; } foreach (self::getCacheableProperties() as $cache_key) { if (!array_key_exists($cache_key, $data)) { // If we're missing a cache key, assume we're looking at an old cache // and ignore it. return false; } } if ($data['cacheVersion'] !== self::CACHE_VERSION) { return false; } unset($data['cacheVersion'], $data['cacheHost']); $cache_prop = array_select_keys($data, self::getCacheableProperties()); foreach ($cache_prop as $cache_key => $v) { $this->$cache_key = $v; } return true; } protected static function getCacheableProperties() { return array( 'visible', 'new', 'old', 'intra', 'newRender', 'oldRender', 'specialAttributes', 'missingOld', 'missingNew', 'cacheVersion', 'cacheHost', ); } public function saveCache() { if (!$this->changesetID) { return false; } $cache = array(); foreach (self::getCacheableProperties() as $cache_key) { switch ($cache_key) { case 'cacheVersion': $cache[$cache_key] = self::CACHE_VERSION; break; case 'cacheHost': $cache[$cache_key] = php_uname('n'); break; default: $cache[$cache_key] = $this->$cache_key; break; } } $cache = json_encode($cache); try { $changeset = new DifferentialChangeset(); $conn_w = $changeset->establishConnection('w'); queryfx( $conn_w, 'INSERT INTO %T (id, cache) VALUES (%d, %s) ON DUPLICATE KEY UPDATE cache = VALUES(cache)', $changeset->getTableName().'_parse_cache', $this->changesetID, $cache); } catch (AphrontQueryException $ex) { // TODO: uhoh } } public function isGenerated() { return idx($this->specialAttributes, self::ATTR_GENERATED, false); } public function isDeleted() { return idx($this->specialAttributes, self::ATTR_DELETED, false); } public function isUnchanged() { return idx($this->specialAttributes, self::ATTR_UNCHANGED, false); } public function isWhitespaceOnly() { return idx($this->specialAttributes, self::ATTR_WHITELINES, false); } public function getLength() { return max(count($this->old), count($this->new)); } protected function applyIntraline(&$render, $intra, $corpus) { foreach ($render as $key => $text) { if (isset($intra[$key])) { $render[$key] = ArcanistDiffUtils::applyIntralineDiff( $text, $intra[$key]); } if (isset($corpus[$key]) && strlen($corpus[$key]) > 80) { $render[$key] = $this->lineWrap($render[$key]); } } } protected function lineWrap($l) { $c = 0; $len = strlen($l); $ins = array(); for ($ii = 0; $ii < $len; ++$ii) { if ($l[$ii] == '&') { do { ++$ii; } while ($l[$ii] != ';'); ++$c; } else if ($l[$ii] == '<') { do { ++$ii; } while ($l[$ii] != '>'); } else { ++$c; } if ($c == 80) { $ins[] = ($ii + 1); $c = 0; } } while (($pos = array_pop($ins))) { $l = substr_replace( $l, "\xE2\xAC\x85
", $pos, 0); } return $l; } protected function tokenHighlight(&$render) { foreach ($render as $key => $text) { $render[$key] = str_replace( "\0", ''."\xEF\xBF\xBD".'', $text); } } protected function sourceHighlight($data, $corpus) { $result = $this->highlightEngine->highlightSource( $this->filetype, $corpus); $result_lines = explode("\n", $result); foreach ($data as $key => $info) { if (!$info) { unset($result_lines[$key]); } } return $result_lines; } private function tryCacheStuff() { $whitespace_mode = $this->whitespaceMode; switch ($whitespace_mode) { case self::WHITESPACE_SHOW_ALL: case self::WHITESPACE_IGNORE_TRAILING: break; default: $whitespace_mode = self::WHITESPACE_IGNORE_ALL; break; } $skip_cache = ($whitespace_mode != self::WHITESPACE_IGNORE_ALL); $this->whitespaceMode = $whitespace_mode; $changeset = $this->changeset; if ($changeset->getFileType() == DifferentialChangeType::FILE_TEXT || $changeset->getFileType() == DifferentialChangeType::FILE_SYMLINK) { if ($skip_cache || !$this->loadCache()) { if ($this->whitespaceMode == self::WHITESPACE_IGNORE_ALL) { // Huge mess. Generate a "-bw" (ignore all whitespace changes) diff, // parse it out, and then play a shell game with the parsed format // in process() so we highlight only changed lines but render // whitespace differences. If we don't do this, we either fail to // render whitespace changes (which is incredibly confusing, // especially for python) or often produce a much larger set of // differences than necessary. $old_tmp = new TempFile(); $new_tmp = new TempFile(); Filesystem::writeFile($old_tmp, $changeset->makeOldFile()); Filesystem::writeFile($new_tmp, $changeset->makeNewFile()); list($err, $diff) = exec_manual( 'diff -bw -U65535 %s %s', $old_tmp, $new_tmp); if (!strlen($diff)) { // If there's no diff text, that means the files are identical // except for whitespace changes. Build a synthetic, changeless // diff. TODO: this is incredibly hacky. $entire_file = explode("\n", $changeset->makeOldFile()); foreach ($entire_file as $k => $line) { $entire_file[$k] = ' '.$line; } $len = count($entire_file); $entire_file = implode("\n", $entire_file); $diff = <<parseDiff($diff); $diff = DifferentialDiff::newFromRawChanges($changes); $changesets = $diff->getChangesets(); $alt_changeset = reset($changesets); $this->subparser = new DifferentialChangesetParser(); $this->subparser->setChangeset($alt_changeset); $this->subparser->setWhitespaceMode(self::WHITESPACE_IGNORE_TRAILING); } foreach ($changeset->getHunks() as $hunk) { $this->parseHunk($hunk); } $this->process(); if (!$skip_cache) { $this->saveCache(); } } } } public function render( $range_start = null, $range_len = null, $mask_force = array()) { $this->highlightEngine = new PhutilDefaultSyntaxHighlighterEngine(); $this->tryCacheStuff(); $changeset_id = $this->changesetID; $feedback_mask = array(); switch ($this->changeset->getFileType()) { case DifferentialChangeType::FILE_IMAGE: $old = null; $cur = null; $metadata = $this->changeset->getMetadata(); $data = idx($metadata, 'attachment-data'); $old_phid = idx($metadata, 'old:binary-phid'); $new_phid = idx($metadata, 'new:binary-phid'); if ($old_phid || $new_phid) { if ($old_phid) { $old_uri = PhabricatorFileURI::getViewURIForPHID($old_phid); $old = phutil_render_tag( 'img', array( 'src' => $old_uri, )); } if ($new_phid) { $new_uri = PhabricatorFileURI::getViewURIForPHID($new_phid); $cur = phutil_render_tag( 'img', array( 'src' => $new_uri, )); } } $output = $this->renderChangesetTable( $this->changeset, ''. ''. ''. '
'. $old. '
'. ''. ''. ''. '
'. $cur. '
'. ''. ''); return $output; case DifferentialChangeType::FILE_DIRECTORY: case DifferentialChangeType::FILE_BINARY: $output = $this->renderChangesetTable($this->changeset, null); return $output; } $shield = null; if ($range_start === null && $range_len === null) { if ($this->isGenerated()) { $shield = $this->renderShield( "This file contains generated code, which does not normally need ". "to be reviewed.", true); } else if ($this->isUnchanged()) { if ($this->isWhitespaceOnly()) { $shield = $this->renderShield( "This file was changed only by adding or removing trailing ". "whitespace.", false); } else { $shield = $this->renderShield( "The contents of this file were not changed.", false); } } else if ($this->isDeleted()) { $shield = $this->renderShield( "This file was completely deleted.", true); } else if ($this->changeset->getAffectedLineCount() > 2500) { $lines = number_format($this->changeset->getAffectedLineCount()); $shield = $this->renderShield( "This file has a very large number of changes ({$lines} lines).", true); } else if (preg_match('/\.sql3$/', $this->changeset->getFilename())) { $shield = $this->renderShield( ".sql3 files are hidden by default.", true); } } if ($shield) { return $this->renderChangesetTable($this->changeset, $shield); } $old_comments = array(); $new_comments = array(); $old_mask = array(); $new_mask = array(); $feedback_mask = array(); if ($this->comments) { foreach ($this->comments as $comment) { $start = max($comment->getLineNumber() - self::LINES_CONTEXT, 0); $end = $comment->getLineNumber() + $comment->getLineLength() + self::LINES_CONTEXT; $new = $this->isCommentInNewFile($comment); for ($ii = $start; $ii <= $end; $ii++) { if ($new) { $new_mask[$ii] = true; } else { $old_mask[$ii] = true; } } } foreach ($this->old as $ii => $old) { if (isset($old['line']) && isset($old_mask[$old['line']])) { $feedback_mask[$ii] = true; } } foreach ($this->new as $ii => $new) { if (isset($new['line']) && isset($new_mask[$new['line']])) { $feedback_mask[$ii] = true; } } $this->comments = msort($this->comments, 'getID'); foreach ($this->comments as $comment) { $final = $comment->getLineNumber() + $comment->getLineLength(); if ($this->isCommentInNewFile($comment)) { $new_comments[$final][] = $comment; } else { $old_comments[$final][] = $comment; } } } $html = $this->renderTextChange( $range_start, $range_len, $mask_force, $feedback_mask, $old_comments, $new_comments); return $this->renderChangesetTable($this->changeset, $html); } private function isCommentInNewFile(DifferentialInlineComment $comment) { if ($this->oldChangesetID) { return ($comment->getChangesetID() != $this->oldChangesetID); } else { return $comment->getIsNewFile(); } } protected function renderShield($message, $more) { if ($more) { $end = $this->getLength(); $reference = $this->getChangeset()->getRenderingReference(); $more = ' '. javelin_render_tag( 'a', array( 'mustcapture' => true, 'sigil' => 'show-more', 'class' => 'complete', 'href' => '#', 'meta' => array( 'id' => $reference, 'range' => "0-{$end}", ), ), 'Show File Contents'); } else { $more = null; } return javelin_render_tag( 'tr', array( 'sigil' => 'context-target', ), ''. phutil_escape_html($message). $more. ''); } protected function renderTextChange( $range_start, $range_len, $mask_force, $feedback_mask, array $old_comments, array $new_comments) { $context_not_available = null; if ($this->missingOld || $this->missingNew) { $context_not_available = javelin_render_tag( 'tr', array( 'sigil' => 'context-target', ), ''. 'Context not available.'. ''); $context_not_available = $context_not_available; } $html = array(); $rows = max( count($this->old), count($this->new)); if ($range_start === null) { $range_start = 0; } if ($range_len === null) { $range_len = $rows; } $range_len = min($range_len, $rows - $range_start); $gaps = array(); $gap_start = 0; $in_gap = false; $mask = $this->visible + $mask_force + $feedback_mask; $mask[$range_start + $range_len] = true; for ($ii = $range_start; $ii <= $range_start + $range_len; $ii++) { if (isset($mask[$ii])) { if ($in_gap) { $gap_length = $ii - $gap_start; if ($gap_length <= self::LINES_CONTEXT) { for ($jj = $gap_start; $jj <= $gap_start + $gap_length; $jj++) { $mask[$jj] = true; } } else { $gaps[] = array($gap_start, $gap_length); } $in_gap = false; } } else { if (!$in_gap) { $gap_start = $ii; $in_gap = true; } } } $gaps = array_reverse($gaps); $changeset = $this->changesetID; $reference = $this->getChangeset()->getRenderingReference(); for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) { if (empty($mask[$ii])) { $gap = array_pop($gaps); $top = $gap[0]; $len = $gap[1]; $end = $top + $len - 20; $contents = array(); if ($len > 40) { $contents[] = javelin_render_tag( 'a', array( 'href' => '#', 'mustcapture' => true, 'sigil' => 'show-more', 'meta' => array( 'id' => $reference, 'range' => "{$top}-{$len}/{$top}-20", ), ), "\xE2\x96\xB2 Show 20 Lines"); } $contents[] = javelin_render_tag( 'a', array( 'href' => '#', 'mustcapture' => true, 'sigil' => 'show-more', 'meta' => array( 'id' => $reference, 'range' => "{$top}-{$len}/{$top}-{$len}", ), ), 'Show All '.$len.' Lines'); if ($len > 40) { $contents[] = javelin_render_tag( 'a', array( 'href' => '#', 'mustcapture' => true, 'sigil' => 'show-more', 'meta' => array( 'id' => $reference, 'range' => "{$top}-{$len}/{$end}-20", ), ), "\xE2\x96\xBC Show 20 Lines"); }; $container = javelin_render_tag( 'tr', array( 'sigil' => 'context-target', ), ''. implode(' • ', $contents). ''); $html[] = $container; $ii += ($len - 1); continue; } if (isset($this->old[$ii])) { $o_num = $this->old[$ii]['line']; $o_text = isset($this->oldRender[$ii]) ? $this->oldRender[$ii] : null; $o_attr = null; if ($this->old[$ii]['type']) { if (empty($this->new[$ii])) { $o_attr = ' class="old old-full"'; } else { $o_attr = ' class="old"'; } } } else { $o_num = null; $o_text = null; $o_attr = null; } if (isset($this->new[$ii])) { $n_num = $this->new[$ii]['line']; $n_text = isset($this->newRender[$ii]) ? $this->newRender[$ii] : null; $n_attr = null; if ($this->new[$ii]['type']) { if (empty($this->old[$ii])) { $n_attr = ' class="new new-full"'; } else { $n_attr = ' class="new"'; } } } else { $n_num = null; $n_text = null; $n_attr = null; } if (($o_num && !empty($this->missingOld[$o_num])) || ($n_num && !empty($this->missingNew[$n_num]))) { $html[] = $context_not_available; } if ($o_num && $changeset) { $o_id = ' id="C'.$changeset.'OL'.$o_num.'"'; } else { $o_id = null; } if ($n_num && $changeset) { $n_id = ' id="C'.$changeset.'NL'.$n_num.'"'; } else { $n_id = null; } // NOTE: The Javascript is sensitive to whitespace changes in this // block! $html[] = ''. ''.$o_num.''. ''.$o_text.''. ''.$n_num.''. ''.$n_text.''. ''; if ($context_not_available && ($ii == $rows - 1)) { $html[] = $context_not_available; } if ($o_num && isset($old_comments[$o_num])) { foreach ($old_comments[$o_num] as $comment) { $xhp = $this->renderInlineComment($comment); $html[] = ''. $xhp. ''; } } if ($n_num && isset($new_comments[$n_num])) { foreach ($new_comments[$n_num] as $comment) { $xhp = $this->renderInlineComment($comment); $html[] = ''. $xhp. ''; } } } return implode('', $html); } private function renderInlineComment(DifferentialInlineComment $comment) { $user = $this->user; $edit = $user && ($comment->getAuthorPHID() == $user->getPHID()) && (!$comment->getCommentID()); $on_right = $this->isCommentInNewFile($comment); return id(new DifferentialInlineCommentView()) ->setInlineComment($comment) ->setOnRight($on_right) ->setHandles($this->handles) ->setMarkupEngine($this->markupEngine) ->setEditable($edit) ->render(); } protected function renderPropertyChangeHeader($changeset) { $old = $changeset->getOldProperties(); $new = $changeset->getNewProperties(); if ($old === $new) { return null; } if ($changeset->getChangeType() == DifferentialChangeType::TYPE_ADD && $new == array('unix:filemode' => '100644')) { return null; } if ($changeset->getChangeType() == DifferentialChangeType::TYPE_DELETE && $old == array('unix:filemode' => '100644')) { return null; } return null; /* TODO $table = ; $table->appendChild( ); $keys = array_keys($old + $new); sort($keys); foreach ($keys as $key) { $oval = idx($old, $key); $nval = idx($new, $key); if ($oval !== $nval) { if ($oval === null) { $oval = null; } if ($nval === null) { $nval = null; } $table->appendChild( ); } } return $table; */ } protected function renderChangesetTable($changeset, $contents) { $props = $this->renderPropertyChangeHeader($this->changeset); $table = null; if ($contents) { $table = - '
Property Changes Old Value New Value
{$key} {$oval} {$nval}
'. + '
'. $contents. '
'; } if (!$table && !$props) { $notice = $this->renderChangeTypeHeader($this->changeset, true); } else { $notice = $this->renderChangeTypeHeader($this->changeset, false); } return implode( "\n", array( $notice, $props, $table, )); } protected function renderChangeTypeHeader($changeset, $force) { static $articles = array( DifferentialChangeType::FILE_IMAGE => 'an', ); static $files = array( DifferentialChangeType::FILE_TEXT => 'file', DifferentialChangeType::FILE_IMAGE => 'image', DifferentialChangeType::FILE_DIRECTORY => 'directory', DifferentialChangeType::FILE_BINARY => 'binary file', DifferentialChangeType::FILE_SYMLINK => 'symlink', ); static $changes = array( DifferentialChangeType::TYPE_ADD => 'added', DifferentialChangeType::TYPE_CHANGE => 'changed', DifferentialChangeType::TYPE_DELETE => 'deleted', DifferentialChangeType::TYPE_MOVE_HERE => 'moved from', DifferentialChangeType::TYPE_COPY_HERE => 'copied from', DifferentialChangeType::TYPE_MOVE_AWAY => 'moved to', DifferentialChangeType::TYPE_COPY_AWAY => 'copied to', DifferentialChangeType::TYPE_MULTICOPY => 'deleted after being copied to', ); $change = $changeset->getChangeType(); $file = $changeset->getFileType(); $message = null; if ($change == DifferentialChangeType::TYPE_CHANGE && $file == DifferentialChangeType::FILE_TEXT) { if ($force) { // We have to force something to render because there were no changes // of other kinds. $message = "This {$files[$file]} was not modified."; } else { // Default case of changes to a text file, no metadata. return null; } } else { $verb = idx($changes, $change, 'changed'); switch ($change) { default: $message = "This {$files[$file]} was {$verb}."; break; case DifferentialChangeType::TYPE_MOVE_HERE: case DifferentialChangeType::TYPE_COPY_HERE: $message = "This {$files[$file]} was {$verb} ". "{$changeset->getOldFile()}."; break; case DifferentialChangeType::TYPE_MOVE_AWAY: case DifferentialChangeType::TYPE_COPY_AWAY: case DifferentialChangeType::TYPE_MULTICOPY: $paths = $changeset->getAwayPaths(); if (count($paths) > 1) { $message = "This {$files[$file]} was {$verb}: ". "".implode(', ', $paths)."."; } else { $message = "This {$files[$file]} was {$verb} ". "".reset($paths)."."; } break; case DifferentialChangeType::TYPE_CHANGE: $message = "This is ".idx($articles, $file, 'a')." {$files[$file]}."; break; } } return '
'. $message. '
'; } public function renderForEmail() { $ret = ''; $min = min(count($this->old), count($this->new)); for ($i = 0; $i < $min; $i++) { $o = $this->old[$i]; $n = $this->new[$i]; if (!isset($this->visible[$i])) { continue; } if ($o['line'] && $n['line']) { // It is quite possible there are better ways to achieve this. For // example, "white-space: pre;" can do a better job, WERE IT NOT for // broken email clients like OWA which use newlines to do weird // wrapping. So dont give them newlines. if (isset($this->intra[$i])) { $ret .= sprintf( "- %s
", str_replace(" ", " ", phutil_escape_html($o['text'])) ); $ret .= sprintf( "+ %s
", str_replace(" ", " ", phutil_escape_html($n['text'])) ); } else { $ret .= sprintf("  %s
", str_replace(" ", " ", phutil_escape_html($n['text'])) ); } } else if ($o['line'] && !$n['line']) { $ret .= sprintf( "- %s
", str_replace(" ", " ", phutil_escape_html($o['text'])) ); } else { $ret .= sprintf( "+ %s
", str_replace(" ", " ", phutil_escape_html($n['text'])) ); } } return $ret; } } diff --git a/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php index b7f4a161cd..a6f5ca6322 100644 --- a/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php +++ b/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php @@ -1,231 +1,231 @@ 'View as Highlighted Text', 'blame' => 'View as Highlighted Text with Blame', 'plain' => 'View as Plain Text', 'plainblame' => 'View as Plain Text with Blame', ); $request = $this->getRequest(); $selected = $request->getStr('view'); $select = ''; $view_select_panel = new AphrontPanelView(); $view_select_form = phutil_render_tag( 'form', array( 'action' => $request->getRequestURI(), 'method' => 'get', 'style' => 'display: inline', ), $select. ''); $view_select_panel->appendChild($view_select_form); // Build the content of the file. $corpus = $this->buildCorpus($selected); // Render the page. $content = array(); $content[] = $this->buildCrumbs( array( 'branch' => true, 'path' => true, 'view' => 'browse', )); $content[] = $view_select_panel; $content[] = $corpus; $nav = $this->buildSideNav('browse', true); $nav->appendChild($content); return $this->buildStandardPageResponse( $nav, array( 'title' => 'Browse', )); } public static function renderRevision( DiffusionRequest $drequest, $revision) { $callsign = $drequest->getCallsign(); $name = 'r'.$callsign.$revision; return phutil_render_tag( 'a', array( 'href' => '/'.$name, ), $name ); } private function buildCorpus($selected) { $blame = ($selected == 'blame' || $selected == 'plainblame'); $file_query = DiffusionFileContentQuery::newFromDiffusionRequest( $this->diffusionRequest); $file_query->setNeedsBlame($blame); // TODO: image // TODO: blame of blame. switch ($selected) { case 'plain': case 'plainblame': $style = "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace"; $corpus = phutil_render_tag( 'textarea', array( 'style' => $style, ), phutil_escape_html($file_query->getRawData())); break; case 'highlighted': case 'blame': default: require_celerity_resource('syntax-highlighting-css'); require_celerity_resource('diffusion-source-css'); list($data, $blamedata, $revs) = $file_query->getTokenizedData(); $drequest = $this->getDiffusionRequest(); $path = $drequest->getPath(); $highlightEngine = new PhutilDefaultSyntaxHighlighterEngine(); $data = $highlightEngine->highlightSource($path, $data); $data = explode("\n", rtrim($data)); $rows = $this->buildDisplayRows($data, $blame, $blamedata, $drequest, $revs); $corpus_table = phutil_render_tag( 'table', array( - 'class' => "diffusion-source remarkup-code", + 'class' => "diffusion-source remarkup-code PhabricatorMonospaced", ), implode("\n", $rows)); $corpus = phutil_render_tag( 'div', array( 'style' => 'padding: 0pt 2em;', ), $corpus_table); break; } return $corpus; } private static function buildDisplayRows($data, $blame, $blamedata, $drequest, $revs) { $last = null; $color = null; $rows = array(); $n = 1; foreach ($data as $k => $line) { if ($blame) { if ($last == $blamedata[$k][0]) { $blameinfo = ''. ''; } else { switch ($drequest->getRepository()->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: // TODO: better color for git. $color = '#dddddd'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $color = sprintf( '#%02xee%02x', $revs[$blamedata[$k][0]], $revs[$blamedata[$k][0]]); break; default: throw new Exception('repository type not supported'); } $revision_link = self::renderRevision( $drequest, $blamedata[$k][0]); $author_link = $blamedata[$k][1]; $blameinfo = ''.$revision_link.''. ''.$author_link.''; $last = $blamedata[$k][0]; } } else { $blameinfo = null; } if ($n == $drequest->getLine()) { $tr = ''; $targ = ''; Javelin::initBehavior('diffusion-jump-to', array('target' => 'scroll_target')); } else { $tr = ''; $targ = null; } $uri_path = $drequest->getUriPath(); $uri_rev = $drequest->getCommit(); $l = phutil_render_tag( 'a', array( 'href' => $uri_path.';'.$uri_rev.'$'.$n, ), $n); $rows[] = $tr.$blameinfo.''.$l.''.$targ.$line.''; ++$n; } return $rows; } } diff --git a/src/applications/people/storage/preferences/PhabricatorUserPreferences.php b/src/applications/people/storage/preferences/PhabricatorUserPreferences.php new file mode 100644 index 0000000000..42438a3ada --- /dev/null +++ b/src/applications/people/storage/preferences/PhabricatorUserPreferences.php @@ -0,0 +1,39 @@ + array( + 'preferences' => self::SERIALIZATION_JSON, + ), + self::CONFIG_TIMESTAMPS => false, + ) + parent::getConfiguration(); + } + + public function getPreference($key) { + return $this->getPreferences()[$key]; + } +} diff --git a/src/applications/people/storage/preferences/__init__.php b/src/applications/people/storage/preferences/__init__.php new file mode 100644 index 0000000000..642ad05c2c --- /dev/null +++ b/src/applications/people/storage/preferences/__init__.php @@ -0,0 +1,12 @@ +profileImagePHID, PhabricatorEnv::getEnvConfig('user.default-profile-image-phid')); } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPHIDConstants::PHID_TYPE_USER); } public function setPassword($password) { $this->setPasswordSalt(md5(mt_rand())); $hash = $this->hashPassword($password); $this->setPasswordHash($hash); return $this; } public function save() { if (!$this->conduitCertificate) { $this->conduitCertificate = $this->generateConduitCertificate(); } return parent::save(); } private function generateConduitCertificate() { $entropy = Filesystem::readRandomBytes(256); $entropy = base64_encode($entropy); $entropy = substr($entropy, 0, 255); return $entropy; } 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; const CSRF_TOKEN_LENGTH = 16; const EMAIL_CYCLE_FREQUENCY = 86400; const EMAIL_TOKEN_LENGTH = 24; public function getCSRFToken($offset = 0) { return $this->generateToken( time() + (self::CSRF_CYCLE_FREQUENCY * $offset), self::CSRF_CYCLE_FREQUENCY, PhabricatorEnv::getEnvConfig('phabricator.csrf-key'), self::CSRF_TOKEN_LENGTH); } public function validateCSRFToken($token) { for ($ii = -1; $ii <= 1; $ii++) { $valid = $this->getCSRFToken($ii); if ($token == $valid) { return true; } } return false; } private function generateToken($epoch, $frequency, $key, $len) { $time_block = floor($epoch / $frequency); $vec = $this->getPHID().$this->passwordHash.$key.$time_block; return substr(sha1($vec), 0, $len); } public function establishSession($session_type) { $conn_w = $this->establishConnection('w'); $entropy = Filesystem::readRandomBytes(20); $session_key = sha1($entropy); queryfx( $conn_w, 'INSERT INTO %T '. '(userPHID, type, sessionKey, sessionStart)'. ' VALUES '. '(%s, %s, %s, UNIX_TIMESTAMP()) '. 'ON DUPLICATE KEY UPDATE '. 'sessionKey = VALUES(sessionKey), '. 'sessionStart = VALUES(sessionStart)', self::SESSION_TABLE, $this->getPHID(), $session_type, $session_key); $this->sessionKey = $session_key; return $session_key; } public function generateEmailToken($offset = 0) { return $this->generateToken( time() + ($offset * self::EMAIL_CYCLE_FREQUENCY), self::EMAIL_CYCLE_FREQUENCY, PhabricatorEnv::getEnvConfig('phabricator.csrf-key').$this->getEmail(), self::EMAIL_TOKEN_LENGTH); } public function validateEmailToken($token) { for ($ii = -1; $ii <= 1; $ii++) { $valid = $this->generateEmailToken($ii); if ($token == $valid) { return true; } } return false; } + public function loadPreferences() { + if ($this->preferences) { + return $this->preferences; + } + + $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_MONOSPACED => ''); + + $preferences->setPreferences($default_dict); + } + + $this->preferences = $preferences; + return $preferences; + } + } diff --git a/src/applications/people/storage/user/__init__.php b/src/applications/people/storage/user/__init__.php index efe13374b1..8a7b435549 100644 --- a/src/applications/people/storage/user/__init__.php +++ b/src/applications/people/storage/user/__init__.php @@ -1,19 +1,20 @@ buildStandardPageView(); + + $page->setApplicationName('Preferences'); + $page->setBaseURI('/preferences/'); + $page->setTitle(idx($data, 'title')); + $page->setGlyph("\xE2\x9A\x92"); + $page->appendChild($view); + + $response = new AphrontWebpageResponse(); + return $response->setContent($page->render()); + } +} diff --git a/src/applications/preferences/controller/base/__init__.php b/src/applications/preferences/controller/base/__init__.php new file mode 100644 index 0000000000..6b30c6011c --- /dev/null +++ b/src/applications/preferences/controller/base/__init__.php @@ -0,0 +1,15 @@ +getRequest(); + $user = $request->getUser(); + $preferences = $user->loadPreferences(); + + if ($request->isFormPost()) { + $monospaced = $request->getStr( + PhabricatorUserPreferences::PREFERENCE_MONOSPACED); + + // Prevent the user from doing stupid things. + $monospaced = preg_replace('/[^a-z0-9 ,"]+/i', '', $monospaced); + + $pref_dict = array( + PhabricatorUserPreferences::PREFERENCE_TITLES => + $request->getStr(PhabricatorUserPreferences::PREFERENCE_TITLES), + PhabricatorUserPreferences::PREFERENCE_MONOSPACED => + $monospaced); + + $preferences->setPreferences($pref_dict); + $preferences->save(); + return id(new AphrontRedirectResponse()) + ->setURI('/preferences/?saved=true'); + } + + $example_string = <<setAction('/preferences/') + ->setUser($user) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel('Page Titles') + ->setName(PhabricatorUserPreferences::PREFERENCE_TITLES) + ->setValue($preferences->getPreference( + PhabricatorUserPreferences::PREFERENCE_TITLES)) + ->setOptions( + array( + 'glyph' => + "In page titles, show Tool names as unicode glyphs: \xE2\x9A\x99", + 'text' => + 'In page titles, show Tool names as plain text: [Differential]', + ))) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Monospaced Font') + ->setName(PhabricatorUserPreferences::PREFERENCE_MONOSPACED) + ->setCaption( + 'Overrides default fonts in tools like Differential. '. + '(Default: 10px "Menlo", "Consolas", "Monaco", '. + 'monospace)') + ->setValue($preferences->getPreference( + PhabricatorUserPreferences::PREFERENCE_MONOSPACED))) + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setValue( + '
'.
+          phutil_escape_html($example_string).
+          '
')) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Save Preferences')); + + $panel = new AphrontPanelView(); + $panel->appendChild($form); + + $error_view = null; + if ($request->getStr('saved') === 'true') { + $error_view = id(new AphrontErrorView()) + ->setTitle('Preferences Saved') + ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) + ->setErrors(array('Your preferences have been saved.')); + } + + return $this->buildStandardPageResponse( + array( + $error_view, + $panel, + ), + array( + 'title' => 'Edit Preferences', + )); + + } +} + diff --git a/src/applications/preferences/controller/edit/__init__.php b/src/applications/preferences/controller/edit/__init__.php new file mode 100644 index 0000000000..d91533583a --- /dev/null +++ b/src/applications/preferences/controller/edit/__init__.php @@ -0,0 +1,20 @@ +request = $request; return $this; } public function getRequest() { return $this->request; } public function setApplicationName($application_name) { $this->applicationName = $application_name; return $this; } public function getApplicationName() { return $this->applicationName; } public function setBaseURI($base_uri) { $this->baseURI = $base_uri; return $this; } public function getBaseURI() { return $this->baseURI; } public function setTabs(array $tabs, $selected_tab) { $this->tabs = $tabs; $this->selectedTab = $selected_tab; return $this; } public function getTitle() { - return $this->getGlyph().' '.parent::getTitle(); + $use_glyph = true; + $request = $this->getRequest(); + if ($request) { + $user = $request->getUser(); + if ($user && $user->loadPreferences()->getPreference( + PhabricatorUserPreferences::PREFERENCE_TITLES) !== 'glyph') { + $use_glyph = false; + } + } + + return ($use_glyph ? + $this->getGlyph() : '['.$this->getApplicationName().']'). + ' '.parent::getTitle(); } protected function willRenderPage() { if (!$this->getRequest()) { throw new Exception( "You must set the Request to render a PhabricatorStandardPageView."); } $console = $this->getRequest()->getApplicationConfiguration()->getConsole(); require_celerity_resource('phabricator-core-css'); require_celerity_resource('phabricator-core-buttons-css'); require_celerity_resource('phabricator-standard-page-view'); require_celerity_resource('javelin-lib-dev'); require_celerity_resource('javelin-workflow-dev'); Javelin::initBehavior('workflow', array()); if ($console) { require_celerity_resource('aphront-dark-console-css'); Javelin::initBehavior( 'dark-console', array( 'uri' => '/~/', )); } $this->bodyContent = $this->renderChildren(); } protected function getHead() { $response = CelerityAPI::getStaticResourceResponse(); - return + $head = ''. $response->renderResourcesOfType('css'). ''; + + $request = $this->getRequest(); + if ($request) { + $user = $request->getUser(); + if ($user) { + $monospaced = $user->loadPreferences()->getPreference( + PhabricatorUserPreferences::PREFERENCE_MONOSPACED + ); + + if (strlen($monospaced)) { + $head .= + ''; + } + } + } + + return $head; } public function setGlyph($glyph) { $this->glyph = $glyph; return $this; } public function getGlyph() { return $this->glyph; } protected function willSendResponse($response) { $console = $this->getRequest()->getApplicationConfiguration()->getConsole(); if ($console) { $response = str_replace( '', $console->render($this->getRequest()), $response); } return $response; } protected function getBody() { $console = $this->getRequest()->getApplicationConfiguration()->getConsole(); $tabs = array(); foreach ($this->tabs as $name => $tab) { $tab_markup = phutil_render_tag( 'a', array( 'href' => idx($tab, 'href'), ), phutil_escape_html(idx($tab, 'name'))); $tab_markup = phutil_render_tag( 'td', array( 'class' => ($name == $this->selectedTab) ? 'phabricator-selected-tab' : null, ), $tab_markup); $tabs[] = $tab_markup; } $tabs = implode('', $tabs); $login_stuff = null; $request = $this->getRequest(); $user = null; if ($request) { $user = $request->getUser(); // NOTE: user may not be set here if we caught an exception early // in the execution workflow. if ($user && $user->getPHID()) { $login_stuff = 'Logged in as '.phutil_render_tag( 'a', array( 'href' => '/p/'.$user->getUsername().'/', ), phutil_escape_html($user->getUsername())). ' · '. 'Settings'. ' · '. phabricator_render_form( $user, array( 'action' => '/search/', 'method' => 'post', 'style' => 'display: inline', ), ''. ''); } } $foot_links = array(); $version = PhabricatorEnv::getEnvConfig('phabricator.version'); $foot_links[] = phutil_escape_html('Phabricator '.$version); if (PhabricatorEnv::getEnvConfig('darkconsole.enabled') && !PhabricatorEnv::getEnvConfig('darkconsole.always-on')) { if ($console) { $link = javelin_render_tag( 'a', array( 'href' => '/~/', 'sigil' => 'workflow', ), 'Disable DarkConsole'); } else { $link = javelin_render_tag( 'a', array( 'href' => '/~/', 'sigil' => 'workflow', ), 'Enable DarkConsole'); } $foot_links[] = $link; } if ($user && $user->getPHID()) { // This ends up very early in tab order at the top of the page and there's // a bunch of junk up there anyway, just shove it down here. $foot_links[] = phabricator_render_form( $user, array( 'action' => '/logout/', 'method' => 'post', 'style' => 'display: inline', ), ''); } $foot_links = implode(' · ', $foot_links); return ($console ? '' : null). '
'. '
'. ''. ''. ''. ''. ''. $tabs. ''. '
'. phutil_render_tag( 'a', array( 'href' => $this->getBaseURI(), 'class' => 'phabricator-head-appname', ), phutil_escape_html($this->getApplicationName())). '
'. '
'. $this->bodyContent. '
'. '
'. '
'. $foot_links. '
'; } protected function getTail() { $response = CelerityAPI::getStaticResourceResponse(); return $response->renderResourcesOfType('js'). $response->renderHTMLFooter(); } } diff --git a/src/view/page/standard/__init__.php b/src/view/page/standard/__init__.php index 5b4e699da5..7db98f4535 100644 --- a/src/view/page/standard/__init__.php +++ b/src/view/page/standard/__init__.php @@ -1,19 +1,20 @@