From f2c36a934ec8a6914a6cfa739568bcd076a514a8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 20 May 2016 06:20:35 -0700 Subject: [PATCH 01/24] Provide an `` control in Remarkup for mobile and users with esoteric windowing systems Summary: Ref T5187. This definitely feels a bit flimsy and I'm going to hold it until I cut the release since it changes a couple of things about Workflow in general, but it seems to work OK and most of it is fine. The intent is described in T5187#176236. In practice, most of that works like I describe, then the `phui-file-upload` behavior gets some weird glue to figure out if the input is part of the form. Not the most elegant system, but I think it'll hold until we come up with many reasons to write a lot more Javascript. Test Plan: Used both drag-and-drop and the upload dialog to upload files in Safari, Firefox and Chrome. {F1653716} Reviewers: chad Reviewed By: chad Maniphest Tasks: T5187 Differential Revision: https://secure.phabricator.com/D15953 --- src/__phutil_library_map__.php | 2 + .../PhabricatorFileDropUploadController.php | 14 +--- .../PhabricatorFileUploadDialogController.php | 49 ++++++++++-- .../files/storage/PhabricatorFile.php | 8 ++ src/view/form/control/PHUIFormFileControl.php | 44 ++++++++++ .../rsrc/externals/javelin/lib/Workflow.js | 63 ++++++++++++++- webroot/rsrc/js/core/DragAndDropFileUpload.js | 6 +- webroot/rsrc/js/core/TextAreaUtils.js | 20 +++++ .../core/behavior-drag-and-drop-textarea.js | 23 ++---- .../behavior-phabricator-remarkup-assist.js | 16 +++- .../rsrc/js/phui/behavior-phui-file-upload.js | 80 +++++++++++++++++++ 11 files changed, 287 insertions(+), 38 deletions(-) create mode 100644 src/view/form/control/PHUIFormFileControl.php create mode 100644 webroot/rsrc/js/phui/behavior-phui-file-upload.js diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7df48b8426..3749af3276 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1594,6 +1594,7 @@ phutil_register_library_map(array( 'PHUIFeedStoryExample' => 'applications/uiexample/examples/PHUIFeedStoryExample.php', 'PHUIFeedStoryView' => 'view/phui/PHUIFeedStoryView.php', 'PHUIFormDividerControl' => 'view/form/control/PHUIFormDividerControl.php', + 'PHUIFormFileControl' => 'view/form/control/PHUIFormFileControl.php', 'PHUIFormFreeformDateControl' => 'view/form/control/PHUIFormFreeformDateControl.php', 'PHUIFormIconSetControl' => 'view/form/control/PHUIFormIconSetControl.php', 'PHUIFormInsetView' => 'view/form/PHUIFormInsetView.php', @@ -5996,6 +5997,7 @@ phutil_register_library_map(array( 'PHUIFeedStoryExample' => 'PhabricatorUIExample', 'PHUIFeedStoryView' => 'AphrontView', 'PHUIFormDividerControl' => 'AphrontFormControl', + 'PHUIFormFileControl' => 'AphrontFormControl', 'PHUIFormFreeformDateControl' => 'AphrontFormControl', 'PHUIFormIconSetControl' => 'AphrontFormControl', 'PHUIFormInsetView' => 'AphrontView', diff --git a/src/applications/files/controller/PhabricatorFileDropUploadController.php b/src/applications/files/controller/PhabricatorFileDropUploadController.php index 222fc799c7..21714994ff 100644 --- a/src/applications/files/controller/PhabricatorFileDropUploadController.php +++ b/src/applications/files/controller/PhabricatorFileDropUploadController.php @@ -56,7 +56,7 @@ final class PhabricatorFileDropUploadController $file_phid = $result['filePHID']; if ($file_phid) { $file = $this->loadFile($file_phid); - $result += $this->getFileDictionary($file); + $result += $file->getDragAndDropDictionary(); } return id(new AphrontAjaxResponse())->setContent($result); @@ -84,7 +84,7 @@ final class PhabricatorFileDropUploadController } else { $result = array( 'complete' => true, - ) + $this->getFileDictionary($file); + ) + $file->getDragAndDropDictionary(); } return id(new AphrontAjaxResponse())->setContent($result); @@ -99,18 +99,10 @@ final class PhabricatorFileDropUploadController 'isExplicitUpload' => true, )); - $result = $this->getFileDictionary($file); + $result = $file->getDragAndDropDictionary(); return id(new AphrontAjaxResponse())->setContent($result); } - private function getFileDictionary(PhabricatorFile $file) { - return array( - 'id' => $file->getID(), - 'phid' => $file->getPHID(), - 'uri' => $file->getBestURI(), - ); - } - private function loadFile($file_phid) { $viewer = $this->getViewer(); diff --git a/src/applications/files/controller/PhabricatorFileUploadDialogController.php b/src/applications/files/controller/PhabricatorFileUploadDialogController.php index cf13f4d694..77fff2561f 100644 --- a/src/applications/files/controller/PhabricatorFileUploadDialogController.php +++ b/src/applications/files/controller/PhabricatorFileUploadDialogController.php @@ -6,12 +6,51 @@ final class PhabricatorFileUploadDialogController public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - return $this->newDialog() - ->setTitle(pht('Upload File')) - ->appendChild(pht( - 'To add files, drag and drop them into the comment text area.')) - ->addCancelButton('/', pht('Close')); + $e_file = true; + $errors = array(); + if ($request->isDialogFormPost()) { + $file_phids = $request->getStrList('filePHIDs'); + if ($file_phids) { + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs($file_phids) + ->setRaisePolicyExceptions(true) + ->execute(); + } else { + $files = array(); + } + if ($files) { + $results = array(); + foreach ($files as $file) { + $results[] = $file->getDragAndDropDictionary(); + } + + $content = array( + 'files' => $results, + ); + + return id(new AphrontAjaxResponse())->setContent($content); + } else { + $e_file = pht('Required'); + $errors[] = pht('You must choose a file to upload.'); + } + } + + $form = id(new AphrontFormView()) + ->appendChild( + id(new PHUIFormFileControl()) + ->setName('filePHIDs') + ->setLabel(pht('Upload File')) + ->setAllowMultiple(true) + ->setError($e_file)); + + return $this->newDialog() + ->setTitle(pht('File')) + ->setErrors($errors) + ->appendForm($form) + ->addSubmitButton(pht('Upload')) + ->addCancelButton('/'); } } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index ff2a4d27ac..d545aa1de0 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -851,6 +851,14 @@ final class PhabricatorFile extends PhabricatorFileDAO return $supported; } + public function getDragAndDropDictionary() { + return array( + 'id' => $this->getID(), + 'phid' => $this->getPHID(), + 'uri' => $this->getBestURI(), + ); + } + public function instantiateStorageEngine() { return self::buildEngine($this->getStorageEngine()); } diff --git a/src/view/form/control/PHUIFormFileControl.php b/src/view/form/control/PHUIFormFileControl.php new file mode 100644 index 0000000000..57f5d30bf5 --- /dev/null +++ b/src/view/form/control/PHUIFormFileControl.php @@ -0,0 +1,44 @@ +allowMultiple = $allow_multiple; + return $this; + } + + public function getAllowMultiple() { + return $this->allowMultiple; + } + + protected function renderInput() { + $file_id = $this->getID(); + + Javelin::initBehavior( + 'phui-file-upload', + array( + 'fileInputID' => $file_id, + 'inputName' => $this->getName(), + 'uploadURI' => '/file/dropupload/', + 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(), + )); + + return phutil_tag( + 'input', + array( + 'type' => 'file', + 'multiple' => $this->getAllowMultiple() ? 'multiple' : null, + 'name' => $this->getName().'.raw', + 'id' => $file_id, + 'disabled' => $this->getDisabled() ? 'disabled' : null, + )); + } + +} diff --git a/webroot/rsrc/externals/javelin/lib/Workflow.js b/webroot/rsrc/externals/javelin/lib/Workflow.js index 50716b1c07..2ee7ef1ff0 100644 --- a/webroot/rsrc/externals/javelin/lib/Workflow.js +++ b/webroot/rsrc/externals/javelin/lib/Workflow.js @@ -25,7 +25,7 @@ JX.install('Workflow', { this.setData(data || {}); }, - events : ['error', 'finally', 'submit'], + events : ['error', 'finally', 'submit', 'start'], statics : { _stack : [], @@ -54,6 +54,9 @@ JX.install('Workflow', { } var workflow = new JX.Workflow(form.getAttribute('action'), {}); + + workflow._form = form; + workflow.setDataWithListOfPairs(pairs); workflow.setMethod(form.getAttribute('method')); workflow.listen('finally', function() { @@ -137,9 +140,14 @@ JX.install('Workflow', { data.push([button.name, button.value || true]); var active = JX.Workflow._getActiveWorkflow(); + + active._form = form; + var e = active.invoke('submit', {form: form, data: data}); if (!e.getStopped()) { - active._destroy(); + // NOTE: Don't remove the current dialog yet because additional + // handlers may still want to access the nodes. + active .setURI(form.getAttribute('action') || active.getURI()) .setDataWithListOfPairs(data) @@ -156,7 +164,41 @@ JX.install('Workflow', { _root : null, _pushed : false, _data : null, + + _form: null, + _paused: 0, + _nextCallback: null, + + getSourceForm: function() { + return this._form; + }, + + pause: function() { + this._paused++; + return this; + }, + + resume: function() { + if (!this._paused) { + JX.$E('Resuming a workflow which is not paused!'); + } + + this._paused--; + + if (!this._paused) { + var next = this._nextCallback; + this._nextCallback = null; + if (next) { + next(); + } + } + + return this; + }, + _onload : function(r) { + this._destroy(); + // It is permissible to send back a falsey redirect to force a page // reload, so we need to take this branch if the key is present. if (r && (typeof r.redirect != 'undefined')) { @@ -247,7 +289,19 @@ JX.install('Workflow', { this._root = null; } }, + start : function() { + var next = JX.bind(this, this._send); + + this.pause(); + this._nextCallback = next; + + this.invoke('start', this); + + this.resume(); + }, + + _send: function() { var uri = this.getURI(); var method = this.getMethod(); var r = new JX.Request(uri, JX.bind(this, this._onload)); @@ -291,6 +345,11 @@ JX.install('Workflow', { return this; }, + addData: function(key, value) { + this._data.push([key, value]); + return this; + }, + setDataWithListOfPairs : function(list_of_pairs) { this._data = list_of_pairs; return this; diff --git a/webroot/rsrc/js/core/DragAndDropFileUpload.js b/webroot/rsrc/js/core/DragAndDropFileUpload.js index 08cda15798..644f705965 100644 --- a/webroot/rsrc/js/core/DragAndDropFileUpload.js +++ b/webroot/rsrc/js/core/DragAndDropFileUpload.js @@ -155,7 +155,7 @@ JX.install('PhabricatorDragAndDropFileUpload', { var files = e.getRawEvent().dataTransfer.files; for (var ii = 0; ii < files.length; ii++) { - this._sendRequest(files[ii]); + this.sendRequest(files[ii]); } // Force depth to 0. @@ -216,7 +216,7 @@ JX.install('PhabricatorDragAndDropFileUpload', { if (!spec.name) { spec.name = 'pasted_file'; } - this._sendRequest(spec); + this.sendRequest(spec); } })); } @@ -224,7 +224,7 @@ JX.install('PhabricatorDragAndDropFileUpload', { this.setIsEnabled(true); }, - _sendRequest : function(spec) { + sendRequest : function(spec) { var file = new JX.PhabricatorFileUpload() .setRawFileObject(spec) .setName(spec.name) diff --git a/webroot/rsrc/js/core/TextAreaUtils.js b/webroot/rsrc/js/core/TextAreaUtils.js index 8f8a81067b..56f0789a46 100644 --- a/webroot/rsrc/js/core/TextAreaUtils.js +++ b/webroot/rsrc/js/core/TextAreaUtils.js @@ -62,6 +62,26 @@ JX.install('TextAreaUtils', { JX.TextAreaUtils.setSelectionRange(area, start, end); }, + + /** + * Insert a reference to a given uploaded file into a textarea. + */ + insertFileReference: function(area, file) { + var ref = '{F' + file.getID() + '}'; + + // If we're inserting immediately after a "}" (usually, another file + // reference), put some newlines before our token so that multiple file + // uploads get laid out more nicely. + var range = JX.TextAreaUtils.getSelectionRange(area); + var before = area.value.substring(0, range.start); + if (before.match(/\}$/)) { + ref = '\n\n' + ref; + } + + JX.TextAreaUtils.setSelectionText(area, ref, false); + }, + + /** * Get the document pixel positions of the beginning and end of a character * range in a textarea. diff --git a/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js b/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js index 7ee536edb4..4493ae3b2b 100644 --- a/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js +++ b/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js @@ -10,32 +10,23 @@ JX.behavior('aphront-drag-and-drop-textarea', function(config) { var target = JX.$(config.target); - function onupload(f) { - var ref = '{F' + f.getID() + '}'; - - // If we're inserting immediately after a "}" (usually, another file - // reference), put some newlines before our token so that multiple file - // uploads get laid out more nicely. - var range = JX.TextAreaUtils.getSelectionRange(target); - var before = target.value.substring(0, range.start); - if (before.match(/\}$/)) { - ref = '\n\n' + ref; - } - - JX.TextAreaUtils.setSelectionText(target, ref, false); - } - if (JX.PhabricatorDragAndDropFileUpload.isSupported()) { var drop = new JX.PhabricatorDragAndDropFileUpload(target) .setURI(config.uri) .setChunkThreshold(config.chunkThreshold); + drop.listen('didBeginDrag', function() { JX.DOM.alterClass(target, config.activatedClass, true); }); + drop.listen('didEndDrag', function() { JX.DOM.alterClass(target, config.activatedClass, false); }); - drop.listen('didUpload', onupload); + + drop.listen('didUpload', function(file) { + JX.TextAreaUtils.insertFileReference(target, file); + }); + drop.start(); } diff --git a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js index f09070e42e..2b1646968c 100644 --- a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js +++ b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js @@ -194,7 +194,21 @@ JX.behavior('phabricator-remarkup-assist', function(config) { .start(); break; case 'fa-cloud-upload': - new JX.Workflow('/file/uploaddialog/').start(); + new JX.Workflow('/file/uploaddialog/') + .setHandler(function(response) { + var files = response.files; + for (var ii = 0; ii < files.length; ii++) { + var file = files[ii]; + + var upload = new JX.PhabricatorFileUpload() + .setID(file.id) + .setPHID(file.phid) + .setURI(file.uri); + + JX.TextAreaUtils.insertFileReference(area, upload); + } + }) + .start(); break; case 'fa-arrows-alt': if (edit_mode == 'fa-arrows-alt') { diff --git a/webroot/rsrc/js/phui/behavior-phui-file-upload.js b/webroot/rsrc/js/phui/behavior-phui-file-upload.js new file mode 100644 index 0000000000..39e3bddff6 --- /dev/null +++ b/webroot/rsrc/js/phui/behavior-phui-file-upload.js @@ -0,0 +1,80 @@ +/** + * @provides javelin-behavior-phui-file-upload + * @requires javelin-behavior + * javelin-stratcom + * javelin-dom + * phuix-dropdown-menu + */ + +JX.behavior('phui-file-upload', function(config) { + + function startUpload(workflow, input) { + var files = input.files; + + if (!files || !files.length) { + return; + } + + var state = { + workflow: workflow, + input: input, + waiting: 0, + phids: [] + }; + + var callback = JX.bind(null, didUpload, state); + + var dummy = input; + var uploader = new JX.PhabricatorDragAndDropFileUpload(dummy) + .setURI(config.uploadURI) + .setChunkThreshold(config.chunkThreshold); + + uploader.listen('didUpload', callback); + uploader.start(); + + workflow.pause(); + for (var ii = 0; ii < files.length; ii++) { + state.waiting++; + uploader.sendRequest(files[ii]); + } + } + + function didUpload(state, file) { + state.phids.push(file.getPHID()); + state.waiting--; + + if (state.waiting) { + return; + } + + state.workflow + .addData(config.inputName, state.phids.join(', ')) + .resume(); + } + + JX.Workflow.listen('start', function(workflow) { + var form = workflow.getSourceForm(); + if (!form) { + return; + } + + var input; + try { + input = JX.$(config.fileInputID); + } catch (ex) { + return; + } + + var local_form = JX.DOM.findAbove(input, 'form'); + if (!local_form) { + return; + } + + if (local_form !== form) { + return; + } + + startUpload(workflow, input); + }); + +}); From ed92d1d8448381af5a947fba76047bd3f394c03e Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 20 May 2016 16:26:11 -0700 Subject: [PATCH 02/24] Regenerate the Celerity map. Auditors: chad --- resources/celerity/map.php | 106 ++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c920068298..1dc0b7bf8f 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,10 +8,10 @@ return array( 'names' => array( 'core.pkg.css' => '204cabae', - 'core.pkg.js' => '6972d365', + 'core.pkg.js' => '9f2969e9', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '33da0633', - 'differential.pkg.js' => 'd0cd0df6', + 'differential.pkg.js' => '4b7d8f19', 'diffusion.pkg.css' => '91c5d3a6', 'diffusion.pkg.js' => '3a9a8bfa', 'maniphest.pkg.css' => '4845691a', @@ -245,7 +245,7 @@ return array( 'rsrc/externals/javelin/lib/URI.js' => 'c989ade3', 'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8', 'rsrc/externals/javelin/lib/WebSocket.js' => 'e292eaf4', - 'rsrc/externals/javelin/lib/Workflow.js' => '28cfbdd0', + 'rsrc/externals/javelin/lib/Workflow.js' => '0eb34d1d', 'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8', 'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b', 'rsrc/externals/javelin/lib/__tests__/JSON.js' => '837a7d68', @@ -457,7 +457,7 @@ return array( 'rsrc/js/application/uiexample/gesture-example.js' => '558829c2', 'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5', 'rsrc/js/core/Busy.js' => '59a7976a', - 'rsrc/js/core/DragAndDropFileUpload.js' => '81f182b5', + 'rsrc/js/core/DragAndDropFileUpload.js' => '58dea2fa', 'rsrc/js/core/DraggableList.js' => '5a13c79f', 'rsrc/js/core/FileUpload.js' => '680ea2c8', 'rsrc/js/core/Hovercard.js' => '1bd28176', @@ -467,7 +467,7 @@ return array( 'rsrc/js/core/Notification.js' => 'ccf1cbf8', 'rsrc/js/core/Prefab.js' => 'e67df814', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', - 'rsrc/js/core/TextAreaUtils.js' => '5813016a', + 'rsrc/js/core/TextAreaUtils.js' => '320810c8', 'rsrc/js/core/Title.js' => 'df5e11d2', 'rsrc/js/core/ToolTip.js' => '6323f942', 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', @@ -478,7 +478,7 @@ return array( 'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2', 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', 'rsrc/js/core/behavior-device.js' => 'b5b36110', - 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '4f6a4b4e', + 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', 'rsrc/js/core/behavior-fancy-datepicker.js' => '568931f3', 'rsrc/js/core/behavior-file-tree.js' => '88236f00', @@ -496,7 +496,7 @@ return array( 'rsrc/js/core/behavior-object-selector.js' => '49b73b36', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 'rsrc/js/core/behavior-phabricator-nav.js' => '56a1ca03', - 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '340c8eff', + 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '116cf19b', 'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207', 'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b', 'rsrc/js/core/behavior-remarkup-preview.js' => '4b700e9e', @@ -514,6 +514,7 @@ return array( 'rsrc/js/core/behavior-workflow.js' => '0a3f3021', 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '54733475', + 'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb', 'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836', 'rsrc/js/phui/behavior-phui-profile-menu.js' => '12884df9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', @@ -578,7 +579,7 @@ return array( 'javelin-behavior-aphlict-status' => 'ea681761', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-crop' => 'fa0f4fc2', - 'javelin-behavior-aphront-drag-and-drop-textarea' => '4f6a4b4e', + 'javelin-behavior-aphront-drag-and-drop-textarea' => '484a6e22', 'javelin-behavior-aphront-form-disable-on-submit' => '5c54cbf3', 'javelin-behavior-aphront-more' => 'a80d0378', 'javelin-behavior-audio-source' => '59b251eb', @@ -654,7 +655,7 @@ return array( 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => '49b73b36', 'javelin-behavior-phabricator-oncopy' => '2926fff2', - 'javelin-behavior-phabricator-remarkup-assist' => '340c8eff', + 'javelin-behavior-phabricator-remarkup-assist' => '116cf19b', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => '06c32383', 'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6', @@ -665,6 +666,7 @@ return array( 'javelin-behavior-pholio-mock-edit' => '246dc085', 'javelin-behavior-pholio-mock-view' => 'fbe497e7', 'javelin-behavior-phui-dropdown-menu' => '54733475', + 'javelin-behavior-phui-file-upload' => 'b003d4fb', 'javelin-behavior-phui-hovercards' => 'bcaccd64', 'javelin-behavior-phui-object-box-tabs' => '2bfa2836', 'javelin-behavior-phui-profile-menu' => '12884df9', @@ -743,7 +745,7 @@ return array( 'javelin-workboard-card' => 'c587b80f', 'javelin-workboard-column' => 'bae58312', 'javelin-workboard-controller' => '55baf5ed', - 'javelin-workflow' => '28cfbdd0', + 'javelin-workflow' => '0eb34d1d', 'lightbox-attachment-css' => '7acac05d', 'maniphest-batch-editor' => 'b0f0b6d5', 'maniphest-report-css' => '9b9580b7', @@ -763,7 +765,7 @@ return array( 'phabricator-core-css' => 'd0801452', 'phabricator-countdown-css' => '16c52f5c', 'phabricator-dashboard-css' => 'bc6f2127', - 'phabricator-drag-and-drop-file-upload' => '81f182b5', + 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => '5a13c79f', 'phabricator-fatal-config-template-css' => '8e6c6fcd', 'phabricator-feed-css' => 'ecd4ec57', @@ -787,7 +789,7 @@ return array( 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => 'cbeef983', 'phabricator-standard-page-view' => 'e709f6d0', - 'phabricator-textareautils' => '5813016a', + 'phabricator-textareautils' => '320810c8', 'phabricator-title' => 'df5e11d2', 'phabricator-tooltip' => '6323f942', 'phabricator-ui-example-css' => '528b19de', @@ -975,10 +977,31 @@ return array( 'javelin-dom', 'javelin-router', ), + '0eb34d1d' => array( + 'javelin-stratcom', + 'javelin-request', + 'javelin-dom', + 'javelin-vector', + 'javelin-install', + 'javelin-util', + 'javelin-mask', + 'javelin-uri', + 'javelin-routable', + ), '0f764c35' => array( 'javelin-install', 'javelin-util', ), + '116cf19b' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phabricator-phtize', + 'phabricator-textareautils', + 'javelin-workflow', + 'javelin-vector', + 'phuix-autocomplete', + ), '12884df9' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1075,17 +1098,6 @@ return array( 'phabricator-drag-and-drop-file-upload', 'phabricator-draggable-list', ), - '28cfbdd0' => array( - 'javelin-stratcom', - 'javelin-request', - 'javelin-dom', - 'javelin-vector', - 'javelin-install', - 'javelin-util', - 'javelin-mask', - 'javelin-uri', - 'javelin-routable', - ), '2926fff2' => array( 'javelin-behavior', 'javelin-dom', @@ -1116,22 +1128,17 @@ return array( '2ee659ce' => array( 'javelin-install', ), + '320810c8' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-vector', + ), '327a00d1' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-workflow', ), - '340c8eff' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'phabricator-phtize', - 'phabricator-textareautils', - 'javelin-workflow', - 'javelin-vector', - 'phuix-autocomplete', - ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1199,6 +1206,12 @@ return array( 'javelin-dom', 'javelin-workflow', ), + '484a6e22' => array( + 'javelin-behavior', + 'javelin-dom', + 'phabricator-drag-and-drop-file-upload', + 'phabricator-textareautils', + ), '49b73b36' => array( 'javelin-behavior', 'javelin-dom', @@ -1216,12 +1229,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - '4f6a4b4e' => array( - 'javelin-behavior', - 'javelin-dom', - 'phabricator-drag-and-drop-file-upload', - 'phabricator-textareautils', - ), '4fbbc3e9' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1319,10 +1326,13 @@ return array( 'javelin-request', 'javelin-util', ), - '5813016a' => array( + '58dea2fa' => array( 'javelin-install', + 'javelin-util', + 'javelin-request', 'javelin-dom', - 'javelin-vector', + 'javelin-uri', + 'phabricator-file-upload', ), '59a7976a' => array( 'javelin-install', @@ -1516,14 +1526,6 @@ return array( 'javelin-vector', 'javelin-stratcom', ), - '81f182b5' => array( - 'javelin-install', - 'javelin-util', - 'javelin-request', - 'javelin-dom', - 'javelin-uri', - 'phabricator-file-upload', - ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', @@ -1740,6 +1742,12 @@ return array( 'javelin-util', 'phabricator-busy', ), + 'b003d4fb' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phuix-dropdown-menu', + ), 'b064af76' => array( 'javelin-behavior', 'javelin-stratcom', From e902fc0e2a646476af486d73faf6617316eb5030 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 21 May 2016 12:08:47 -0700 Subject: [PATCH 03/24] Move Tablet breakpoint from 768 -> 920 Summary: Bumping this up higher since two column views get extra tight fast below 900 px. This felt most correct to me, dialing it back from first attempt at 960. Mostly I don't want to ever accidentally trigger it when I'm on the 12" MacBook. Ref T10926 Test Plan: Durable Column, Workboards, Dashboards, Tasks. Reviewers: epriestley Reviewed By: epriestley Subscribers: avivey, Korvin Maniphest Tasks: T10926 Differential Revision: https://secure.phabricator.com/D15960 --- resources/celerity/map.php | 50 +++++++++---------- webroot/rsrc/css/phui/phui-crumbs-view.css | 4 +- .../conpherence/behavior-durable-column.js | 2 +- webroot/rsrc/js/core/behavior-device.js | 2 +- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1dc0b7bf8f..1cf9e506ee 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,8 +7,8 @@ */ return array( 'names' => array( - 'core.pkg.css' => '204cabae', - 'core.pkg.js' => '9f2969e9', + 'core.pkg.css' => '8aeacc63', + 'core.pkg.js' => '437c65c2', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '33da0633', 'differential.pkg.js' => '4b7d8f19', @@ -126,7 +126,7 @@ return array( 'rsrc/css/phui/phui-box.css' => '5c8387cf', 'rsrc/css/phui/phui-button.css' => 'a64a8de6', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', - 'rsrc/css/phui/phui-crumbs-view.css' => '1a1265d4', + 'rsrc/css/phui/phui-crumbs-view.css' => '6b813619', 'rsrc/css/phui/phui-curtain-view.css' => '7148ae25', 'rsrc/css/phui/phui-document-pro.css' => '8419560b', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', @@ -367,7 +367,7 @@ return array( 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', - 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'c72aa091', + 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'd3506890', 'rsrc/js/application/conpherence/behavior-menu.js' => '1d45c74d', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', @@ -477,7 +477,7 @@ return array( 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', 'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2', 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', - 'rsrc/js/core/behavior-device.js' => 'b5b36110', + 'rsrc/js/core/behavior-device.js' => 'bb1dd507', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', 'rsrc/js/core/behavior-fancy-datepicker.js' => '568931f3', @@ -601,7 +601,7 @@ return array( 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', 'javelin-behavior-day-view' => '5c46cff2', 'javelin-behavior-desktop-notifications-control' => 'edd1ba66', - 'javelin-behavior-device' => 'b5b36110', + 'javelin-behavior-device' => 'bb1dd507', 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', @@ -620,7 +620,7 @@ return array( 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-drydock-live-operation-status' => '901935ef', - 'javelin-behavior-durable-column' => 'c72aa091', + 'javelin-behavior-durable-column' => 'd3506890', 'javelin-behavior-editengine-reorder-configs' => 'd7a74243', 'javelin-behavior-editengine-reorder-fields' => 'b59e1e96', 'javelin-behavior-error-log' => '6882e80a', @@ -823,7 +823,7 @@ return array( 'phui-calendar-list-css' => 'c1c7f338', 'phui-calendar-month-css' => '476be7e0', 'phui-chart-css' => '6bf6f78e', - 'phui-crumbs-view-css' => '1a1265d4', + 'phui-crumbs-view-css' => '6b813619', 'phui-curtain-view-css' => '7148ae25', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => '715aedfb', @@ -1794,13 +1794,6 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), - 'b5b36110' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-vector', - 'javelin-install', - ), 'b5c256b8' => array( 'javelin-install', 'javelin-dom', @@ -1827,6 +1820,13 @@ return array( 'javelin-install', 'javelin-workboard-card', ), + 'bb1dd507' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-vector', + 'javelin-install', + ), 'bcaccd64' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1866,16 +1866,6 @@ return array( 'c587b80f' => array( 'javelin-install', ), - 'c72aa091' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-behavior-device', - 'javelin-scrollbar', - 'javelin-quicksand', - 'phabricator-keyboard-shortcut', - 'conpherence-thread-manager', - ), 'c7ccd872' => array( 'phui-fontkit-css', ), @@ -1941,6 +1931,16 @@ return array( 'd254d646' => array( 'javelin-util', ), + 'd3506890' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-behavior-device', + 'javelin-scrollbar', + 'javelin-quicksand', + 'phabricator-keyboard-shortcut', + 'conpherence-thread-manager', + ), 'd4505101' => array( 'javelin-stratcom', 'javelin-install', diff --git a/webroot/rsrc/css/phui/phui-crumbs-view.css b/webroot/rsrc/css/phui/phui-crumbs-view.css index 6e99b70e19..b47418e240 100644 --- a/webroot/rsrc/css/phui/phui-crumbs-view.css +++ b/webroot/rsrc/css/phui/phui-crumbs-view.css @@ -20,7 +20,9 @@ text-decoration: none; } -.device-phone .phui-crumbs-view { +.device-tablet .phui-crumbs-view, +.device-phone .phui-crumbs-view, +.project-board-nav .phui-crumbs-view { padding-left: 8px; padding-right: 0; } diff --git a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js index 115daa15c9..9c3d2bba39 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js +++ b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js @@ -33,7 +33,7 @@ JX.behavior('durable-column', function(config, statics) { var columnWidth = (300 + margin); // This is the smallest window size where we'll enable the column. - var minimumViewportWidth = (768 - margin); + var minimumViewportWidth = (920 - margin); var quick = JX.$('phabricator-standard-page-body'); diff --git a/webroot/rsrc/js/core/behavior-device.js b/webroot/rsrc/js/core/behavior-device.js index bc6d6732d2..d74939a7e0 100644 --- a/webroot/rsrc/js/core/behavior-device.js +++ b/webroot/rsrc/js/core/behavior-device.js @@ -10,7 +10,7 @@ JX.install('Device', { statics : { _device : null, - _tabletBreakpoint: 768, + _tabletBreakpoint: 920, setTabletBreakpoint: function(width) { var self = JX.Device; From a91004ef1ba3bb09c3302cac3d6340e147ae7fe5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 21 May 2016 11:16:55 -0700 Subject: [PATCH 04/24] Detect timezone discrepancies and prompt users to reconcile them Summary: Ref T3025. This adds a check for different client/server timezone offsets and gives users an option to fix them or ignore them. Test Plan: - Fiddled with timezone in Settings and System Preferences. - Got appropriate prompts and behavior after simulating various trips to and from exotic locales. In particular, this slightly tricky case seems to work correctly: - Travel to NY. - Ignore discrepancy (you're only there for a couple hours for an important meeting, and returning to SF on a later flight). - Return to SF for a few days. - Travel back to NY. - You should be prompted again, since you left the timezone after you ignored the discrepancy. {F1654528} {F1654529} {F1654530} Reviewers: chad Reviewed By: chad Maniphest Tasks: T3025 Differential Revision: https://secure.phabricator.com/D15961 --- resources/celerity/packages.php | 1 + src/__phutil_library_map__.php | 2 + .../people/storage/PhabricatorUser.php | 11 ++ .../PhabricatorSettingsApplication.php | 2 + .../PhabricatorSettingsTimezoneController.php | 108 ++++++++++++++++++ .../PhabricatorDateTimeSettingsPanel.php | 4 +- .../storage/PhabricatorUserPreferences.php | 1 + src/view/page/PhabricatorStandardPageView.php | 24 ++++ .../rsrc/js/core/behavior-detect-timezone.js | 53 +++++++++ 9 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 src/applications/settings/controller/PhabricatorSettingsTimezoneController.php create mode 100644 webroot/rsrc/js/core/behavior-detect-timezone.js diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index fa19f50095..b44707f407 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -81,6 +81,7 @@ return array( 'javelin-behavior-scrollbar', 'javelin-behavior-durable-column', 'conpherence-thread-manager', + 'javelin-behavior-detect-timezone', ), 'core.pkg.css' => array( 'phabricator-core-css', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3749af3276..3dbd3f1b57 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3355,6 +3355,7 @@ phutil_register_library_map(array( 'PhabricatorSettingsMainController' => 'applications/settings/controller/PhabricatorSettingsMainController.php', 'PhabricatorSettingsMainMenuBarExtension' => 'applications/settings/extension/PhabricatorSettingsMainMenuBarExtension.php', 'PhabricatorSettingsPanel' => 'applications/settings/panel/PhabricatorSettingsPanel.php', + 'PhabricatorSettingsTimezoneController' => 'applications/settings/controller/PhabricatorSettingsTimezoneController.php', 'PhabricatorSetupCheck' => 'applications/config/check/PhabricatorSetupCheck.php', 'PhabricatorSetupCheckTestCase' => 'applications/config/check/__tests__/PhabricatorSetupCheckTestCase.php', 'PhabricatorSetupIssue' => 'applications/config/issue/PhabricatorSetupIssue.php', @@ -8065,6 +8066,7 @@ phutil_register_library_map(array( 'PhabricatorSettingsMainController' => 'PhabricatorController', 'PhabricatorSettingsMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PhabricatorSettingsPanel' => 'Phobject', + 'PhabricatorSettingsTimezoneController' => 'PhabricatorController', 'PhabricatorSetupCheck' => 'Phobject', 'PhabricatorSetupCheckTestCase' => 'PhabricatorTestCase', 'PhabricatorSetupIssue' => 'Phobject', diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index bdb2415733..dc36f55bc0 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -755,6 +755,17 @@ final class PhabricatorUser return new DateTimeZone($this->getTimezoneIdentifier()); } + public function getTimeZoneOffset() { + $timezone = $this->getTimeZone(); + $now = new DateTime('@'.PhabricatorTime::getNow()); + $offset = $timezone->getOffset($now); + + // Javascript offsets are in minutes and have the opposite sign. + $offset = -(int)($offset / 60); + + return $offset; + } + public function formatShortDateTime($when, $now = null) { if ($now === null) { $now = PhabricatorTime::getNow(); diff --git a/src/applications/settings/application/PhabricatorSettingsApplication.php b/src/applications/settings/application/PhabricatorSettingsApplication.php index d0d6494c12..66bce7205f 100644 --- a/src/applications/settings/application/PhabricatorSettingsApplication.php +++ b/src/applications/settings/application/PhabricatorSettingsApplication.php @@ -32,6 +32,8 @@ final class PhabricatorSettingsApplication extends PhabricatorApplication { '(?:(?P\d+)/)?(?:panel/(?P[^/]+)/)?' => 'PhabricatorSettingsMainController', 'adjust/' => 'PhabricatorSettingsAdjustController', + 'timezone/(?P[^/]+)/' + => 'PhabricatorSettingsTimezoneController', ), ); } diff --git a/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php b/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php new file mode 100644 index 0000000000..6af4d88eb9 --- /dev/null +++ b/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php @@ -0,0 +1,108 @@ +getViewer(); + + $client_offset = $request->getURIData('offset'); + $client_offset = (int)$client_offset; + + $timezones = DateTimeZone::listIdentifiers(); + $now = new DateTime('@'.PhabricatorTime::getNow()); + + $options = array( + 'ignore' => pht('Ignore Conflict'), + ); + + foreach ($timezones as $identifier) { + $zone = new DateTimeZone($identifier); + $offset = -($zone->getOffset($now) / 60); + if ($offset == $client_offset) { + $options[$identifier] = $identifier; + } + } + + $settings_help = pht( + 'You can change your date and time preferences in Settings.'); + + if ($request->isFormPost()) { + $timezone = $request->getStr('timezone'); + + $pref_ignore = PhabricatorUserPreferences::PREFERENCE_IGNORE_OFFSET; + + $preferences = $viewer->loadPreferences(); + + if ($timezone == 'ignore') { + $preferences + ->setPreference($pref_ignore, $client_offset) + ->save(); + + return $this->newDialog() + ->setTitle(pht('Conflict Ignored')) + ->appendParagraph( + pht( + 'The conflict between your browser and profile timezone '. + 'settings will be ignored.')) + ->appendParagraph($settings_help) + ->addCancelButton('/', pht('Done')); + } + + if (isset($options[$timezone])) { + $preferences + ->setPreference($pref_ignore, null) + ->save(); + + $viewer + ->setTimezoneIdentifier($timezone) + ->save(); + } + } + + $server_offset = $viewer->getTimeZoneOffset(); + + if ($client_offset == $server_offset) { + return $this->newDialog() + ->setTitle(pht('Timezone Calibrated')) + ->appendParagraph( + pht( + 'Your browser timezone and profile timezone are now '. + 'in agreement (%s).', + $this->formatOffset($client_offset))) + ->appendParagraph($settings_help) + ->addCancelButton('/', pht('Done')); + } + + $form = id(new AphrontFormView()) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setName('timezone') + ->setLabel(pht('Timezone')) + ->setOptions($options)); + + return $this->newDialog() + ->setTitle(pht('Adjust Timezone')) + ->appendParagraph( + pht( + 'Your browser timezone (%s) differs from your profile timezone '. + '(%s). You can ignore this conflict or adjust your profile setting '. + 'to match your client.', + $this->formatOffset($client_offset), + $this->formatOffset($server_offset))) + ->appendForm($form) + ->addCancelButton(pht('Cancel')) + ->addSubmitButton(pht('Submit')); + } + + private function formatOffset($offset) { + $offset = $offset / 60; + + if ($offset >= 0) { + return pht('GMT-%d', $offset); + } else { + return pht('GMT+%d', -$offset); + } + } + +} diff --git a/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php b/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php index 7c70089836..6628f21187 100644 --- a/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php @@ -21,6 +21,7 @@ final class PhabricatorDateTimeSettingsPanel extends PhabricatorSettingsPanel { $pref_time = PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT; $pref_date = PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT; $pref_week_start = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY; + $pref_ignore = PhabricatorUserPreferences::PREFERENCE_IGNORE_OFFSET; $preferences = $user->loadPreferences(); $errors = array(); @@ -41,7 +42,8 @@ final class PhabricatorDateTimeSettingsPanel extends PhabricatorSettingsPanel { $request->getStr($pref_date)) ->setPreference( $pref_week_start, - $request->getStr($pref_week_start)); + $request->getStr($pref_week_start)) + ->setPreference($pref_ignore, null); if (!$errors) { $preferences->save(); diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php index d8c4982ccc..18f0dfe980 100644 --- a/src/applications/settings/storage/PhabricatorUserPreferences.php +++ b/src/applications/settings/storage/PhabricatorUserPreferences.php @@ -43,6 +43,7 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO { const PREFERENCE_PROFILE_MENU_COLLAPSED = 'profile-menu.collapsed'; const PREFERENCE_FAVORITE_POLICIES = 'policy.favorites'; + const PREFERENCE_IGNORE_OFFSET = 'time.offset.ignore'; // These are in an unusual order for historic reasons. const MAILTAG_PREFERENCE_NOTIFY = 0; diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index 08f0c0b3c4..03c83f267a 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -223,6 +223,30 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView } if ($user) { + if ($user->isLoggedIn()) { + $offset = $user->getTimeZoneOffset(); + + $preferences = $user->loadPreferences(); + $ignore_key = PhabricatorUserPreferences::PREFERENCE_IGNORE_OFFSET; + + $ignore = $preferences->getPreference($ignore_key); + if (!strlen($ignore)) { + $ignore = null; + } + + Javelin::initBehavior( + 'detect-timezone', + array( + 'offset' => $offset, + 'uri' => '/settings/timezone/', + 'message' => pht( + 'Your browser timezone setting differs from the timezone '. + 'setting in your profile.'), + 'ignoreKey' => $ignore_key, + 'ignore' => $ignore, + )); + } + $default_img_uri = celerity_get_resource_uri( 'rsrc/image/icon/fatcow/document_black.png'); diff --git a/webroot/rsrc/js/core/behavior-detect-timezone.js b/webroot/rsrc/js/core/behavior-detect-timezone.js new file mode 100644 index 0000000000..fa0a8e96a7 --- /dev/null +++ b/webroot/rsrc/js/core/behavior-detect-timezone.js @@ -0,0 +1,53 @@ +/** + * @provides javelin-behavior-detect-timezone + * @requires javelin-behavior + * javelin-uri + * phabricator-notification + */ + +JX.behavior('detect-timezone', function(config) { + + var offset = new Date().getTimezoneOffset(); + var ignore = config.ignore; + + if (ignore !== null) { + // If we're ignoring a client offset and it's the current offset, just + // bail. This means the user has chosen to ignore the clock difference + // between the current client setting and their server setting. + if (offset == ignore) { + return; + } + + // If we're ignoring a client offset but the current offset is different, + // wipe the offset. If you go from SF to NY, ignore the difference, return + // to SF, then travel back to NY a few months later, we want to prompt you + // again. This code will clear the ignored setting upon your return to SF. + new JX.Request('/settings/adjust/', JX.bag) + .setData({key: config.ignoreKey, value: ''}) + .send(); + + ignore = null; + } + + // If the client and server clocks are in sync, we're all set. + if (offset == config.offset) { + return; + } + + var notification = new JX.Notification() + .alterClassName('jx-notification-alert', true) + .setContent(config.message) + .setDuration(0); + + notification.listen('activate', function() { + JX.Stratcom.context().kill(); + notification.hide(); + + var uri = config.uri + offset + '/'; + + new JX.Workflow(uri) + .start(); + }); + + notification.show(); +}); From 8b9df5e90f014ebc2bfcb4a20d95107c86177106 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 21 May 2016 13:27:56 -0700 Subject: [PATCH 05/24] Update Celerity map. --- resources/celerity/map.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1cf9e506ee..d821f0230a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => '8aeacc63', - 'core.pkg.js' => '437c65c2', + 'core.pkg.js' => '7b44c14f', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '33da0633', 'differential.pkg.js' => '4b7d8f19', @@ -477,6 +477,7 @@ return array( 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', 'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2', 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', + 'rsrc/js/core/behavior-detect-timezone.js' => 'ae9f2ec9', 'rsrc/js/core/behavior-device.js' => 'bb1dd507', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', @@ -601,6 +602,7 @@ return array( 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', 'javelin-behavior-day-view' => '5c46cff2', 'javelin-behavior-desktop-notifications-control' => 'edd1ba66', + 'javelin-behavior-detect-timezone' => 'ae9f2ec9', 'javelin-behavior-device' => 'bb1dd507', 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 'javelin-behavior-differential-comment-jump' => '4fdb476d', @@ -1742,6 +1744,11 @@ return array( 'javelin-util', 'phabricator-busy', ), + 'ae9f2ec9' => array( + 'javelin-behavior', + 'javelin-uri', + 'phabricator-notification', + ), 'b003d4fb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2332,6 +2339,7 @@ return array( 'javelin-behavior-scrollbar', 'javelin-behavior-durable-column', 'conpherence-thread-manager', + 'javelin-behavior-detect-timezone', ), 'darkconsole.pkg.js' => array( 'javelin-behavior-dark-console', From 5d30ea56cf5e58f8e0a4af6af315d31f4b31407e Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 21 May 2016 10:04:37 -0700 Subject: [PATCH 06/24] Add a modern `user.search` Conduit API method Summary: Ref T10512. This is fairly bare-bones but appears to work. Test Plan: Queried all users, queried some stuff by constraints. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10512 Differential Revision: https://secure.phabricator.com/D15959 --- src/__phutil_library_map__.php | 3 + .../conduit/UserSearchConduitAPIMethod.php | 18 +++++ .../query/PhabricatorPeopleSearchEngine.php | 52 ++++++++++---- .../people/storage/PhabricatorUser.php | 67 ++++++++++++++++++- .../PhabricatorSearchEngineAPIMethod.php | 1 + .../PhabricatorSearchThreeStateField.php | 4 ++ 6 files changed, 132 insertions(+), 13 deletions(-) create mode 100644 src/applications/people/conduit/UserSearchConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3dbd3f1b57..74d01f946b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4135,6 +4135,7 @@ phutil_register_library_map(array( 'UserEnableConduitAPIMethod' => 'applications/people/conduit/UserEnableConduitAPIMethod.php', 'UserFindConduitAPIMethod' => 'applications/people/conduit/UserFindConduitAPIMethod.php', 'UserQueryConduitAPIMethod' => 'applications/people/conduit/UserQueryConduitAPIMethod.php', + 'UserSearchConduitAPIMethod' => 'applications/people/conduit/UserSearchConduitAPIMethod.php', 'UserWhoAmIConduitAPIMethod' => 'applications/people/conduit/UserWhoAmIConduitAPIMethod.php', ), 'function' => array( @@ -8313,6 +8314,7 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFulltextInterface', + 'PhabricatorConduitResultInterface', ), 'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField', 'PhabricatorUserCardView' => 'AphrontTagView', @@ -9028,6 +9030,7 @@ phutil_register_library_map(array( 'UserEnableConduitAPIMethod' => 'UserConduitAPIMethod', 'UserFindConduitAPIMethod' => 'UserConduitAPIMethod', 'UserQueryConduitAPIMethod' => 'UserConduitAPIMethod', + 'UserSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'UserWhoAmIConduitAPIMethod' => 'UserConduitAPIMethod', ), )); diff --git a/src/applications/people/conduit/UserSearchConduitAPIMethod.php b/src/applications/people/conduit/UserSearchConduitAPIMethod.php new file mode 100644 index 0000000000..5d007386a9 --- /dev/null +++ b/src/applications/people/conduit/UserSearchConduitAPIMethod.php @@ -0,0 +1,18 @@ +setLabel(pht('Usernames')) ->setKey('usernames') - ->setAliases(array('username')), + ->setAliases(array('username')) + ->setDescription(pht('Find users by exact username.')), id(new PhabricatorSearchTextField()) ->setLabel(pht('Name Contains')) - ->setKey('nameLike'), + ->setKey('nameLike') + ->setDescription( + pht('Find users whose usernames contain a substring.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Administrators')) ->setKey('isAdmin') ->setOptions( pht('(Show All)'), pht('Show Only Administrators'), - pht('Hide Administrators')), + pht('Hide Administrators')) + ->setDescription( + pht( + 'Pass true to find only administrators, or false to omit '. + 'administrators.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Disabled')) ->setKey('isDisabled') ->setOptions( pht('(Show All)'), pht('Show Only Disabled Users'), - pht('Hide Disabled Users')), + pht('Hide Disabled Users')) + ->setDescription( + pht( + 'Pass true to find only disabled users, or false to omit '. + 'disabled users.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Bots')) - ->setKey('isSystemAgent') + ->setKey('isBot') + ->setAliases(array('isSystemAgent')) ->setOptions( pht('(Show All)'), pht('Show Only Bots'), - pht('Hide Bots')), + pht('Hide Bots')) + ->setDescription( + pht( + 'Pass true to find only bots, or false to omit bots.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Mailing Lists')) ->setKey('isMailingList') ->setOptions( pht('(Show All)'), pht('Show Only Mailing Lists'), - pht('Hide Mailing Lists')), + pht('Hide Mailing Lists')) + ->setDescription( + pht( + 'Pass true to find only mailing lists, or false to omit '. + 'mailing lists.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Needs Approval')) ->setKey('needsApproval') ->setOptions( pht('(Show All)'), pht('Show Only Unapproved Users'), - pht('Hide Unappproved Users')), + pht('Hide Unappproved Users')) + ->setDescription( + pht( + 'Pass true to find only users awaiting administrative approval, '. + 'or false to omit these users.')), id(new PhabricatorSearchDateField()) ->setKey('createdStart') - ->setLabel(pht('Joined After')), + ->setLabel(pht('Joined After')) + ->setDescription( + pht('Find user accounts created after a given time.')), id(new PhabricatorSearchDateField()) ->setKey('createdEnd') - ->setLabel(pht('Joined Before')), + ->setLabel(pht('Joined Before')) + ->setDescription( + pht('Find user accounts created before a given time.')), + ); } @@ -115,8 +143,8 @@ final class PhabricatorPeopleSearchEngine $query->withIsMailingList($map['isMailingList']); } - if ($map['isSystemAgent'] !== null) { - $query->withIsSystemAgent($map['isSystemAgent']); + if ($map['isBot'] !== null) { + $query->withIsSystemAgent($map['isBot']); } if ($map['needsApproval'] !== null) { diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index dc36f55bc0..464c14492f 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -16,7 +16,8 @@ final class PhabricatorUser PhabricatorSSHPublicKeyInterface, PhabricatorFlaggableInterface, PhabricatorApplicationTransactionInterface, - PhabricatorFulltextInterface { + PhabricatorFulltextInterface, + PhabricatorConduitResultInterface { const SESSION_TABLE = 'phabricator_session'; const NAMETOKEN_TABLE = 'user_nametoken'; @@ -1389,4 +1390,68 @@ final class PhabricatorUser return new PhabricatorUserFulltextEngine(); } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('username') + ->setType('string') + ->setDescription(pht("The user's username.")), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('realName') + ->setType('string') + ->setDescription(pht("The user's real name.")), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('roles') + ->setType('list') + ->setDescription(pht('List of acccount roles.')), + ); + } + + public function getFieldValuesForConduit() { + $roles = array(); + + if ($this->getIsDisabled()) { + $roles[] = 'disabled'; + } + + if ($this->getIsSystemAgent()) { + $roles[] = 'bot'; + } + + if ($this->getIsMailingList()) { + $roles[] = 'list'; + } + + if ($this->getIsAdmin()) { + $roles[] = 'admin'; + } + + if ($this->getIsEmailVerified()) { + $roles[] = 'verified'; + } + + if ($this->getIsApproved()) { + $roles[] = 'approved'; + } + + if ($this->isUserActivated()) { + $roles[] = 'activated'; + } + + return array( + 'username' => $this->getUsername(), + 'realName' => $this->getRealName(), + 'roles' => $roles, + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + } diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php index d1e8e5f3f8..f0e4f02672 100644 --- a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php +++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php @@ -501,6 +501,7 @@ EOTEXT } $table = id(new AphrontTableView($rows)) + ->setNoDataString(pht('This call does not support any attachments.')) ->setHeaders( array( pht('Key'), diff --git a/src/applications/search/field/PhabricatorSearchThreeStateField.php b/src/applications/search/field/PhabricatorSearchThreeStateField.php index f7e7a80c5b..e2e899cb94 100644 --- a/src/applications/search/field/PhabricatorSearchThreeStateField.php +++ b/src/applications/search/field/PhabricatorSearchThreeStateField.php @@ -45,4 +45,8 @@ final class PhabricatorSearchThreeStateField return null; } + protected function newConduitParameterType() { + return new ConduitBoolParameterType(); + } + } From 2a00f185eb2f938917b79f8b372696bf913d01c1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 22 May 2016 06:50:12 -0700 Subject: [PATCH 07/24] When the JS "Intl" API is available, use it to guess the timezone Summary: Ref T3025. Chrome gives us an easily-accessible, much better guess at which timezone the user is in. Firefox also exposes "Intl" but this doesn't seem to be a reliable method to read the timezone. Test Plan: In Chrome, swapped my system date/time between zones, clicked the "reconcile" popup, got the dropdown prefilled accurately. In Safari (no `Intl` API) got the normal flow with no default selected. Reviewers: chad Reviewed By: chad Maniphest Tasks: T3025 Differential Revision: https://secure.phabricator.com/D15962 --- resources/celerity/map.php | 16 ++++++++-------- .../PhabricatorSettingsTimezoneController.php | 10 +++++++++- webroot/rsrc/js/core/behavior-detect-timezone.js | 12 ++++++++++++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d821f0230a..fbf4fe03d2 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => '8aeacc63', - 'core.pkg.js' => '7b44c14f', + 'core.pkg.js' => '50e9228e', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '33da0633', 'differential.pkg.js' => '4b7d8f19', @@ -477,7 +477,7 @@ return array( 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', 'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2', 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', - 'rsrc/js/core/behavior-detect-timezone.js' => 'ae9f2ec9', + 'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96', 'rsrc/js/core/behavior-device.js' => 'bb1dd507', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', @@ -602,7 +602,7 @@ return array( 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', 'javelin-behavior-day-view' => '5c46cff2', 'javelin-behavior-desktop-notifications-control' => 'edd1ba66', - 'javelin-behavior-detect-timezone' => 'ae9f2ec9', + 'javelin-behavior-detect-timezone' => '4c193c96', 'javelin-behavior-device' => 'bb1dd507', 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 'javelin-behavior-differential-comment-jump' => '4fdb476d', @@ -1226,6 +1226,11 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), + '4c193c96' => array( + 'javelin-behavior', + 'javelin-uri', + 'phabricator-notification', + ), '4e3e79a6' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1744,11 +1749,6 @@ return array( 'javelin-util', 'phabricator-busy', ), - 'ae9f2ec9' => array( - 'javelin-behavior', - 'javelin-uri', - 'phabricator-notification', - ), 'b003d4fb' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php b/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php index 6af4d88eb9..128ba17226 100644 --- a/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php +++ b/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php @@ -74,12 +74,20 @@ final class PhabricatorSettingsTimezoneController ->addCancelButton('/', pht('Done')); } + // If we have a guess at the timezone from the client, select it as the + // default. + $guess = $request->getStr('guess'); + if (empty($options[$guess])) { + $guess = 'ignore'; + } + $form = id(new AphrontFormView()) ->appendChild( id(new AphrontFormSelectControl()) ->setName('timezone') ->setLabel(pht('Timezone')) - ->setOptions($options)); + ->setOptions($options) + ->setValue($guess)); return $this->newDialog() ->setTitle(pht('Adjust Timezone')) diff --git a/webroot/rsrc/js/core/behavior-detect-timezone.js b/webroot/rsrc/js/core/behavior-detect-timezone.js index fa0a8e96a7..29c565b9f4 100644 --- a/webroot/rsrc/js/core/behavior-detect-timezone.js +++ b/webroot/rsrc/js/core/behavior-detect-timezone.js @@ -45,6 +45,18 @@ JX.behavior('detect-timezone', function(config) { var uri = config.uri + offset + '/'; + // Some browsers (notably, Chrome) expose an "Intl" API which gives us + // direct access to a timezone setting. If we are able to read this, use + // it to guess which timezone the user is in so we can prefill the + // dropdown. + try { + var guess = Intl.DateTimeFormat().resolvedOptions().timeZone; + uri = JX.$U(uri).setQueryParam('guess', guess); + } catch (error) { + // Ignore any errors here, we'll just make the user pick from the big + // list. + } + new JX.Workflow(uri) .start(); }); From 3d3fff4991ab921853fd0d1fc96f8b1e65b962d7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 22 May 2016 09:54:41 -0700 Subject: [PATCH 08/24] Fix weird remarkup linewrapping on a few instructions forms, plus move toward fixing Phame/CORGI remarkup issues Summary: Fixes T10381. When we converted to `PHUIRemarkupView`, some instructional text got linebreaks added when it shouldn't have them (the source is written in PHP and wrapped at 80 characters, but the output should flow naturally). Fix this so we don't preserve linebreaks. This also makes `PHUIRemarkupView` a little more powerful and inches us toward fixing Phame/CORGI remarkup issues, getting rid of `PhabricatorMarkupInterface` / `PhabricatorMarkupOneOff`, and dropping all the application hard-coding in `PhabricatorMarkupEngine`. Test Plan: - Grepped for all callsites, looking for callsites which accept remarkup written in `<<setRemarkupOptions( + array( + PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS => false, + )); + return id(new PHUIBoxView()) ->appendChild($view) ->addPadding(PHUI::PADDING_LARGE); diff --git a/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php b/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php index b0abe516de..d21e2105fb 100644 --- a/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php +++ b/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php @@ -313,7 +313,14 @@ EOTEXT protected function renderInstructions($corpus) { $viewer = $this->getUser(); - return new PHUIRemarkupView($viewer, $corpus); + $view = new PHUIRemarkupView($viewer, $corpus); + + $view->setRemarkupOptions( + array( + PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS => false, + )); + + return $view; } } diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php index 92e027f86d..dc3809f873 100644 --- a/src/infrastructure/markup/PhabricatorMarkupEngine.php +++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -470,6 +470,7 @@ final class PhabricatorMarkupEngine extends Phobject { $engine = new PhutilRemarkupEngine(); $engine->setConfig('preserve-linebreaks', $options['preserve-linebreaks']); + $engine->setConfig('pygments.enabled', $options['pygments']); $engine->setConfig( 'uri.allowed-protocols', diff --git a/src/infrastructure/markup/PhabricatorMarkupOneOff.php b/src/infrastructure/markup/PhabricatorMarkupOneOff.php index 0bfba1722f..6bffcf7e2b 100644 --- a/src/infrastructure/markup/PhabricatorMarkupOneOff.php +++ b/src/infrastructure/markup/PhabricatorMarkupOneOff.php @@ -10,6 +10,7 @@ final class PhabricatorMarkupOneOff private $content; private $preserveLinebreaks; private $engineRuleset; + private $engine; private $disableCache; public function setEngineRuleset($engine_ruleset) { @@ -35,6 +36,15 @@ final class PhabricatorMarkupOneOff return $this->content; } + public function setEngine(PhutilMarkupEngine $engine) { + $this->engine = $engine; + return $this; + } + + public function getEngine() { + return $this->engine; + } + public function setDisableCache($disable_cache) { $this->disableCache = $disable_cache; return $this; @@ -49,6 +59,10 @@ final class PhabricatorMarkupOneOff } public function newMarkupEngine($field) { + if ($this->engine) { + return $this->engine; + } + if ($this->engineRuleset) { return PhabricatorMarkupEngine::getEngine($this->engineRuleset); } else if ($this->preserveLinebreaks) { diff --git a/src/infrastructure/markup/view/PHUIRemarkupView.php b/src/infrastructure/markup/view/PHUIRemarkupView.php index 0a07e64a87..e30c09ce7c 100644 --- a/src/infrastructure/markup/view/PHUIRemarkupView.php +++ b/src/infrastructure/markup/view/PHUIRemarkupView.php @@ -12,21 +12,19 @@ final class PHUIRemarkupView extends AphrontView { private $corpus; - private $markupType; private $contextObject; + private $options; - const DOCUMENT = 'document'; + // TODO: In the long run, rules themselves should define available options. + // For now, just define constants here so we can more easily replace things + // later once this is cleaned up. + const OPTION_PRESERVE_LINEBREAKS = 'preserve-linebreaks'; public function __construct(PhabricatorUser $viewer, $corpus) { $this->setUser($viewer); $this->corpus = $corpus; } - private function setMarkupType($type) { - $this->markupType($type); - return $this; - } - public function setContextObject($context_object) { $this->contextObject = $context_object; return $this; @@ -36,29 +34,63 @@ final class PHUIRemarkupView extends AphrontView { return $this->contextObject; } + public function setRemarkupOption($key, $value) { + $this->options[$key] = $value; + return $this; + } + + public function setRemarkupOptions(array $options) { + foreach ($options as $key => $value) { + $this->setRemarkupOption($key, $value); + } + return $this; + } + public function render() { - $viewer = $this->getUser(); + $viewer = $this->getViewer(); $corpus = $this->corpus; $context = $this->getContextObject(); + $options = $this->options; + + $oneoff = id(new PhabricatorMarkupOneOff()) + ->setContent($corpus); + + if ($options) { + $oneoff->setEngine($this->getEngine()); + } else { + $oneoff->setPreserveLinebreaks(true); + } + $content = PhabricatorMarkupEngine::renderOneObject( - id(new PhabricatorMarkupOneOff()) - ->setPreserveLinebreaks(true) - ->setContent($corpus), + $oneoff, 'default', $viewer, $context); - if ($this->markupType == self::DOCUMENT) { - return phutil_tag( - 'div', - array( - 'class' => 'phabricator-remarkup phui-document-view', - ), - $content); - } - return $content; } + private function getEngine() { + $options = $this->options; + $viewer = $this->getViewer(); + + $viewer_key = $viewer->getCacheFragment(); + + ksort($options); + $engine_key = serialize($options); + $engine_key = PhabricatorHash::digestForIndex($engine_key); + + $cache = PhabricatorCaches::getRequestCache(); + $cache_key = "remarkup.engine({$viewer}, {$engine_key})"; + + $engine = $cache->getKey($cache_key); + if (!$engine) { + $engine = PhabricatorMarkupEngine::newMarkupEngine($options); + $cache->setKey($cache_key, $engine); + } + + return $engine; + } + } diff --git a/src/view/form/AphrontFormView.php b/src/view/form/AphrontFormView.php index ecd4c1206e..3c92f72c88 100644 --- a/src/view/form/AphrontFormView.php +++ b/src/view/form/AphrontFormView.php @@ -84,9 +84,20 @@ final class AphrontFormView extends AphrontView { } public function appendRemarkupInstructions($remarkup) { - return $this->appendInstructions( - new PHUIRemarkupView($this->getViewer(), $remarkup)); + $view = $this->newInstructionsRemarkupView($remarkup); + return $this->appendInstructions($view); + } + public function newInstructionsRemarkupView($remarkup) { + $viewer = $this->getViewer(); + $view = new PHUIRemarkupView($viewer, $remarkup); + + $view->setRemarkupOptions( + array( + PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS => false, + )); + + return $view; } public function buildLayoutView() { diff --git a/src/view/form/PHUIFormLayoutView.php b/src/view/form/PHUIFormLayoutView.php index ffc0eb31d5..682c6c62cf 100644 --- a/src/view/form/PHUIFormLayoutView.php +++ b/src/view/form/PHUIFormLayoutView.php @@ -31,14 +31,11 @@ final class PHUIFormLayoutView extends AphrontView { } public function appendRemarkupInstructions($remarkup) { - if ($this->getUser() === null) { - throw new PhutilInvalidStateException('setUser'); - } + $view = id(new AphrontFormView()) + ->setViewer($this->getViewer()) + ->newInstructionsRemarkupView($remarkup); - $viewer = $this->getUser(); - $instructions = new PHUIRemarkupView($viewer, $remarkup); - - return $this->appendInstructions($instructions); + return $this->appendInstructions($view); } public function render() { From efd001b42fd524f254d22cfd0b44a68b8a19330c Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 May 2016 10:25:51 -0700 Subject: [PATCH 09/24] Wordsmith the timezone selection UX Summary: Ref T3025. - Show current zone to make the current vs new more clear. - Tweak some text. Test Plan: {F1656534} Reviewers: chad Reviewed By: chad Maniphest Tasks: T3025 Differential Revision: https://secure.phabricator.com/D15965 --- .../PhabricatorSettingsTimezoneController.php | 12 ++++++++++-- src/view/page/PhabricatorStandardPageView.php | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php b/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php index 128ba17226..a637401f1f 100644 --- a/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php +++ b/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php @@ -81,16 +81,24 @@ final class PhabricatorSettingsTimezoneController $guess = 'ignore'; } + $current_zone = $viewer->getTimezoneIdentifier(); + $current_zone = phutil_tag('strong', array(), $current_zone); + $form = id(new AphrontFormView()) + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Current Setting')) + ->setValue($current_zone)) ->appendChild( id(new AphrontFormSelectControl()) ->setName('timezone') - ->setLabel(pht('Timezone')) + ->setLabel(pht('New Setting')) ->setOptions($options) ->setValue($guess)); return $this->newDialog() ->setTitle(pht('Adjust Timezone')) + ->setWidth(AphrontDialogView::WIDTH_FORM) ->appendParagraph( pht( 'Your browser timezone (%s) differs from your profile timezone '. @@ -100,7 +108,7 @@ final class PhabricatorSettingsTimezoneController $this->formatOffset($server_offset))) ->appendForm($form) ->addCancelButton(pht('Cancel')) - ->addSubmitButton(pht('Submit')); + ->addSubmitButton(pht('Change Timezone')); } private function formatOffset($offset) { diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index 03c83f267a..25779dad98 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -241,7 +241,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView 'uri' => '/settings/timezone/', 'message' => pht( 'Your browser timezone setting differs from the timezone '. - 'setting in your profile.'), + 'setting in your profile, click to reconcile.'), 'ignoreKey' => $ignore_key, 'ignore' => $ignore, )); From de645301b57f9e62e47f29ac331c7d5a69416e1d Mon Sep 17 00:00:00 2001 From: lkassianik Date: Thu, 5 May 2016 09:14:07 -0700 Subject: [PATCH 10/24] Adding a calendar preview panel to people profile Summary: Ref T9606 Test Plan: Open people profile for a user with events today/tomorrow, see a panel under badges panel with event list Reviewers: chad, epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Maniphest Tasks: T9606 Differential Revision: https://secure.phabricator.com/D15851 --- resources/celerity/map.php | 4 +- src/__phutil_library_map__.php | 2 + ...PhabricatorPeopleProfileViewController.php | 65 ++++++++++ .../phui/calendar/PHUICalendarDayView.php | 91 +------------ .../phui/calendar/PHUICalendarWeekView.php | 121 ++++++++++++++++++ .../css/phui/calendar/phui-calendar-list.css | 4 - 6 files changed, 195 insertions(+), 92 deletions(-) create mode 100644 src/view/phui/calendar/PHUICalendarWeekView.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index fbf4fe03d2..cfdc45980f 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -116,7 +116,7 @@ return array( 'rsrc/css/layout/phabricator-side-menu-view.css' => 'dd849797', 'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983', 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93', - 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1c7f338', + 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'e0866209', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '476be7e0', 'rsrc/css/phui/calendar/phui-calendar.css' => 'ccabe893', 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', @@ -822,7 +822,7 @@ return array( 'phui-button-css' => 'a64a8de6', 'phui-calendar-css' => 'ccabe893', 'phui-calendar-day-css' => 'd1cf6f93', - 'phui-calendar-list-css' => 'c1c7f338', + 'phui-calendar-list-css' => 'e0866209', 'phui-calendar-month-css' => '476be7e0', 'phui-chart-css' => '6bf6f78e', 'phui-crumbs-view-css' => '6b813619', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 74d01f946b..a54f840afb 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1569,6 +1569,7 @@ phutil_register_library_map(array( 'PHUICalendarDayView' => 'view/phui/calendar/PHUICalendarDayView.php', 'PHUICalendarListView' => 'view/phui/calendar/PHUICalendarListView.php', 'PHUICalendarMonthView' => 'view/phui/calendar/PHUICalendarMonthView.php', + 'PHUICalendarWeekView' => 'view/phui/calendar/PHUICalendarWeekView.php', 'PHUICalendarWidgetView' => 'view/phui/calendar/PHUICalendarWidgetView.php', 'PHUIColorPalletteExample' => 'applications/uiexample/examples/PHUIColorPalletteExample.php', 'PHUICrumbView' => 'view/phui/PHUICrumbView.php', @@ -5974,6 +5975,7 @@ phutil_register_library_map(array( 'PHUICalendarDayView' => 'AphrontView', 'PHUICalendarListView' => 'AphrontTagView', 'PHUICalendarMonthView' => 'AphrontView', + 'PHUICalendarWeekView' => 'AphrontView', 'PHUICalendarWidgetView' => 'AphrontTagView', 'PHUIColorPalletteExample' => 'PhabricatorUIExample', 'PHUICrumbView' => 'AphrontView', diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index f77933df90..170eabcad0 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -59,6 +59,7 @@ final class PhabricatorPeopleProfileViewController $projects = $this->buildProjectsView($user); $badges = $this->buildBadgesView($user); + $calendar = $this->buildCalendarDayView($user); require_celerity_resource('project-view-css'); $home = id(new PHUITwoColumnView()) @@ -73,6 +74,7 @@ final class PhabricatorPeopleProfileViewController array( $projects, $badges, + $calendar, )); $nav = $this->getProfileMenu(); @@ -172,6 +174,69 @@ final class PhabricatorPeopleProfileViewController return $box; } + private function buildCalendarDayView(PhabricatorUser $user) { + $viewer = $this->getViewer(); + $class = 'PhabricatorCalendarApplication'; + + if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) { + return null; + } + + $midnight = PhabricatorTime::getTodayMidnightDateTime($viewer); + $week_end = clone $midnight; + $week_end = $week_end->modify('+3 days'); + + $range_start = $midnight->format('U'); + $range_end = $week_end->format('U'); + + $query = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withDateRange($range_start, $range_end) + ->withInvitedPHIDs(array($viewer->getPHID())) + ->withIsCancelled(false); + + $statuses = $query->execute(); + $phids = mpull($statuses, 'getUserPHID'); + $events = array(); + + foreach ($statuses as $status) { + $viewer_is_invited = $status->getIsUserInvited($viewer->getPHID()); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $status, + PhabricatorPolicyCapability::CAN_EDIT); + + $event = new AphrontCalendarEventView(); + $event->setCanEdit($can_edit); + $event->setEventID($status->getID()); + $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); + $event->setIsAllDay($status->getIsAllDay()); + $event->setIcon($status->getIcon()); + $event->setViewerIsInvited($viewer_is_invited); + + $event->setName($status->getName()); + $event->setURI($status->getURI()); + $events[] = $event; + } + + $events = msort($events, 'getEpochStart'); + $day_view = id(new PHUICalendarWeekView()) + ->setViewer($viewer) + ->setEvents($events) + ->setWeekLength(3) + ->render(); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Calendar')); + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($day_view) + ->setBackground(PHUIObjectBoxView::GREY); + + return $box; + } + private function buildBadgesView(PhabricatorUser $user) { $viewer = $this->getViewer(); diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index d71554abe5..d7b5f9bda2 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -145,7 +145,11 @@ final class PHUICalendarDayView extends AphrontView { } $header = $this->renderDayViewHeader(); - $sidebar = $this->renderSidebar(); + $sidebar = id(new PHUICalendarWeekView()) + ->setViewer($this->getViewer()) + ->setEvents($this->events) + ->setDateTime($this->getDateTime()) + ->render(); $warnings = $this->getQueryRangeWarning(); $table_id = celerity_generate_unique_node_id(); @@ -242,91 +246,6 @@ final class PHUICalendarDayView extends AphrontView { return $errors; } - private function renderSidebar() { - $this->events = msort($this->events, 'getEpochStart'); - $week_of_boxes = $this->getWeekOfBoxes(); - $filled_boxes = array(); - - foreach ($week_of_boxes as $day_box) { - $box_start = $day_box['start']; - $box_end = id(clone $box_start)->modify('+1 day'); - - $box_start = $box_start->format('U'); - $box_end = $box_end->format('U'); - - $box_events = array(); - - foreach ($this->events as $event) { - $event_start = $event->getEpochStart(); - $event_end = $event->getEpochEnd(); - - if ($event_start < $box_end && $event_end > $box_start) { - $box_events[] = $event; - } - } - - $filled_boxes[] = $this->renderSidebarBox( - $box_events, - $day_box['title']); - } - - return $filled_boxes; - } - - private function renderSidebarBox($events, $title) { - $widget = id(new PHUICalendarWidgetView()) - ->addClass('calendar-day-view-sidebar'); - - $list = id(new PHUICalendarListView()) - ->setUser($this->getViewer()) - ->setView('day'); - - if (count($events) == 0) { - $list->showBlankState(true); - } else { - $sorted_events = msort($events, 'getEpochStart'); - foreach ($sorted_events as $event) { - $list->addEvent($event); - } - } - - $widget - ->setCalendarList($list) - ->setHeader($title); - return $widget; - } - - private function getWeekOfBoxes() { - $sidebar_day_boxes = array(); - - $display_start_day = $this->getDateTime(); - $display_end_day = id(clone $display_start_day)->modify('+6 day'); - - $box_start_time = clone $display_start_day; - - $today_time = PhabricatorTime::getTodayMidnightDateTime($this->getViewer()); - $tomorrow_time = clone $today_time; - $tomorrow_time->modify('+1 day'); - - while ($box_start_time <= $display_end_day) { - if ($box_start_time == $today_time) { - $title = pht('Today'); - } else if ($box_start_time == $tomorrow_time) { - $title = pht('Tomorrow'); - } else { - $title = $box_start_time->format('l'); - } - - $sidebar_day_boxes[] = array( - 'title' => $title, - 'start' => clone $box_start_time, - ); - - $box_start_time->modify('+1 day'); - } - return $sidebar_day_boxes; - } - private function renderDayViewHeader() { $button_bar = null; $uri = $this->getBrowseURI(); diff --git a/src/view/phui/calendar/PHUICalendarWeekView.php b/src/view/phui/calendar/PHUICalendarWeekView.php new file mode 100644 index 0000000000..bcfc3666c1 --- /dev/null +++ b/src/view/phui/calendar/PHUICalendarWeekView.php @@ -0,0 +1,121 @@ +events = $events; + return $this; + } + + public function setDateTime($date_time) { + $this->dateTime = $date_time; + return $this; + } + + private function getDateTime() { + if ($this->dateTime) { + return $this->dateTime; + } + return $this->getDefaultDateTime(); + } + + public function setWeekLength($week_length) { + $this->weekLength = $week_length; + return $this; + } + + public function render() { + $this->events = msort($this->events, 'getEpochStart'); + $week_of_boxes = $this->getWeekOfBoxes(); + $filled_boxes = array(); + + foreach ($week_of_boxes as $day_box) { + $box_start = $day_box['start']; + $box_end = id(clone $box_start)->modify('+1 day'); + + $box_start = $box_start->format('U'); + $box_end = $box_end->format('U'); + + $box_events = array(); + + foreach ($this->events as $event) { + $event_start = $event->getEpochStart(); + $event_end = $event->getEpochEnd(); + + if ($event_start < $box_end && $event_end > $box_start) { + $box_events[] = $event; + } + } + + $filled_boxes[] = $this->renderSidebarBox( + $box_events, + $day_box['title']); + } + + return $filled_boxes; + } + + private function renderSidebarBox($events, $title) { + $widget = id(new PHUICalendarWidgetView()) + ->addClass('calendar-day-view-sidebar'); + + $list = id(new PHUICalendarListView()) + ->setUser($this->getViewer()) + ->setView('day'); + + if (count($events) == 0) { + $list->showBlankState(true); + } else { + $sorted_events = msort($events, 'getEpochStart'); + foreach ($sorted_events as $event) { + $list->addEvent($event); + } + } + + $widget + ->setCalendarList($list) + ->setHeader($title); + return $widget; + } + + private function getWeekOfBoxes() { + $day_boxes = array(); + $week_length = $this->weekLength - 1; + + $display_start_day = $this->getDateTime(); + $display_end_day = id(clone $display_start_day) + ->modify('+'.$week_length.' day'); + + $box_start_time = clone $display_start_day; + + $today_time = PhabricatorTime::getTodayMidnightDateTime($this->getViewer()); + $tomorrow_time = clone $today_time; + $tomorrow_time->modify('+1 day'); + + while ($box_start_time <= $display_end_day) { + if ($box_start_time == $today_time) { + $title = pht('Today'); + } else if ($box_start_time == $tomorrow_time) { + $title = pht('Tomorrow'); + } else { + $title = $box_start_time->format('l'); + } + + $day_boxes[] = array( + 'title' => $title, + 'start' => clone $box_start_time, + ); + + $box_start_time->modify('+1 day'); + } + return $day_boxes; + } + + private function getDefaultDateTime() { + return PhabricatorTime::getTodayMidnightDateTime($this->getViewer()); + } + +} diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-list.css b/webroot/rsrc/css/phui/calendar/phui-calendar-list.css index c9bf7023d1..f34fa3cb73 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-list.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-list.css @@ -2,10 +2,6 @@ * @provides phui-calendar-list-css */ -.phui-calendar-list-container { - width: 300px; -} - .device-phone .phui-calendar-list-container { width: auto; } From 359e8d4aa59e5940235e9b321352f0ef5e1f84b1 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Mon, 23 May 2016 10:24:02 -0700 Subject: [PATCH 11/24] Hover hint on calendar list items should appear on the most convenient side of the item Summary: Hover hint on calendar list items should be to the right in day view, left in profile view, on top in month view Test Plan: Open profile view, calendar items should have a left hover. Open day view, calendar items should have a right hover. Open month view, calendar items should have top hover. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Maniphest Tasks: T9606 Differential Revision: https://secure.phabricator.com/D15964 --- .../PhabricatorPeopleProfileViewController.php | 1 + src/view/phui/calendar/PHUICalendarListView.php | 8 +++++++- src/view/phui/calendar/PHUICalendarMonthView.php | 5 +++-- src/view/phui/calendar/PHUICalendarWeekView.php | 12 +++++++++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index 170eabcad0..323d5f66af 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -223,6 +223,7 @@ final class PhabricatorPeopleProfileViewController $events = msort($events, 'getEpochStart'); $day_view = id(new PHUICalendarWeekView()) ->setViewer($viewer) + ->setView('week') ->setEvents($events) ->setWeekLength(3) ->render(); diff --git a/src/view/phui/calendar/PHUICalendarListView.php b/src/view/phui/calendar/PHUICalendarListView.php index 9f9bc86a80..fc2929adae 100644 --- a/src/view/phui/calendar/PHUICalendarListView.php +++ b/src/view/phui/calendar/PHUICalendarListView.php @@ -85,7 +85,13 @@ final class PHUICalendarListView extends AphrontTagView { } $tip = $this->getEventTooltip($event); - $tip_align = ($this->getView() == 'day') ? 'E' : 'N'; + if ($this->getView() == 'day') { + $tip_align = 'E'; + } else if ($this->getView() == 'month') { + $tip_align = 'N'; + } else { + $tip_align = 'W'; + } $content = javelin_tag( 'a', array( diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index d40736494e..396947cdbe 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -90,8 +90,9 @@ final class PHUICalendarMonthView extends AphrontView { $max_daily = 15; $counter = 0; - $list = new PHUICalendarListView(); - $list->setViewer($viewer); + $list = id(new PHUICalendarListView()) + ->setViewer($viewer) + ->setView('month'); foreach ($all_day_events as $item) { if ($counter <= $max_daily) { $list->addEvent($item); diff --git a/src/view/phui/calendar/PHUICalendarWeekView.php b/src/view/phui/calendar/PHUICalendarWeekView.php index bcfc3666c1..6237e5b569 100644 --- a/src/view/phui/calendar/PHUICalendarWeekView.php +++ b/src/view/phui/calendar/PHUICalendarWeekView.php @@ -4,6 +4,7 @@ final class PHUICalendarWeekView extends AphrontView { private $events; private $dateTime; private $weekLength = 7; + private $view = 'day'; public function setEvents($events) { $this->events = $events; @@ -27,6 +28,15 @@ final class PHUICalendarWeekView extends AphrontView { return $this; } + public function setView($view) { + $this->view = $view; + return $this; + } + + private function getView() { + return $this->view; + } + public function render() { $this->events = msort($this->events, 'getEpochStart'); $week_of_boxes = $this->getWeekOfBoxes(); @@ -64,7 +74,7 @@ final class PHUICalendarWeekView extends AphrontView { $list = id(new PHUICalendarListView()) ->setUser($this->getViewer()) - ->setView('day'); + ->setView($this->getView()); if (count($events) == 0) { $list->showBlankState(true); From 627b95bf78d31f90ea3c8ad92894554e6ca14d57 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Mon, 23 May 2016 11:17:27 -0700 Subject: [PATCH 12/24] Remove calendar panel in profile and make calendar box header a link to user's calendar Summary: Ref T9606, Clicking on the calendar preview header in user's profile page should link to user's full month calendar Test Plan: Open user profile, scroll to calendar preview, click on Calendar box header. This should open the month calendar for the user (not viewer) Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Maniphest Tasks: T9606 Differential Revision: https://secure.phabricator.com/D15967 --- ...PhabricatorPeopleProfileViewController.php | 29 ++++++++++--------- .../PhabricatorPeopleProfilePanelEngine.php | 17 ----------- 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index 323d5f66af..e5073eff94 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -192,7 +192,7 @@ final class PhabricatorPeopleProfileViewController $query = id(new PhabricatorCalendarEventQuery()) ->setViewer($viewer) ->withDateRange($range_start, $range_end) - ->withInvitedPHIDs(array($viewer->getPHID())) + ->withInvitedPHIDs(array($user->getPHID())) ->withIsCancelled(false); $statuses = $query->execute(); @@ -200,23 +200,22 @@ final class PhabricatorPeopleProfileViewController $events = array(); foreach ($statuses as $status) { - $viewer_is_invited = $status->getIsUserInvited($viewer->getPHID()); + $viewer_is_invited = $status->getIsUserInvited($user->getPHID()); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $status, PhabricatorPolicyCapability::CAN_EDIT); - $event = new AphrontCalendarEventView(); - $event->setCanEdit($can_edit); - $event->setEventID($status->getID()); - $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); - $event->setIsAllDay($status->getIsAllDay()); - $event->setIcon($status->getIcon()); - $event->setViewerIsInvited($viewer_is_invited); - - $event->setName($status->getName()); - $event->setURI($status->getURI()); + $event = id(new AphrontCalendarEventView()) + ->setCanEdit($can_edit) + ->setEventID($status->getID()) + ->setEpochRange($status->getDateFrom(), $status->getDateTo()) + ->setIsAllDay($status->getIsAllDay()) + ->setIcon($status->getIcon()) + ->setViewerIsInvited($viewer_is_invited) + ->setName($status->getName()) + ->setURI($status->getURI()); $events[] = $event; } @@ -229,7 +228,11 @@ final class PhabricatorPeopleProfileViewController ->render(); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Calendar')); + ->setHeader(pht('Calendar')) + ->setHref( + urisprintf( + '/calendar/?invitedPHIDs=%s#R', + $user->getPHID())); $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($day_view) diff --git a/src/applications/people/engine/PhabricatorPeopleProfilePanelEngine.php b/src/applications/people/engine/PhabricatorPeopleProfilePanelEngine.php index af8608900d..db362d2049 100644 --- a/src/applications/people/engine/PhabricatorPeopleProfilePanelEngine.php +++ b/src/applications/people/engine/PhabricatorPeopleProfilePanelEngine.php @@ -26,23 +26,6 @@ final class PhabricatorPeopleProfilePanelEngine ->setBuiltinKey(self::PANEL_PROFILE) ->setPanelKey(PhabricatorPeopleDetailsProfilePanel::PANELKEY); - // TODO: Convert this into a proper panel type. - $have_calendar = PhabricatorApplication::isClassInstalledForViewer( - 'PhabricatorCalendarApplication', - $viewer); - if ($have_calendar) { - $uri = urisprintf( - '/p/%s/calendar/', - $object->getUsername()); - - $panels[] = $this->newPanel() - ->setBuiltinKey('calendar') - ->setPanelKey(PhabricatorLinkProfilePanel::PANELKEY) - ->setPanelProperty('icon', 'calendar') - ->setPanelProperty('name', pht('Calendar')) - ->setPanelProperty('uri', $uri); - } - $have_maniphest = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorManiphestApplication', $viewer); From e1ad312fdd2c570eeaacd3e02eda2142cc5d9849 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 May 2016 10:42:05 -0700 Subject: [PATCH 13/24] Fix one more "Reviewers" wire format issue Summary: Fixes T11010. This also needs to be inflated until we fix the whole client/server responsibility issue here. Test Plan: - Created a revision while observing error log, no error. - Disabled "allow self accept", tried to make myself a reviewer, got rejected with an error message. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11010 Differential Revision: https://secure.phabricator.com/D15966 --- .../differential/customfield/DifferentialReviewersField.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/differential/customfield/DifferentialReviewersField.php b/src/applications/differential/customfield/DifferentialReviewersField.php index d23dac53d9..f77f8ab749 100644 --- a/src/applications/differential/customfield/DifferentialReviewersField.php +++ b/src/applications/differential/customfield/DifferentialReviewersField.php @@ -242,6 +242,7 @@ final class DifferentialReviewersField $config_self_accept_key = 'differential.allow-self-accept'; $allow_self_accept = PhabricatorEnv::getEnvConfig($config_self_accept_key); + $value = $this->inflateReviewers($value); foreach ($value as $spec) { $phid = $spec['phid']; From 725d60eb4aa8473e4138ed4787e3dccba6b383cd Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 May 2016 17:04:27 -0700 Subject: [PATCH 14/24] Fix "Reviewers" validation issue with empty reviewers Summary: Fixes T11021. Test Plan: Created a revision without reviewers. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11021 Differential Revision: https://secure.phabricator.com/D15968 --- .../differential/customfield/DifferentialReviewersField.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/differential/customfield/DifferentialReviewersField.php b/src/applications/differential/customfield/DifferentialReviewersField.php index f77f8ab749..41a4cf8f26 100644 --- a/src/applications/differential/customfield/DifferentialReviewersField.php +++ b/src/applications/differential/customfield/DifferentialReviewersField.php @@ -237,6 +237,10 @@ final class DifferentialReviewersField } public function validateCommitMessageValue($value) { + if (!$value) { + return; + } + $author_phid = $this->getObject()->getAuthorPHID(); $config_self_accept_key = 'differential.allow-self-accept'; From bb16a1b0e2a8011a61951f2b1f007f17963e237c Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 May 2016 17:13:12 -0700 Subject: [PATCH 15/24] Fix a possible fatal on the first push to a cluster repository Summary: Fixes T11020. I think this resolves things -- `$new_version` (set above) should be used, not `$new_log` directly. Specifically, we would get into trouble if the initial push failed for some reason (working copy not initialized yet, commit hook rejected, etc). Test Plan: Made a bad push to a new repository. Saw it freeze before the patch and succeed afterwards. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11020 Differential Revision: https://secure.phabricator.com/D15969 --- .../diffusion/protocol/DiffusionRepositoryClusterEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php index e3c70fecd9..b271c16741 100644 --- a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php +++ b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php @@ -391,7 +391,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { $repository_phid, $device_phid, $this->clusterWriteVersion, - $new_log->getID(), + $new_version, $this->clusterWriteOwner); $did_release = true; break; From 74e117ae41dd537485e1e5632913535f15c3a73f Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 May 2016 17:24:25 -0700 Subject: [PATCH 16/24] Don't send mail to "uninteresting" auditors Summary: Fixes T11017. We add packages as "uninteresting" auditors so that we can query commits by package later. Until recently, this didn't matter because we didn't send mail to packages. But now we do, so stop mailing them when they don't actually need to do anything. Test Plan: - Made a commit to a file which was part of a package but which I owned (so it does not trigger auditing). - `var_dump()`'d mail "To:" PHIDs. - Before patch: included package. - After patch: no package. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11017 Differential Revision: https://secure.phabricator.com/D15970 --- src/applications/audit/editor/PhabricatorAuditEditor.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/audit/editor/PhabricatorAuditEditor.php b/src/applications/audit/editor/PhabricatorAuditEditor.php index 8018e5314a..d795d4b97d 100644 --- a/src/applications/audit/editor/PhabricatorAuditEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditEditor.php @@ -642,6 +642,12 @@ final class PhabricatorAuditEditor $status_resigned = PhabricatorAuditStatusConstants::RESIGNED; foreach ($object->getAudits() as $audit) { + if (!$audit->isInteresting()) { + // Don't send mail to uninteresting auditors, like packages which + // own this code but which audits have not triggered for. + continue; + } + if ($audit->getAuditStatus() != $status_resigned) { $phids[] = $audit->getAuditorPHID(); } From d1eed54d85bc499e80dfaa96987282c203df4c00 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 24 May 2016 06:05:22 -0700 Subject: [PATCH 17/24] Fix expansion of projects into lists of user PHIDs Summary: Ref T11016. I think I inverted the meaning of this function by accident in D14893. The intent is to return a list of users: direct users, and all members of all projects. Prior to this patch actually returns direct users, and all projects they are members of. Test Plan: - Created "Project with Dog". - Added user "dog" to project. - Created package "X", owning file "/x", with audit enabled. - Made "X" owned by "Project with Dog". - Modified "/x" and had user "dog" accept it. - Landed change. - Prior to change: package "X" incorrectly added as auditor. - After change: package "X" correctly omitted as auditor, because a member reviewed the change. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11016 Differential Revision: https://secure.phabricator.com/D15971 --- .../owners/storage/PhabricatorOwnersOwner.php | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/applications/owners/storage/PhabricatorOwnersOwner.php b/src/applications/owners/storage/PhabricatorOwnersOwner.php index 07d5b59a20..ddca80b099 100644 --- a/src/applications/owners/storage/PhabricatorOwnersOwner.php +++ b/src/applications/owners/storage/PhabricatorOwnersOwner.php @@ -45,25 +45,37 @@ final class PhabricatorOwnersOwner extends PhabricatorOwnersDAO { 'packageID IN (%Ls)', $package_ids); - $all_phids = phid_group_by_type(mpull($owners, 'getUserPHID')); + $type_user = PhabricatorPeopleUserPHIDType::TYPECONST; + $type_project = PhabricatorProjectProjectPHIDType::TYPECONST; - $user_phids = idx($all_phids, - PhabricatorPeopleUserPHIDType::TYPECONST, - array()); - - if ($user_phids) { - $projects = id(new PhabricatorProjectQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withMemberPHIDs($user_phids) - ->withIsMilestone(false) - ->execute(); - $project_phids = mpull($projects, 'getPHID'); - } else { - $project_phids = array(); + $user_phids = array(); + $project_phids = array(); + foreach ($owners as $owner) { + $owner_phid = $owner->getUserPHID(); + switch (phid_get_type($owner_phid)) { + case PhabricatorPeopleUserPHIDType::TYPECONST: + $user_phids[] = $owner_phid; + break; + case PhabricatorProjectProjectPHIDType::TYPECONST: + $project_phids[] = $owner_phid; + break; + } } - $all_phids = array_fuse($user_phids) + array_fuse($project_phids); + if ($project_phids) { + $projects = id(new PhabricatorProjectQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($project_phids) + ->needMembers(true) + ->execute(); + foreach ($projects as $project) { + foreach ($project->getMemberPHIDs() as $member_phid) { + $user_phids[] = $member_phid; + } + } + } - return array_values($all_phids); + $user_phids = array_fuse($user_phids); + return array_values($user_phids); } } From 189600e4116e97dde117083d5fa6778d7cb13755 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 25 May 2016 08:07:38 -0700 Subject: [PATCH 18/24] Allow broader HTTP access to public repositories, respect nonstandard Phabricator HTTP port when generating repository URIs Summary: Fixes T11030. Fixes T11032. - Allow HTTP access to "Public" repositories even if `diffusion.allow-http-auth` is disabled. - If you run Phabricator on an unusual port (???) use that port as the default when generating HTTP URIs. Test Plan: - Faked `phabricator.base-uri` to an unusual port, saw repository HTTP URI generate with an unusual port. - Disabled `diffusion.allow-http-auth`, confirmed that toggling view policy between "public" and "users" activated or deactivated HTTP clone URI. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11030, T11032 Differential Revision: https://secure.phabricator.com/D15973 --- .../storage/PhabricatorRepository.php | 8 +++- .../storage/PhabricatorRepositoryURI.php | 40 +++++++++++++++---- .../user/userguide/diffusion_uris.diviner | 6 ++- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index bc786e46a1..c95a4361e1 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -2078,7 +2078,13 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_ID => true, ); - $allow_http = PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth'); + // If the view policy of the repository is public, support anonymous HTTP + // even if authenticated HTTP is not supported. + if ($this->getViewPolicy() === PhabricatorPolicies::POLICY_PUBLIC) { + $allow_http = true; + } else { + $allow_http = PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth'); + } $base_uri = PhabricatorEnv::getURI('/'); $base_uri = new PhutilURI($base_uri); diff --git a/src/applications/repository/storage/PhabricatorRepositoryURI.php b/src/applications/repository/storage/PhabricatorRepositoryURI.php index 53b9040453..f0326f12a9 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryURI.php +++ b/src/applications/repository/storage/PhabricatorRepositoryURI.php @@ -379,14 +379,40 @@ final class PhabricatorRepositoryURI } private function getForcedPort() { - switch ($this->getBuiltinProtocol()) { - case self::BUILTIN_PROTOCOL_SSH: - return PhabricatorEnv::getEnvConfig('diffusion.ssh-port'); - case self::BUILTIN_PROTOCOL_HTTP: - case self::BUILTIN_PROTOCOL_HTTPS: - default: - return null; + $protocol = $this->getBuiltinProtocol(); + + if ($protocol == self::BUILTIN_PROTOCOL_SSH) { + return PhabricatorEnv::getEnvConfig('diffusion.ssh-port'); } + + // If Phabricator is running on a nonstandard port, use that as the defualt + // port for URIs with the same protocol. + + $is_http = ($protocol == self::BUILTIN_PROTOCOL_HTTP); + $is_https = ($protocol == self::BUILTIN_PROTOCOL_HTTPS); + + if ($is_http || $is_https) { + $uri = PhabricatorEnv::getURI('/'); + $uri = new PhutilURI($uri); + + $port = $uri->getPort(); + if (!$port) { + return null; + } + + $uri_protocol = $uri->getProtocol(); + $use_port = + ($is_http && ($uri_protocol == 'http')) || + ($is_https && ($uri_protocol == 'https')); + + if (!$use_port) { + return null; + } + + return $port; + } + + return null; } private function getForcedPath() { diff --git a/src/docs/user/userguide/diffusion_uris.diviner b/src/docs/user/userguide/diffusion_uris.diviner index 7377250f21..08be97b6b8 100644 --- a/src/docs/user/userguide/diffusion_uris.diviner +++ b/src/docs/user/userguide/diffusion_uris.diviner @@ -173,14 +173,16 @@ SSH clone URIs by examining configuration. **HTTP**: The `http://` clone URI will be available if these conditions are satisfied: - - `diffusion.allow-http-auth` must be enabled. + - `diffusion.allow-http-auth` must be enabled or the repository view policy + must be "Public". - The repository must be a Git or Mercurial repository. - `security.require-https` must be disabled. **HTTPS**: The `https://` clone URI will be available if these conditions are satisfied: - - `diffusion.allow-http-auth` must be enabled. + - `diffusion.allow-http-auth` must be enabled or the repository view policy + must be "Public". - The repository must be a Git or Mercurial repository. - The `phabricator.base-uri` protocol must be `https://`. From a4e57800431f649e73adc78f756e7b7df9434119 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 25 May 2016 18:29:39 -0700 Subject: [PATCH 19/24] Remove "Search Preferences" Summary: Ref T4103. This removes these options: {F1660585} The jump nav option came from T916, when we had a separate jump nav on the home page. Essentially no one has ever been confused by the behavior of search or disabled this feature. Here are the stats for this install: | Total Users | 36656 | | Have Set Any Preference | 3084 | | Have Disabled Jump | 6 | Are Not "Security Researchers" | 2 | Any Account Activity | 0 The "/" option came in the same change, but the preference came from T989. This keystroke conflicts with a default Firefox keystroke. Almost no one cares about this either, but I count 6 real users who have disabled the behavior. I suspect the number of real users who //use// it may be smaller. In Safari and Firefox, the "tab" key does the same thing. In Chrome, the "tab" key does the same thing if {nav Preferences > Web Content > "Pressing Tab highlights..."} is disabled. Upshot: jump nav is great, bulk of the change in T989 was clearly great, specific preferences that came out of it seem not-so-great and now is a good time to kill them as we head into T4103. Test Plan: - Grepped for removed constants. - Pressed "/". - Searched for `T123`. - Viewed settings. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4103 Differential Revision: https://secure.phabricator.com/D15976 --- resources/celerity/map.php | 20 +++--- src/__phutil_library_map__.php | 2 - .../PhabricatorSearchController.php | 13 ++-- ...bricatorSearchPreferencesSettingsPanel.php | 62 ------------------- .../storage/PhabricatorUserPreferences.php | 2 - .../page/menu/PhabricatorMainMenuView.php | 5 -- .../js/core/behavior-keyboard-shortcuts.js | 10 --- 7 files changed, 15 insertions(+), 99 deletions(-) delete mode 100644 src/applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index cfdc45980f..3bd1fd002a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => '8aeacc63', - 'core.pkg.js' => '50e9228e', + 'core.pkg.js' => '3f15fa62', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '33da0633', 'differential.pkg.js' => '4b7d8f19', @@ -490,7 +490,7 @@ return array( 'rsrc/js/core/behavior-history-install.js' => '7ee2b591', 'rsrc/js/core/behavior-hovercard.js' => 'bcaccd64', 'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0', - 'rsrc/js/core/behavior-keyboard-shortcuts.js' => 'd75709e6', + 'rsrc/js/core/behavior-keyboard-shortcuts.js' => '7835f8c9', 'rsrc/js/core/behavior-lightbox-attachments.js' => 'f8ba29d7', 'rsrc/js/core/behavior-line-linker.js' => '1499a8cb', 'rsrc/js/core/behavior-more.js' => 'a80d0378', @@ -651,7 +651,7 @@ return array( 'javelin-behavior-phabricator-gesture' => '3ab51e2c', 'javelin-behavior-phabricator-gesture-example' => '558829c2', 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', - 'javelin-behavior-phabricator-keyboard-shortcuts' => 'd75709e6', + 'javelin-behavior-phabricator-keyboard-shortcuts' => '7835f8c9', 'javelin-behavior-phabricator-line-linker' => '1499a8cb', 'javelin-behavior-phabricator-nav' => '56a1ca03', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', @@ -1492,6 +1492,13 @@ return array( 'multirow-row-manager', 'javelin-json', ), + '7835f8c9' => array( + 'javelin-behavior', + 'javelin-workflow', + 'javelin-json', + 'javelin-dom', + 'phabricator-keyboard-shortcut', + ), '7927a7d3' => array( 'javelin-behavior', 'javelin-quicksand', @@ -1973,13 +1980,6 @@ return array( 'javelin-json', 'phabricator-prefab', ), - 'd75709e6' => array( - 'javelin-behavior', - 'javelin-workflow', - 'javelin-json', - 'javelin-dom', - 'phabricator-keyboard-shortcut', - ), 'd7a74243' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a54f840afb..76919876ec 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3331,7 +3331,6 @@ phutil_register_library_map(array( 'PhabricatorSearchNgramsDestructionEngineExtension' => 'applications/search/engineextension/PhabricatorSearchNgramsDestructionEngineExtension.php', 'PhabricatorSearchOrderController' => 'applications/search/controller/PhabricatorSearchOrderController.php', 'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php', - 'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php', 'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php', 'PhabricatorSearchResultBucket' => 'applications/search/buckets/PhabricatorSearchResultBucket.php', 'PhabricatorSearchResultBucketGroup' => 'applications/search/buckets/PhabricatorSearchResultBucketGroup.php', @@ -8044,7 +8043,6 @@ phutil_register_library_map(array( 'PhabricatorSearchNgramsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'PhabricatorSearchOrderController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchOrderField' => 'PhabricatorSearchField', - 'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorSearchRelationship' => 'Phobject', 'PhabricatorSearchResultBucket' => 'Phobject', 'PhabricatorSearchResultBucketGroup' => 'Phobject', diff --git a/src/applications/search/controller/PhabricatorSearchController.php b/src/applications/search/controller/PhabricatorSearchController.php index d6fbf6356b..a1f6fd68b5 100644 --- a/src/applications/search/controller/PhabricatorSearchController.php +++ b/src/applications/search/controller/PhabricatorSearchController.php @@ -13,14 +13,11 @@ final class PhabricatorSearchController $viewer = $this->getViewer(); if ($request->getStr('jump') != 'no') { - $pref_jump = PhabricatorUserPreferences::PREFERENCE_SEARCHBAR_JUMP; - if ($viewer->loadPreferences($pref_jump, 1)) { - $response = PhabricatorJumpNavHandler::getJumpResponse( - $viewer, - $request->getStr('query')); - if ($response) { - return $response; - } + $response = PhabricatorJumpNavHandler::getJumpResponse( + $viewer, + $request->getStr('query')); + if ($response) { + return $response; } } diff --git a/src/applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php b/src/applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php deleted file mode 100644 index f05a5dd9f9..0000000000 --- a/src/applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php +++ /dev/null @@ -1,62 +0,0 @@ -getUser(); - $preferences = $user->loadPreferences(); - - $pref_jump = PhabricatorUserPreferences::PREFERENCE_SEARCHBAR_JUMP; - $pref_shortcut = PhabricatorUserPreferences::PREFERENCE_SEARCH_SHORTCUT; - - if ($request->isFormPost()) { - $preferences->setPreference($pref_jump, - $request->getBool($pref_jump)); - - $preferences->setPreference($pref_shortcut, - $request->getBool($pref_shortcut)); - - $preferences->save(); - return id(new AphrontRedirectResponse()) - ->setURI($this->getPanelURI('?saved=true')); - } - - $form = id(new AphrontFormView()) - ->setUser($user) - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->addCheckbox($pref_jump, - 1, - pht('Enable jump nav functionality in all search boxes.'), - $preferences->getPreference($pref_jump, 1)) - ->addCheckbox($pref_shortcut, - 1, - pht("Press '%s' to focus the search input.", '/'), - $preferences->getPreference($pref_shortcut, 1))) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save'))); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Search Preferences')) - ->setFormSaved($request->getStr('saved') === 'true') - ->setForm($form); - - return array( - $form_box, - ); - } -} diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php index 18f0dfe980..c14cc9b910 100644 --- a/src/applications/settings/storage/PhabricatorUserPreferences.php +++ b/src/applications/settings/storage/PhabricatorUserPreferences.php @@ -19,8 +19,6 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO { const PREFERENCE_VARY_SUBJECT = 'vary-subject'; const PREFERENCE_HTML_EMAILS = 'html-emails'; - const PREFERENCE_SEARCHBAR_JUMP = 'searchbar-jump'; - const PREFERENCE_SEARCH_SHORTCUT = 'search-shortcut'; const PREFERENCE_SEARCH_SCOPE = 'search-scope'; const PREFERENCE_DIFFUSION_BLAME = 'diffusion-blame'; diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index e804573ff0..69ff522d5b 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -186,11 +186,6 @@ final class PhabricatorMainMenuView extends AphrontView { } $result = $search; - - $pref_shortcut = PhabricatorUserPreferences::PREFERENCE_SEARCH_SHORTCUT; - if ($viewer->loadPreferences()->getPreference($pref_shortcut, true)) { - $keyboard_config['searchID'] = $search->getID(); - } } Javelin::initBehavior('phabricator-keyboard-shortcuts', $keyboard_config); diff --git a/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js b/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js index 5c296420da..a2f7ca172b 100644 --- a/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js +++ b/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js @@ -30,14 +30,4 @@ JX.behavior('phabricator-keyboard-shortcuts', function(config) { }) .register(); - if (config.searchID) { - desc = 'Give keyboard focus to the search box.'; - new JX.KeyboardShortcut('/', desc) - .setHandler(function() { - var search = JX.$(config.searchID); - search.focus(); - search.select(); - }) - .register(); - } }); From 10ffa42504db24cd6f11526d98f28711bddad382 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 26 May 2016 06:48:43 -0700 Subject: [PATCH 20/24] Separate locales into more usable groups in the translation menu Summary: Ref T5267. Ref T4103. Currently, adding new locale support to the upstream fills this menu with confusing options which don't do anything. Separate it into four groups: - Translations: these have a "reasonable number" of strings and you'll probably see some obvious effect if you switch to the translation. - Limited Translations: these have very few or no strings, and include locales which we've added but don't ship translations for. - Silly Translations: Pirate english, etc. - Test Translations: ALLCAPS, raw strings, etc. Czech is currently in "test" instead of "limited" for historical reasons; I'll remedy this in the next change. Test Plan: {F1661523} Reviewers: chad Reviewed By: chad Maniphest Tasks: T4103, T5267 Differential Revision: https://secure.phabricator.com/D15978 --- .../panel/PhabricatorAccountSettingsPanel.php | 101 ++++++++++++++---- 1 file changed, 78 insertions(+), 23 deletions(-) diff --git a/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php b/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php index 72a225249a..176e4ce66b 100644 --- a/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php @@ -53,29 +53,7 @@ final class PhabricatorAccountSettingsPanel extends PhabricatorSettingsPanel { PhutilPerson::SEX_FEMALE => $label_her, ); - $locales = PhutilLocale::loadAllLocales(); - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - $is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); - - $translations = array(); - foreach ($locales as $locale) { - if ($is_serious && $locale->isSillyLocale()) { - // Omit silly locales on serious business installs. - continue; - } - if (!$is_dev && $locale->isTestLocale()) { - // Omit test locales on installs which aren't in development mode. - continue; - } - $translations[$locale->getLocaleCode()] = $locale->getLocaleName(); - } - - asort($translations); - // TODO: Implement "locale.default" and use it here. - $default = 'en_US'; - $translations = array( - '' => pht('Server Default: %s', $locales[$default]->getLocaleName()), - ) + $translations; + $translations = $this->getTranslationOptions(); $form = new AphrontFormView(); $form @@ -107,4 +85,81 @@ final class PhabricatorAccountSettingsPanel extends PhabricatorSettingsPanel { $form_box, ); } + + private function getTranslationOptions() { + $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); + $locales = PhutilLocale::loadAllLocales(); + + $group_labels = array( + 'normal' => pht('Translations'), + 'limited' => pht('Limited Translations'), + 'silly' => pht('Silly Translations'), + 'test' => pht('Developer/Test Translations'), + ); + + $groups = array_fill_keys(array_keys($group_labels), array()); + + $translations = array(); + foreach ($locales as $locale) { + $code = $locale->getLocaleCode(); + $name = $locale->getLocaleName(); + + if ($locale->isSillyLocale()) { + if ($is_serious) { + // Omit silly locales on serious business installs. + continue; + } + $groups['silly'][$code] = $name; + continue; + } + + if ($locale->isTestLocale()) { + $groups['test'][$code] = $name; + continue; + } + + $strings = PhutilTranslation::getTranslationMapForLocale($code); + $size = count($strings); + + // If a translation is English, assume it can fall back to the default + // strings and don't caveat its completeness. + $is_english = (substr($code, 0, 3) == 'en_'); + + // Arbitrarily pick some number of available strings to promote a + // translation out of the "limited" group. The major goal is just to + // keep locales with very few strings out of the main group, so users + // aren't surprised if a locale has no upstream translations available. + if ($size > 512 || $is_english) { + $type = 'normal'; + } else { + $type = 'limited'; + } + + $groups[$type][$code] = $name; + } + + // TODO: Select a default properly. + $default = 'en_US'; + + $results = array(); + foreach ($groups as $key => $group) { + $label = $group_labels[$key]; + if (!$group) { + continue; + } + + asort($group); + + if ($key == 'normal') { + $group = array( + '' => pht('Server Default: %s', $locales[$default]->getLocaleName()), + ) + $group; + } + + $results[$label] = $group; + } + + return $results; + } + } From 5b77b86ffbf93653268c11ccccd05e29510ad0d7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 26 May 2016 07:46:47 -0700 Subject: [PATCH 21/24] Show translation option names natively, instead of in the current translation Summary: Ref T5267. Put "Deutsch" in the list instead of "German", so you can find your language without knowing the English word for it. Test Plan: {F1661598} Reviewers: chad Reviewed By: chad Maniphest Tasks: T5267 Differential Revision: https://secure.phabricator.com/D15980 --- src/__phutil_library_map__.php | 2 ++ .../panel/PhabricatorAccountSettingsPanel.php | 6 ++++++ .../translation/PhabricatorEmojiTranslation.php | 15 +++++++++++++++ .../PhabricatorVeryWowEnglishTranslation.php | 1 + 4 files changed, 24 insertions(+) create mode 100644 src/infrastructure/internationalization/translation/PhabricatorEmojiTranslation.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 76919876ec..6c5ac74685 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2383,6 +2383,7 @@ phutil_register_library_map(array( 'PhabricatorEmailVerificationController' => 'applications/auth/controller/PhabricatorEmailVerificationController.php', 'PhabricatorEmbedFileRemarkupRule' => 'applications/files/markup/PhabricatorEmbedFileRemarkupRule.php', 'PhabricatorEmojiRemarkupRule' => 'applications/macro/markup/PhabricatorEmojiRemarkupRule.php', + 'PhabricatorEmojiTranslation' => 'infrastructure/internationalization/translation/PhabricatorEmojiTranslation.php', 'PhabricatorEmptyQueryException' => 'infrastructure/query/PhabricatorEmptyQueryException.php', 'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php', 'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php', @@ -6912,6 +6913,7 @@ phutil_register_library_map(array( 'PhabricatorEmailVerificationController' => 'PhabricatorAuthController', 'PhabricatorEmbedFileRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorEmojiRemarkupRule' => 'PhutilRemarkupRule', + 'PhabricatorEmojiTranslation' => 'PhutilTranslation', 'PhabricatorEmptyQueryException' => 'Exception', 'PhabricatorEnv' => 'Phobject', 'PhabricatorEnvTestCase' => 'PhabricatorTestCase', diff --git a/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php b/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php index 176e4ce66b..274e44f15e 100644 --- a/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php @@ -102,7 +102,13 @@ final class PhabricatorAccountSettingsPanel extends PhabricatorSettingsPanel { $translations = array(); foreach ($locales as $locale) { $code = $locale->getLocaleCode(); + + // Get the locale's localized name if it's available. For example, + // "Deutsch" instead of "German". This helps users who do not speak the + // current language to find the correct setting. + $raw_scope = PhabricatorEnv::beginScopedLocale($code); $name = $locale->getLocaleName(); + unset($raw_scope); if ($locale->isSillyLocale()) { if ($is_serious) { diff --git a/src/infrastructure/internationalization/translation/PhabricatorEmojiTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorEmojiTranslation.php new file mode 100644 index 0000000000..40ea2e8829 --- /dev/null +++ b/src/infrastructure/internationalization/translation/PhabricatorEmojiTranslation.php @@ -0,0 +1,15 @@ + "\xF0\x9F\x92\xAC (\xF0\x9F\x8C\x8D)", + ); + } +} diff --git a/src/infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php index b269fcfed2..cbb3e3bc4a 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php @@ -33,6 +33,7 @@ final class PhabricatorVeryWowEnglishTranslation 'Prototype' => 'Chew Toy', 'Continue' => 'Bark And Run', 'Countdown to Events' => 'To the Moon!', + 'English (Very Wow)' => 'Such English', ); } } From 727a7de75941f756186ec7a3058c26a0a03eab9e Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 26 May 2016 10:01:44 -0700 Subject: [PATCH 22/24] Sort project typeahead tokens by display name, not hashtag Summary: Fixes T8510. Results are internally ordered by "name", which is the full list of strings a user can type to match a result. On the balance, it is probably good/correct to order by this (particularly, it allows `function(x)` to sort near `x`). However, the way projects were built put the tags first, so a project like "Discovery" could end up last if it had originally been created with a different name like "Search Team", so that its first slug is "search-team". Instead, put the display name first in the ordering. Test Plan: {F1661775} To reproduce in particular: - Create a project named "Zebra". - Create a lot of projects named "Armadillo-blahblahblah". - Rename "Zebra" to "Armadillo". Before the patch, the new "Armadillo" project would still sort as though it were named "Zebra". After the patch, it sorts as expected normally. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8510 Differential Revision: https://secure.phabricator.com/D15981 --- .../project/typeahead/PhabricatorProjectDatasource.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/applications/project/typeahead/PhabricatorProjectDatasource.php b/src/applications/project/typeahead/PhabricatorProjectDatasource.php index 40d8dbb0e6..b51c55ee0a 100644 --- a/src/applications/project/typeahead/PhabricatorProjectDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectDatasource.php @@ -82,8 +82,11 @@ final class PhabricatorProjectDatasource $closed = pht('Archived'); } - $all_strings = mpull($proj->getSlugs(), 'getSlug'); + $all_strings = array(); $all_strings[] = $proj->getDisplayName(); + foreach ($proj->getSlugs() as $project_slug) { + $all_strings[] = $project_slug->getSlug(); + } $all_strings = implode(' ', $all_strings); $proj_result = id(new PhabricatorTypeaheadResult()) From 74a36f9d7bf0503b729dcc003222267aa0f044f2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 26 May 2016 12:10:30 -0700 Subject: [PATCH 23/24] Read "Database Status" page connection information from cluster config if present Summary: Fixes T11043. This page was still reading the old information directly instead of going through the cluster-aware stuff. Have it ask the cluster-aware stuff for information instead. Test Plan: - Nuked MySQL on localhost. - Configured cluster hosts. - Loaded "Database Status" page -- worked after patch. - Grepped for any remaining `mysql.configuration-provider` stragglers, came up empty. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11043 Differential Revision: https://secure.phabricator.com/D15982 --- .../PhabricatorConfigDatabaseController.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseController.php b/src/applications/config/controller/PhabricatorConfigDatabaseController.php index 225312a52f..0619a4a3a1 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseController.php @@ -4,16 +4,14 @@ abstract class PhabricatorConfigDatabaseController extends PhabricatorConfigController { protected function buildSchemaQuery() { - $conf = PhabricatorEnv::newObjectFromConfig( - 'mysql.configuration-provider', - array($dao = null, 'w')); + $ref = PhabricatorDatabaseRef::getMasterDatabaseRef(); $api = id(new PhabricatorStorageManagementAPI()) - ->setUser($conf->getUser()) - ->setHost($conf->getHost()) - ->setPort($conf->getPort()) + ->setUser($ref->getUser()) + ->setHost($ref->getHost()) + ->setPort($ref->getPort()) ->setNamespace(PhabricatorLiskDAO::getDefaultStorageNamespace()) - ->setPassword($conf->getPassword()); + ->setPassword($ref->getPass()); $query = id(new PhabricatorConfigSchemaQuery()) ->setAPI($api); From 10cc633b88b2c48cad867aba6695233e0a64c533 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 26 May 2016 19:58:37 -0700 Subject: [PATCH 24/24] Warn and continue when failing to extract pht() strings Summary: Ref T5267. Fixes T9643. Currently, we can not parse/extract a small fraction of strings: - PHP files which contain obscure syntax that XHPAST can not parse. - HEREDOCs do not have a useable `evalStatic()` behavior right now. Emit these as warnings, but continue and generate all usable/extractable translations. Test Plan: ``` epriestley@orbital ~/dev/phabricator $ ./bin/i18n extract . >/dev/null Found 4,548 files... Done. WARNING: Failed to extract strings from file "/Users/epriestley/dev/core/lib/phabricator/externals/stripe-php/lib/Stripe/ApiRequestor.php": XHPAST Parse Error: syntax error, unexpected T_STRING on line 357 WARNING: Failed to evaluate pht() call on line 141 in "/Users/epriestley/dev/core/lib/phabricator/scripts/repository/commit_hook.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 24 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorClusterConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 23 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 49 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 83 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 91 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 97 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 103 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 113 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 119 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 128 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 137 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 149 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 159 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 166 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 179 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 192 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 24 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/config/option/PhabricatorNotificationConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 26 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 154 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 282 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 84 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/maniphest/editor/ManiphestEditEngine.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 345 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 59 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 26 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/project/config/PhabricatorProjectConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 53 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/project/config/PhabricatorProjectConfigOptions.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 98 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 165 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 252 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 299 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 360 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 439 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 529 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 54 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/transactions/controller/PhabricatorEditEngineConfigurationLockController.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 56 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 126 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 185 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 238 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 288 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php": Unexpected node during static evaluation, of type: n_HEREDOC WARNING: Failed to evaluate pht() call on line 17 in "/Users/epriestley/dev/core/lib/phabricator/src/applications/uiexample/examples/PhabricatorRemarkupUIExample.php": Unexpected node during static evaluation, of type: n_HEREDOC epriestley@orbital ~/dev/phabricator $ ``` Reviewers: chad Reviewed By: chad Maniphest Tasks: T5267, T9643 Differential Revision: https://secure.phabricator.com/D15983 --- ...tionalizationManagementExtractWorkflow.php | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php b/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php index b22040de1a..7714ad81ab 100644 --- a/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php +++ b/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php @@ -35,7 +35,7 @@ final class PhabricatorInternationalizationManagementExtractWorkflow } } - $console->writeOut( + $console->writeErr( "%s\n", pht('Found %s file(s)...', phutil_count($futures))); @@ -44,14 +44,24 @@ final class PhabricatorInternationalizationManagementExtractWorkflow $bar = id(new PhutilConsoleProgressBar()) ->setTotal(count($futures)); + $messages = array(); + $futures = id(new FutureIterator($futures)) ->limit(8); foreach ($futures as $full_path => $future) { $bar->update(1); - $tree = XHPASTTree::newFromDataAndResolvedExecFuture( - Filesystem::readFile($full_path), - $future->resolve()); + try { + $tree = XHPASTTree::newFromDataAndResolvedExecFuture( + Filesystem::readFile($full_path), + $future->resolve()); + } catch (Exception $ex) { + $messages[] = pht( + 'WARNING: Failed to extract strings from file "%s": %s', + $full_path, + $ex->getMessage()); + continue; + } $root = $tree->getRootNode(); $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); @@ -69,7 +79,11 @@ final class PhabricatorInternationalizationManagementExtractWorkflow 'line' => $string_line, ); } catch (Exception $ex) { - // TODO: Deal with this junks. + $messages[] = pht( + 'WARNING: Failed to evaluate pht() call on line %d in "%s": %s', + $call->getLineNumber(), + $full_path, + $ex->getMessage()); } } } @@ -78,6 +92,10 @@ final class PhabricatorInternationalizationManagementExtractWorkflow } $bar->done(); + foreach ($messages as $message) { + $console->writeErr("%s\n", $message); + } + ksort($results); $out = array();