diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 190f73e0e4..11531ebcf0 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,11 +7,11 @@ */ return array( 'names' => array( - 'core.pkg.css' => '7e38026c', - 'core.pkg.js' => '5058979d', + 'core.pkg.css' => '87ab9c7e', + 'core.pkg.js' => 'cf262309', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', - 'differential.pkg.js' => '6223dd9d', + 'differential.pkg.js' => '64e69521', 'diffusion.pkg.css' => 'f45955ed', 'diffusion.pkg.js' => 'ca1c8b5a', 'maniphest.pkg.css' => '4845691a', @@ -82,7 +82,7 @@ return array( 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', 'rsrc/css/application/paste/paste.css' => 'a5157c48', 'rsrc/css/application/people/people-profile.css' => '25970776', - 'rsrc/css/application/phame/phame.css' => '164515da', + 'rsrc/css/application/phame/phame.css' => '09a39e8d', 'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', 'rsrc/css/application/pholio/pholio.css' => '95174bdd', @@ -104,7 +104,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => 'a76cefc9', - 'rsrc/css/core/remarkup.css' => '275e362f', + 'rsrc/css/core/remarkup.css' => '7afb543c', 'rsrc/css/core/syntax.css' => '9fd11da8', 'rsrc/css/core/z-index.css' => '57ddcaa2', 'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa', @@ -123,22 +123,23 @@ return array( 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => 'f25c3476', + 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => 'a5bb366d', 'rsrc/css/phui/phui-button.css' => '16020a60', 'rsrc/css/phui/phui-crumbs-view.css' => '414406b5', 'rsrc/css/phui/phui-document-pro.css' => 'e0fad431', - 'rsrc/css/phui/phui-document-summary.css' => '8c1e0aca', + 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'a4a1c3b9', 'rsrc/css/phui/phui-feed-story.css' => 'b7b26d23', 'rsrc/css/phui/phui-fontkit.css' => '9cda225e', - 'rsrc/css/phui/phui-form-view.css' => '93e0ec4c', - 'rsrc/css/phui/phui-form.css' => 'afdb2c6e', + 'rsrc/css/phui/phui-form-view.css' => '61e78dcb', + 'rsrc/css/phui/phui-form.css' => '0b98e572', 'rsrc/css/phui/phui-header-view.css' => '55bb32dd', 'rsrc/css/phui/phui-icon.css' => 'b0a6b1b6', 'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => '6d7c3509', - 'rsrc/css/phui/phui-list.css' => '125599df', + 'rsrc/css/phui/phui-list.css' => '9da2aa00', 'rsrc/css/phui/phui-object-box.css' => '407eaf5a', 'rsrc/css/phui/phui-object-item-list-view.css' => 'ede98c4b', 'rsrc/css/phui/phui-pager.css' => 'bea33d23', @@ -151,7 +152,7 @@ return array( 'rsrc/css/phui/phui-text.css' => 'cf019f54', 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', 'rsrc/css/phui/phui-two-column-view.css' => '39ecafb1', - 'rsrc/css/phui/phui-workboard-view.css' => '6704d68d', + 'rsrc/css/phui/phui-workboard-view.css' => '24fe2a66', 'rsrc/css/phui/phui-workpanel-view.css' => 'adec7699', 'rsrc/css/sprite-login.css' => '60e8560e', 'rsrc/css/sprite-main-header.css' => 'f07bbb87', @@ -246,7 +247,7 @@ return array( 'rsrc/externals/javelin/lib/__tests__/URI.js' => '1e45fda9', 'rsrc/externals/javelin/lib/__tests__/behavior.js' => '1ea62783', 'rsrc/externals/javelin/lib/behavior.js' => '61cbc29a', - 'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => 'c431f925', + 'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '8d3bc1b2', 'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => '70baed2f', 'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => 'e6e25838', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '503e17fd', @@ -417,7 +418,7 @@ return array( 'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => '3f5d6dbf', 'rsrc/js/application/phortune/behavior-test-payment-form.js' => 'fc91ab6c', 'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef', - 'rsrc/js/application/policy/behavior-policy-control.js' => '7d470398', + 'rsrc/js/application/policy/behavior-policy-control.js' => 'ae45872f', 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c', 'rsrc/js/application/projects/behavior-project-boards.js' => 'ba4fa35c', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', @@ -428,7 +429,7 @@ return array( 'rsrc/js/application/repository/repository-crossreference.js' => 'e5339c43', 'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08', 'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f', - 'rsrc/js/application/transactions/behavior-comment-actions.js' => 'bb0d2d0c', + 'rsrc/js/application/transactions/behavior-comment-actions.js' => 'b65559c0', 'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243', 'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96', 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => 'dbbf48b6', @@ -458,9 +459,9 @@ return array( 'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 'rsrc/js/core/Notification.js' => 'ccf1cbf8', - 'rsrc/js/core/Prefab.js' => 'be38fe4e', + 'rsrc/js/core/Prefab.js' => '666c80c5', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', - 'rsrc/js/core/TextAreaUtils.js' => '5c93c52c', + 'rsrc/js/core/TextAreaUtils.js' => '9e54692d', 'rsrc/js/core/Title.js' => 'df5e11d2', 'rsrc/js/core/ToolTip.js' => '1d298e3a', 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', @@ -470,7 +471,7 @@ return array( 'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2', 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', 'rsrc/js/core/behavior-device.js' => 'a205cf28', - 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6d49590e', + 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '4f6a4b4e', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', 'rsrc/js/core/behavior-fancy-datepicker.js' => '8ae55229', 'rsrc/js/core/behavior-file-tree.js' => '88236f00', @@ -488,7 +489,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' => 'eeaa9e5a', + 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'ecddcbe2', 'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b', 'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45', 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', @@ -566,14 +567,14 @@ 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' => '6d49590e', + 'javelin-behavior-aphront-drag-and-drop-textarea' => '4f6a4b4e', 'javelin-behavior-aphront-form-disable-on-submit' => '5c54cbf3', 'javelin-behavior-aphront-more' => 'a80d0378', 'javelin-behavior-audio-source' => '59b251eb', 'javelin-behavior-audit-preview' => 'd835b03a', 'javelin-behavior-bulk-job-reload' => 'edf8a145', 'javelin-behavior-choose-control' => 'dfaafb14', - 'javelin-behavior-comment-actions' => 'bb0d2d0c', + 'javelin-behavior-comment-actions' => 'b65559c0', 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', 'javelin-behavior-conpherence-menu' => '1d45c74d', @@ -641,7 +642,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' => 'eeaa9e5a', + 'javelin-behavior-phabricator-remarkup-assist' => 'ecddcbe2', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => '048330fa', 'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6', @@ -653,7 +654,7 @@ return array( 'javelin-behavior-pholio-mock-view' => 'fbe497e7', 'javelin-behavior-phui-dropdown-menu' => '54733475', 'javelin-behavior-phui-object-box-tabs' => '2bfa2836', - 'javelin-behavior-policy-control' => '7d470398', + 'javelin-behavior-policy-control' => 'ae45872f', 'javelin-behavior-policy-rule-editor' => '5e9f347c', 'javelin-behavior-project-boards' => 'ba4fa35c', 'javelin-behavior-project-create' => '065227cc', @@ -704,7 +705,7 @@ return array( 'javelin-scrollbar' => '087e919c', 'javelin-sound' => '949c0fe5', 'javelin-stratcom' => '6c53634d', - 'javelin-tokenizer' => 'c431f925', + 'javelin-tokenizer' => '8d3bc1b2', 'javelin-typeahead' => '70baed2f', 'javelin-typeahead-composite-source' => '503e17fd', 'javelin-typeahead-normalizer' => 'e6e25838', @@ -759,15 +760,15 @@ return array( 'phabricator-notification-menu-css' => 'f31c0bde', 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', - 'phabricator-prefab' => 'be38fe4e', - 'phabricator-remarkup-css' => '275e362f', + 'phabricator-prefab' => '666c80c5', + 'phabricator-remarkup-css' => '7afb543c', 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-side-menu-view-css' => 'bec2458e', 'phabricator-slowvote-css' => 'da0afb1b', 'phabricator-source-code-view-css' => 'cbeef983', 'phabricator-standard-page-view' => '3c99cdf4', - 'phabricator-textareautils' => '5c93c52c', + 'phabricator-textareautils' => '9e54692d', 'phabricator-title' => 'df5e11d2', 'phabricator-tooltip' => '1d298e3a', 'phabricator-ui-example-css' => '528b19de', @@ -783,7 +784,7 @@ return array( 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', 'phabricator-welcome-page' => 'c370f13b', 'phabricator-zindex-css' => '57ddcaa2', - 'phame-css' => '164515da', + 'phame-css' => '09a39e8d', 'pholio-css' => '95174bdd', 'pholio-edit-css' => '3ad9d1ee', 'pholio-inline-comments-css' => '8e545e49', @@ -794,6 +795,7 @@ return array( 'phriction-document-css' => 'd1861e06', 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => 'f25c3476', + 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => 'a5bb366d', 'phui-button-css' => '16020a60', 'phui-calendar-css' => 'ccabe893', @@ -801,21 +803,21 @@ return array( 'phui-calendar-list-css' => 'c1c7f338', 'phui-calendar-month-css' => '476be7e0', 'phui-crumbs-view-css' => '414406b5', - 'phui-document-summary-view-css' => '8c1e0aca', + 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'a4a1c3b9', 'phui-document-view-pro-css' => 'e0fad431', 'phui-feed-story-css' => 'b7b26d23', 'phui-font-icon-base-css' => 'ecbbb4c2', 'phui-fontkit-css' => '9cda225e', - 'phui-form-css' => 'afdb2c6e', - 'phui-form-view-css' => '93e0ec4c', + 'phui-form-css' => '0b98e572', + 'phui-form-view-css' => '61e78dcb', 'phui-header-view-css' => '55bb32dd', 'phui-icon-view-css' => 'b0a6b1b6', 'phui-image-mask-css' => '5a8b09c8', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '6d7c3509', 'phui-inline-comment-view-css' => '0fdb3667', - 'phui-list-view-css' => '125599df', + 'phui-list-view-css' => '9da2aa00', 'phui-object-box-css' => '407eaf5a', 'phui-object-item-list-view-css' => 'ede98c4b', 'phui-pager-css' => 'bea33d23', @@ -829,7 +831,7 @@ return array( 'phui-theme-css' => '6b451f24', 'phui-timeline-view-css' => '2efceff8', 'phui-two-column-view-css' => '39ecafb1', - 'phui-workboard-view-css' => '6704d68d', + 'phui-workboard-view-css' => '24fe2a66', 'phui-workpanel-view-css' => 'adec7699', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '8cf6d262', @@ -1135,6 +1137,12 @@ return array( 'javelin-stratcom', 'javelin-dom', ), + '4f6a4b4e' => array( + 'javelin-behavior', + 'javelin-dom', + 'phabricator-drag-and-drop-file-upload', + 'phabricator-textareautils', + ), '4fdb476d' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1236,11 +1244,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - '5c93c52c' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-vector', - ), '5d7c9f33' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1293,6 +1296,18 @@ return array( 'javelin-vector', 'differential-inline-comment-editor', ), + '666c80c5' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-typeahead', + 'javelin-tokenizer', + 'javelin-typeahead-preloaded-source', + 'javelin-typeahead-ondemand-source', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + ), '6882e80a' => array( 'javelin-dom', ), @@ -1323,12 +1338,6 @@ return array( 'javelin-typeahead', 'javelin-uri', ), - '6d49590e' => array( - 'javelin-behavior', - 'javelin-dom', - 'phabricator-drag-and-drop-file-upload', - 'phabricator-textareautils', - ), '70baed2f' => array( 'javelin-install', 'javelin-dom', @@ -1391,15 +1400,6 @@ return array( 'javelin-request', 'javelin-router', ), - '7d470398' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'phuix-dropdown-menu', - 'phuix-action-list-view', - 'phuix-action-view', - 'javelin-workflow', - ), '7e41274a' => array( 'javelin-install', ), @@ -1499,6 +1499,12 @@ return array( 'javelin-dom', 'javelin-util', ), + '8d3bc1b2' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + ), '8ef9ab58' => array( 'javelin-behavior', 'javelin-dom', @@ -1549,6 +1555,11 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), + '9e54692d' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-vector', + ), '9f36c42d' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1648,6 +1659,15 @@ return array( 'javelin-uri', 'phabricator-file-upload', ), + 'ae45872f' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'phuix-dropdown-menu', + 'phuix-action-list-view', + 'phuix-action-view', + 'javelin-workflow', + ), 'b064af76' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1704,6 +1724,15 @@ return array( 'javelin-dom', 'javelin-util', ), + 'b65559c0' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-dom', + 'phuix-form-control-view', + 'phuix-icon-view', + 'javelin-behavior-phabricator-gesture', + ), 'b6993408' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1723,14 +1752,6 @@ return array( 'javelin-workflow', 'phabricator-draggable-list', ), - 'bb0d2d0c' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-dom', - 'phuix-form-control-view', - 'phuix-icon-view', - ), 'bd4c8dca' => array( 'javelin-install', 'javelin-util', @@ -1744,18 +1765,6 @@ return array( 'javelin-util', 'javelin-request', ), - 'be38fe4e' => array( - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-typeahead', - 'javelin-tokenizer', - 'javelin-typeahead-preloaded-source', - 'javelin-typeahead-ondemand-source', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - ), 'bff6884b' => array( 'javelin-install', 'javelin-dom', @@ -1767,12 +1776,6 @@ return array( 'javelin-dom', 'javelin-vector', ), - 'c431f925' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - ), 'c72aa091' => array( 'javelin-behavior', 'javelin-dom', @@ -1953,6 +1956,15 @@ return array( 'phabricator-phtize', 'javelin-dom', ), + 'ecddcbe2' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phabricator-phtize', + 'phabricator-textareautils', + 'javelin-workflow', + 'javelin-vector', + ), 'edd1ba66' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1964,15 +1976,6 @@ return array( 'javelin-behavior', 'javelin-uri', ), - 'eeaa9e5a' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'phabricator-phtize', - 'phabricator-textareautils', - 'javelin-workflow', - 'javelin-vector', - ), 'efe49472' => array( 'javelin-install', 'javelin-util', diff --git a/resources/sql/autopatches/20151218.key.1.keyphid.sql b/resources/sql/autopatches/20151218.key.1.keyphid.sql new file mode 100644 index 0000000000..568572c1ba --- /dev/null +++ b/resources/sql/autopatches/20151218.key.1.keyphid.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_auth.auth_sshkey + ADD phid VARBINARY(64) NOT NULL AFTER id; diff --git a/resources/sql/autopatches/20151218.key.2.keyphid.php b/resources/sql/autopatches/20151218.key.2.keyphid.php new file mode 100644 index 0000000000..eb732742ca --- /dev/null +++ b/resources/sql/autopatches/20151218.key.2.keyphid.php @@ -0,0 +1,17 @@ +establishConnection('w'); + +foreach (new LiskMigrationIterator($table) as $cursor) { + if (strlen($cursor->getPHID())) { + continue; + } + + queryfx( + $conn_w, + 'UPDATE %T SET phid = %s WHERE id = %d', + $table->getTableName(), + $table->generatePHID(), + $cursor->getID()); +} diff --git a/resources/sql/autopatches/20151219.proj.01.prislug.sql b/resources/sql/autopatches/20151219.proj.01.prislug.sql new file mode 100644 index 0000000000..8001dd756c --- /dev/null +++ b/resources/sql/autopatches/20151219.proj.01.prislug.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project + ADD COLUMN primarySlug VARCHAR(128) COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20151219.proj.02.prislugkey.sql b/resources/sql/autopatches/20151219.proj.02.prislugkey.sql new file mode 100644 index 0000000000..a1dbf6ff99 --- /dev/null +++ b/resources/sql/autopatches/20151219.proj.02.prislugkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project + ADD UNIQUE KEY `key_primaryslug` (primarySlug); diff --git a/resources/sql/autopatches/20151219.proj.03.copyslug.sql b/resources/sql/autopatches/20151219.proj.03.copyslug.sql new file mode 100644 index 0000000000..2b954c6cdc --- /dev/null +++ b/resources/sql/autopatches/20151219.proj.03.copyslug.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_project.project + SET primarySlug = TRIM(TRAILING "/" FROM phrictionSlug); diff --git a/resources/sql/autopatches/20151219.proj.04.dropslugkey.sql b/resources/sql/autopatches/20151219.proj.04.dropslugkey.sql new file mode 100644 index 0000000000..bfe1f2eb4b --- /dev/null +++ b/resources/sql/autopatches/20151219.proj.04.dropslugkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project + DROP KEY `phrictionSlug`; diff --git a/resources/sql/autopatches/20151219.proj.05.dropslug.sql b/resources/sql/autopatches/20151219.proj.05.dropslug.sql new file mode 100644 index 0000000000..924f31ff95 --- /dev/null +++ b/resources/sql/autopatches/20151219.proj.05.dropslug.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project + DROP COLUMN phrictionSlug; diff --git a/resources/sql/autopatches/20151219.proj.06.defaultpolicy.php b/resources/sql/autopatches/20151219.proj.06.defaultpolicy.php new file mode 100644 index 0000000000..e3548d026c --- /dev/null +++ b/resources/sql/autopatches/20151219.proj.06.defaultpolicy.php @@ -0,0 +1,28 @@ +getPolicy(ProjectDefaultViewCapability::CAPABILITY); +$edit_policy = $app->getPolicy(ProjectDefaultEditCapability::CAPABILITY); +$join_policy = $app->getPolicy(ProjectDefaultJoinCapability::CAPABILITY); + +$table = new PhabricatorProject(); +$conn_w = $table->establishConnection('w'); + +queryfx( + $conn_w, + 'UPDATE %T SET viewPolicy = %s WHERE viewPolicy IS NULL', + $table->getTableName(), + $view_policy); + +queryfx( + $conn_w, + 'UPDATE %T SET editPolicy = %s WHERE editPolicy IS NULL', + $table->getTableName(), + $edit_policy); + +queryfx( + $conn_w, + 'UPDATE %T SET joinPolicy = %s WHERE joinPolicy IS NULL', + $table->getTableName(), + $join_policy); diff --git a/resources/sql/autopatches/20151219.proj.07.viewnull.sql b/resources/sql/autopatches/20151219.proj.07.viewnull.sql new file mode 100644 index 0000000000..0e5539fc21 --- /dev/null +++ b/resources/sql/autopatches/20151219.proj.07.viewnull.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project + CHANGE viewPolicy viewPolicy VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20151219.proj.08.editnull.sql b/resources/sql/autopatches/20151219.proj.08.editnull.sql new file mode 100644 index 0000000000..362e72bf4f --- /dev/null +++ b/resources/sql/autopatches/20151219.proj.08.editnull.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project + CHANGE editPolicy editPolicy VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20151219.proj.09.joinnull.sql b/resources/sql/autopatches/20151219.proj.09.joinnull.sql new file mode 100644 index 0000000000..9517ed54f7 --- /dev/null +++ b/resources/sql/autopatches/20151219.proj.09.joinnull.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project + CHANGE joinPolicy joinPolicy VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20151219.proj.10.subcolumns.sql b/resources/sql/autopatches/20151219.proj.10.subcolumns.sql new file mode 100644 index 0000000000..ea6725a8ab --- /dev/null +++ b/resources/sql/autopatches/20151219.proj.10.subcolumns.sql @@ -0,0 +1,17 @@ +ALTER TABLE {$NAMESPACE}_project.project + ADD parentProjectPHID VARBINARY(64); + +ALTER TABLE {$NAMESPACE}_project.project + ADD hasWorkboard BOOL NOT NULL; + +ALTER TABLE {$NAMESPACE}_project.project + ADD hasMilestones BOOL NOT NULL; + +ALTER TABLE {$NAMESPACE}_project.project + ADD hasSubprojects BOOL NOT NULL; + +ALTER TABLE {$NAMESPACE}_project.project + ADD milestoneNumber INT UNSIGNED; + +ALTER TABLE {$NAMESPACE}_project.project + ADD UNIQUE KEY `key_milestone` (parentProjectPHID, milestoneNumber); diff --git a/resources/sql/autopatches/20151219.proj.11.subprojectphids.sql b/resources/sql/autopatches/20151219.proj.11.subprojectphids.sql new file mode 100644 index 0000000000..4e51b1358d --- /dev/null +++ b/resources/sql/autopatches/20151219.proj.11.subprojectphids.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project + DROP subprojectPHIDs; diff --git a/resources/sql/autopatches/20151221.search.1.version.sql b/resources/sql/autopatches/20151221.search.1.version.sql new file mode 100644 index 0000000000..dca993f1ce --- /dev/null +++ b/resources/sql/autopatches/20151221.search.1.version.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_search.search_indexversion ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + objectPHID VARBINARY(64) NOT NULL, + extensionKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}, + version VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT}, + UNIQUE KEY `key_object` (objectPHID, extensionKey) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20151221.search.2.ownersngrams.sql b/resources/sql/autopatches/20151221.search.2.ownersngrams.sql new file mode 100644 index 0000000000..028d9e22f1 --- /dev/null +++ b/resources/sql/autopatches/20151221.search.2.ownersngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_owners.owners_name_ngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + KEY `key_object` (objectID), + KEY `key_ngram` (ngram, objectID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20151221.search.3.reindex.php b/resources/sql/autopatches/20151221.search.3.reindex.php new file mode 100644 index 0000000000..09556d5ea0 --- /dev/null +++ b/resources/sql/autopatches/20151221.search.3.reindex.php @@ -0,0 +1,11 @@ +getPHID(), + array( + 'force' => true, + )); +} diff --git a/resources/sql/autopatches/20151223.proj.01.paths.sql b/resources/sql/autopatches/20151223.proj.01.paths.sql new file mode 100644 index 0000000000..724a2ff324 --- /dev/null +++ b/resources/sql/autopatches/20151223.proj.01.paths.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project + ADD projectPath VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20151223.proj.02.depths.sql b/resources/sql/autopatches/20151223.proj.02.depths.sql new file mode 100644 index 0000000000..a2d3d238b7 --- /dev/null +++ b/resources/sql/autopatches/20151223.proj.02.depths.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project + ADD projectDepth INT UNSIGNED NOT NULL; diff --git a/resources/sql/autopatches/20151223.proj.03.pathkey.sql b/resources/sql/autopatches/20151223.proj.03.pathkey.sql new file mode 100644 index 0000000000..875bf2675c --- /dev/null +++ b/resources/sql/autopatches/20151223.proj.03.pathkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project + ADD KEY `key_path` (projectPath, projectDepth); diff --git a/resources/sql/autopatches/20151223.proj.04.keycol.sql b/resources/sql/autopatches/20151223.proj.04.keycol.sql new file mode 100644 index 0000000000..bbc938e7ea --- /dev/null +++ b/resources/sql/autopatches/20151223.proj.04.keycol.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project + ADD projectPathKey BINARY(4) NOT NULL; diff --git a/resources/sql/autopatches/20151223.proj.05.updatekeys.php b/resources/sql/autopatches/20151223.proj.05.updatekeys.php new file mode 100644 index 0000000000..fe4a1ffd1c --- /dev/null +++ b/resources/sql/autopatches/20151223.proj.05.updatekeys.php @@ -0,0 +1,24 @@ +establishConnection('w'); + +foreach (new LiskMigrationIterator($table) as $project) { + $path = $project->getProjectPath(); + $key = $project->getProjectPathKey(); + + if (strlen($path) && ($key !== "\0\0\0\0")) { + continue; + } + + $path_key = PhabricatorHash::digestForIndex($project->getPHID()); + $path_key = substr($path_key, 0, 4); + + queryfx( + $conn_w, + 'UPDATE %T SET projectPath = %s, projectPathKey = %s WHERE id = %d', + $project->getTableName(), + $path_key, + $path_key, + $project->getID()); +} diff --git a/resources/sql/autopatches/20151223.proj.06.uniq.sql b/resources/sql/autopatches/20151223.proj.06.uniq.sql new file mode 100644 index 0000000000..2e28c18dbe --- /dev/null +++ b/resources/sql/autopatches/20151223.proj.06.uniq.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project + ADD UNIQUE KEY `key_pathkey` (projectPathKey); diff --git a/scripts/lipsum/manage_lipsum.php b/scripts/lipsum/manage_lipsum.php index c2fa8f3a3b..9d45b573d4 100755 --- a/scripts/lipsum/manage_lipsum.php +++ b/scripts/lipsum/manage_lipsum.php @@ -5,10 +5,10 @@ $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; $args = new PhutilArgumentParser($argv); -$args->setTagline(pht('manage lipsum')); +$args->setTagline(pht('synthetic data generator')); $args->setSynopsis(<< 'applications/almanac/storage/AlmanacNetworkTransaction.php', 'AlmanacNetworkTransactionQuery' => 'applications/almanac/query/AlmanacNetworkTransactionQuery.php', 'AlmanacNetworkViewController' => 'applications/almanac/controller/AlmanacNetworkViewController.php', + 'AlmanacPropertiesDestructionEngineExtension' => 'applications/almanac/engineextension/AlmanacPropertiesDestructionEngineExtension.php', 'AlmanacProperty' => 'applications/almanac/storage/AlmanacProperty.php', 'AlmanacPropertyController' => 'applications/almanac/controller/AlmanacPropertyController.php', 'AlmanacPropertyDeleteController' => 'applications/almanac/controller/AlmanacPropertyDeleteController.php', @@ -284,7 +285,7 @@ phutil_register_library_map(array( 'ConpherenceSettings' => 'applications/conpherence/constants/ConpherenceSettings.php', 'ConpherenceTestCase' => 'applications/conpherence/__tests__/ConpherenceTestCase.php', 'ConpherenceThread' => 'applications/conpherence/storage/ConpherenceThread.php', - 'ConpherenceThreadIndexer' => 'applications/conpherence/search/ConpherenceThreadIndexer.php', + 'ConpherenceThreadIndexEngineExtension' => 'applications/conpherence/engineextension/ConpherenceThreadIndexEngineExtension.php', 'ConpherenceThreadListView' => 'applications/conpherence/view/ConpherenceThreadListView.php', 'ConpherenceThreadMailReceiver' => 'applications/conpherence/mail/ConpherenceThreadMailReceiver.php', 'ConpherenceThreadMembersPolicyRule' => 'applications/conpherence/policyrule/ConpherenceThreadMembersPolicyRule.php', @@ -481,6 +482,7 @@ phutil_register_library_map(array( 'DifferentialRevisionDependsOnRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php', 'DifferentialRevisionDetailView' => 'applications/differential/view/DifferentialRevisionDetailView.php', 'DifferentialRevisionEditController' => 'applications/differential/controller/DifferentialRevisionEditController.php', + 'DifferentialRevisionFulltextEngine' => 'applications/differential/search/DifferentialRevisionFulltextEngine.php', 'DifferentialRevisionHasCommitEdgeType' => 'applications/differential/edge/DifferentialRevisionHasCommitEdgeType.php', 'DifferentialRevisionHasReviewerEdgeType' => 'applications/differential/edge/DifferentialRevisionHasReviewerEdgeType.php', 'DifferentialRevisionHasTaskEdgeType' => 'applications/differential/edge/DifferentialRevisionHasTaskEdgeType.php', @@ -506,7 +508,6 @@ phutil_register_library_map(array( 'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/DifferentialRevisionUpdateHistoryView.php', 'DifferentialRevisionViewController' => 'applications/differential/controller/DifferentialRevisionViewController.php', 'DifferentialSchemaSpec' => 'applications/differential/storage/DifferentialSchemaSpec.php', - 'DifferentialSearchIndexer' => 'applications/differential/search/DifferentialSearchIndexer.php', 'DifferentialSetDiffPropertyConduitAPIMethod' => 'applications/differential/conduit/DifferentialSetDiffPropertyConduitAPIMethod.php', 'DifferentialStoredCustomField' => 'applications/differential/customfield/DifferentialStoredCustomField.php', 'DifferentialSubscribersField' => 'applications/differential/customfield/DifferentialSubscribersField.php', @@ -555,6 +556,7 @@ phutil_register_library_map(array( 'DiffusionCommitDiffContentRemovedHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffContentRemovedHeraldField.php', 'DiffusionCommitDiffEnormousHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffEnormousHeraldField.php', 'DiffusionCommitEditController' => 'applications/diffusion/controller/DiffusionCommitEditController.php', + 'DiffusionCommitFulltextEngine' => 'applications/repository/search/DiffusionCommitFulltextEngine.php', 'DiffusionCommitHasRevisionEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasRevisionEdgeType.php', 'DiffusionCommitHasTaskEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasTaskEdgeType.php', 'DiffusionCommitHash' => 'applications/diffusion/data/DiffusionCommitHash.php', @@ -769,7 +771,6 @@ phutil_register_library_map(array( 'DivinerAtomQuery' => 'applications/diviner/query/DivinerAtomQuery.php', 'DivinerAtomRef' => 'applications/diviner/atom/DivinerAtomRef.php', 'DivinerAtomSearchEngine' => 'applications/diviner/query/DivinerAtomSearchEngine.php', - 'DivinerAtomSearchIndexer' => 'applications/diviner/search/DivinerAtomSearchIndexer.php', 'DivinerAtomizeWorkflow' => 'applications/diviner/workflow/DivinerAtomizeWorkflow.php', 'DivinerAtomizer' => 'applications/diviner/atomizer/DivinerAtomizer.php', 'DivinerBookController' => 'applications/diviner/controller/DivinerBookController.php', @@ -778,7 +779,6 @@ phutil_register_library_map(array( 'DivinerBookItemView' => 'applications/diviner/view/DivinerBookItemView.php', 'DivinerBookPHIDType' => 'applications/diviner/phid/DivinerBookPHIDType.php', 'DivinerBookQuery' => 'applications/diviner/query/DivinerBookQuery.php', - 'DivinerBookSearchIndexer' => 'applications/diviner/search/DivinerBookSearchIndexer.php', 'DivinerController' => 'applications/diviner/controller/DivinerController.php', 'DivinerDAO' => 'applications/diviner/storage/DivinerDAO.php', 'DivinerDefaultEditCapability' => 'applications/diviner/capability/DivinerDefaultEditCapability.php', @@ -791,10 +791,12 @@ phutil_register_library_map(array( 'DivinerLiveAtom' => 'applications/diviner/storage/DivinerLiveAtom.php', 'DivinerLiveBook' => 'applications/diviner/storage/DivinerLiveBook.php', 'DivinerLiveBookEditor' => 'applications/diviner/editor/DivinerLiveBookEditor.php', + 'DivinerLiveBookFulltextEngine' => 'applications/diviner/search/DivinerLiveBookFulltextEngine.php', 'DivinerLiveBookTransaction' => 'applications/diviner/storage/DivinerLiveBookTransaction.php', 'DivinerLiveBookTransactionQuery' => 'applications/diviner/query/DivinerLiveBookTransactionQuery.php', 'DivinerLivePublisher' => 'applications/diviner/publisher/DivinerLivePublisher.php', 'DivinerLiveSymbol' => 'applications/diviner/storage/DivinerLiveSymbol.php', + 'DivinerLiveSymbolFulltextEngine' => 'applications/diviner/search/DivinerLiveSymbolFulltextEngine.php', 'DivinerMainController' => 'applications/diviner/controller/DivinerMainController.php', 'DivinerPHPAtomizer' => 'applications/diviner/atomizer/DivinerPHPAtomizer.php', 'DivinerParameterTableView' => 'applications/diviner/view/DivinerParameterTableView.php', @@ -883,6 +885,7 @@ phutil_register_library_map(array( 'DrydockLeasePHIDType' => 'applications/drydock/phid/DrydockLeasePHIDType.php', 'DrydockLeaseQuery' => 'applications/drydock/query/DrydockLeaseQuery.php', 'DrydockLeaseQueuedLogType' => 'applications/drydock/logtype/DrydockLeaseQueuedLogType.php', + 'DrydockLeaseReclaimLogType' => 'applications/drydock/logtype/DrydockLeaseReclaimLogType.php', 'DrydockLeaseReleaseController' => 'applications/drydock/controller/DrydockLeaseReleaseController.php', 'DrydockLeaseReleasedLogType' => 'applications/drydock/logtype/DrydockLeaseReleasedLogType.php', 'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php', @@ -900,6 +903,7 @@ phutil_register_library_map(array( 'DrydockLogType' => 'applications/drydock/logtype/DrydockLogType.php', 'DrydockManagementCommandWorkflow' => 'applications/drydock/management/DrydockManagementCommandWorkflow.php', 'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php', + 'DrydockManagementReclaimWorkflow' => 'applications/drydock/management/DrydockManagementReclaimWorkflow.php', 'DrydockManagementReleaseLeaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php', 'DrydockManagementReleaseResourceWorkflow' => 'applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php', 'DrydockManagementUpdateLeaseWorkflow' => 'applications/drydock/management/DrydockManagementUpdateLeaseWorkflow.php', @@ -926,6 +930,7 @@ phutil_register_library_map(array( 'DrydockResourceListView' => 'applications/drydock/view/DrydockResourceListView.php', 'DrydockResourcePHIDType' => 'applications/drydock/phid/DrydockResourcePHIDType.php', 'DrydockResourceQuery' => 'applications/drydock/query/DrydockResourceQuery.php', + 'DrydockResourceReclaimLogType' => 'applications/drydock/logtype/DrydockResourceReclaimLogType.php', 'DrydockResourceReleaseController' => 'applications/drydock/controller/DrydockResourceReleaseController.php', 'DrydockResourceSearchEngine' => 'applications/drydock/query/DrydockResourceSearchEngine.php', 'DrydockResourceStatus' => 'applications/drydock/constants/DrydockResourceStatus.php', @@ -982,7 +987,7 @@ phutil_register_library_map(array( 'FundInitiativeCloseController' => 'applications/fund/controller/FundInitiativeCloseController.php', 'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php', 'FundInitiativeEditor' => 'applications/fund/editor/FundInitiativeEditor.php', - 'FundInitiativeIndexer' => 'applications/fund/search/FundInitiativeIndexer.php', + 'FundInitiativeFulltextEngine' => 'applications/fund/search/FundInitiativeFulltextEngine.php', 'FundInitiativeListController' => 'applications/fund/controller/FundInitiativeListController.php', 'FundInitiativePHIDType' => 'applications/fund/phid/FundInitiativePHIDType.php', 'FundInitiativeQuery' => 'applications/fund/query/FundInitiativeQuery.php', @@ -1192,6 +1197,7 @@ phutil_register_library_map(array( 'HeraldTransactionQuery' => 'applications/herald/query/HeraldTransactionQuery.php', 'HeraldTranscript' => 'applications/herald/storage/transcript/HeraldTranscript.php', 'HeraldTranscriptController' => 'applications/herald/controller/HeraldTranscriptController.php', + 'HeraldTranscriptDestructionEngineExtension' => 'applications/herald/engineextension/HeraldTranscriptDestructionEngineExtension.php', 'HeraldTranscriptGarbageCollector' => 'applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php', 'HeraldTranscriptListController' => 'applications/herald/controller/HeraldTranscriptListController.php', 'HeraldTranscriptQuery' => 'applications/herald/query/HeraldTranscriptQuery.php', @@ -1289,9 +1295,9 @@ phutil_register_library_map(array( 'ManiphestHovercardEventListener' => 'applications/maniphest/event/ManiphestHovercardEventListener.php', 'ManiphestInfoConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php', 'ManiphestNameIndex' => 'applications/maniphest/storage/ManiphestNameIndex.php', - 'ManiphestNameIndexEventListener' => 'applications/maniphest/event/ManiphestNameIndexEventListener.php', 'ManiphestPriorityConfigOptionType' => 'applications/maniphest/config/ManiphestPriorityConfigOptionType.php', 'ManiphestPriorityEmailCommand' => 'applications/maniphest/command/ManiphestPriorityEmailCommand.php', + 'ManiphestProjectNameFulltextEngineExtension' => 'applications/maniphest/engineextension/ManiphestProjectNameFulltextEngineExtension.php', 'ManiphestQueryConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php', 'ManiphestQueryStatusesConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestQueryStatusesConduitAPIMethod.php', 'ManiphestRemarkupRule' => 'applications/maniphest/remarkup/ManiphestRemarkupRule.php', @@ -1317,6 +1323,7 @@ phutil_register_library_map(array( 'ManiphestTaskDetailController' => 'applications/maniphest/controller/ManiphestTaskDetailController.php', 'ManiphestTaskEditBulkJobType' => 'applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php', 'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php', + 'ManiphestTaskFulltextEngine' => 'applications/maniphest/search/ManiphestTaskFulltextEngine.php', 'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php', 'ManiphestTaskHasMockEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasMockEdgeType.php', 'ManiphestTaskHasRevisionEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasRevisionEdgeType.php', @@ -1441,6 +1448,7 @@ phutil_register_library_map(array( 'PHUIBadgeExample' => 'applications/uiexample/examples/PHUIBadgeExample.php', 'PHUIBadgeMiniView' => 'view/phui/PHUIBadgeMiniView.php', 'PHUIBadgeView' => 'view/phui/PHUIBadgeView.php', + 'PHUIBigInfoView' => 'view/phui/PHUIBigInfoView.php', 'PHUIBoxExample' => 'applications/uiexample/examples/PHUIBoxExample.php', 'PHUIBoxView' => 'view/phui/PHUIBoxView.php', 'PHUIButtonBarExample' => 'applications/uiexample/examples/PHUIButtonBarExample.php', @@ -1531,6 +1539,7 @@ phutil_register_library_map(array( 'PassphraseCredentialCreateController' => 'applications/passphrase/controller/PassphraseCredentialCreateController.php', 'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php', 'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php', + 'PassphraseCredentialFulltextEngine' => 'applications/passphrase/search/PassphraseCredentialFulltextEngine.php', 'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php', 'PassphraseCredentialLockController' => 'applications/passphrase/controller/PassphraseCredentialLockController.php', 'PassphraseCredentialPHIDType' => 'applications/passphrase/phid/PassphraseCredentialPHIDType.php', @@ -1558,7 +1567,6 @@ phutil_register_library_map(array( 'PassphraseSSHPrivateKeyFileCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHPrivateKeyFileCredentialType.php', 'PassphraseSSHPrivateKeyTextCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHPrivateKeyTextCredentialType.php', 'PassphraseSchemaSpec' => 'applications/passphrase/storage/PassphraseSchemaSpec.php', - 'PassphraseSearchIndexer' => 'applications/passphrase/search/PassphraseSearchIndexer.php', 'PassphraseSecret' => 'applications/passphrase/storage/PassphraseSecret.php', 'PasteConduitAPIMethod' => 'applications/paste/conduit/PasteConduitAPIMethod.php', 'PasteCreateConduitAPIMethod' => 'applications/paste/conduit/PasteCreateConduitAPIMethod.php', @@ -1639,6 +1647,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionNoEffectResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionNoEffectResponse.php', 'PhabricatorApplicationTransactionPublishWorker' => 'applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php', 'PhabricatorApplicationTransactionQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionQuery.php', + 'PhabricatorApplicationTransactionRemarkupPreviewController' => 'applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php', 'PhabricatorApplicationTransactionReplyHandler' => 'applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php', 'PhabricatorApplicationTransactionResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionResponse.php', 'PhabricatorApplicationTransactionShowOlderController' => 'applications/transactions/controller/PhabricatorApplicationTransactionShowOlderController.php', @@ -1748,6 +1757,7 @@ phutil_register_library_map(array( 'PhabricatorAuthSSHKeyDeleteController' => 'applications/auth/controller/PhabricatorAuthSSHKeyDeleteController.php', 'PhabricatorAuthSSHKeyEditController' => 'applications/auth/controller/PhabricatorAuthSSHKeyEditController.php', 'PhabricatorAuthSSHKeyGenerateController' => 'applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php', + 'PhabricatorAuthSSHKeyPHIDType' => 'applications/auth/phid/PhabricatorAuthSSHKeyPHIDType.php', 'PhabricatorAuthSSHKeyQuery' => 'applications/auth/query/PhabricatorAuthSSHKeyQuery.php', 'PhabricatorAuthSSHKeyTableView' => 'applications/auth/view/PhabricatorAuthSSHKeyTableView.php', 'PhabricatorAuthSSHPublicKey' => 'applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php', @@ -1837,6 +1847,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php', 'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php', 'PhabricatorCalendarEventEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventEmailCommand.php', + 'PhabricatorCalendarEventFulltextEngine' => 'applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php', 'PhabricatorCalendarEventInvitee' => 'applications/calendar/storage/PhabricatorCalendarEventInvitee.php', 'PhabricatorCalendarEventInviteeQuery' => 'applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php', 'PhabricatorCalendarEventJoinController' => 'applications/calendar/controller/PhabricatorCalendarEventJoinController.php', @@ -1846,7 +1857,6 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php', 'PhabricatorCalendarEventRSVPEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventRSVPEmailCommand.php', 'PhabricatorCalendarEventSearchEngine' => 'applications/calendar/query/PhabricatorCalendarEventSearchEngine.php', - 'PhabricatorCalendarEventSearchIndexer' => 'applications/calendar/search/PhabricatorCalendarEventSearchIndexer.php', 'PhabricatorCalendarEventTransaction' => 'applications/calendar/storage/PhabricatorCalendarEventTransaction.php', 'PhabricatorCalendarEventTransactionComment' => 'applications/calendar/storage/PhabricatorCalendarEventTransactionComment.php', 'PhabricatorCalendarEventTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarEventTransactionQuery.php', @@ -1873,7 +1883,7 @@ phutil_register_library_map(array( 'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php', 'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php', 'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php', - 'PhabricatorCommentEditEngineExtension' => 'applications/transactions/editengineextension/PhabricatorCommentEditEngineExtension.php', + 'PhabricatorCommentEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php', 'PhabricatorCommentEditField' => 'applications/transactions/editfield/PhabricatorCommentEditField.php', 'PhabricatorCommentEditType' => 'applications/transactions/edittype/PhabricatorCommentEditType.php', 'PhabricatorCommitBranchesField' => 'applications/repository/customfield/PhabricatorCommitBranchesField.php', @@ -2013,6 +2023,7 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldEditEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldEditEngineExtension.php', 'PhabricatorCustomFieldEditField' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php', 'PhabricatorCustomFieldEditType' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php', + 'PhabricatorCustomFieldFulltextEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php', 'PhabricatorCustomFieldHeraldField' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldField.php', 'PhabricatorCustomFieldHeraldFieldGroup' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldFieldGroup.php', 'PhabricatorCustomFieldImplementationIncompleteException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldImplementationIncompleteException.php', @@ -2124,6 +2135,8 @@ phutil_register_library_map(array( 'PhabricatorDesktopNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php', 'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php', 'PhabricatorDestructionEngine' => 'applications/system/engine/PhabricatorDestructionEngine.php', + 'PhabricatorDestructionEngineExtension' => 'applications/system/engine/PhabricatorDestructionEngineExtension.php', + 'PhabricatorDestructionEngineExtensionModule' => 'applications/system/engine/PhabricatorDestructionEngineExtensionModule.php', 'PhabricatorDeveloperConfigOptions' => 'applications/config/option/PhabricatorDeveloperConfigOptions.php', 'PhabricatorDeveloperPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorDeveloperPreferencesSettingsPanel.php', 'PhabricatorDiffInlineCommentQuery' => 'infrastructure/diff/query/PhabricatorDiffInlineCommentQuery.php', @@ -2152,6 +2165,7 @@ phutil_register_library_map(array( 'PhabricatorEdgeTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeTestCase.php', 'PhabricatorEdgeType' => 'infrastructure/edges/type/PhabricatorEdgeType.php', 'PhabricatorEdgeTypeTestCase' => 'infrastructure/edges/type/__tests__/PhabricatorEdgeTypeTestCase.php', + 'PhabricatorEdgesDestructionEngineExtension' => 'infrastructure/edges/engineextension/PhabricatorEdgesDestructionEngineExtension.php', 'PhabricatorEditEngine' => 'applications/transactions/editengine/PhabricatorEditEngine.php', 'PhabricatorEditEngineAPIMethod' => 'applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php', 'PhabricatorEditEngineCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php', @@ -2175,8 +2189,8 @@ phutil_register_library_map(array( 'PhabricatorEditEngineConfigurationTransactionQuery' => 'applications/transactions/query/PhabricatorEditEngineConfigurationTransactionQuery.php', 'PhabricatorEditEngineConfigurationViewController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php', 'PhabricatorEditEngineController' => 'applications/transactions/controller/PhabricatorEditEngineController.php', - 'PhabricatorEditEngineExtension' => 'applications/transactions/editengineextension/PhabricatorEditEngineExtension.php', - 'PhabricatorEditEngineExtensionModule' => 'applications/transactions/editengineextension/PhabricatorEditEngineExtensionModule.php', + 'PhabricatorEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorEditEngineExtension.php', + 'PhabricatorEditEngineExtensionModule' => 'applications/transactions/engineextension/PhabricatorEditEngineExtensionModule.php', 'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php', 'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php', 'PhabricatorEditEngineSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineSearchEngine.php', @@ -2185,7 +2199,7 @@ phutil_register_library_map(array( 'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php', 'PhabricatorEditType' => 'applications/transactions/edittype/PhabricatorEditType.php', 'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php', - 'PhabricatorElasticSearchEngine' => 'applications/search/engine/PhabricatorElasticSearchEngine.php', + 'PhabricatorElasticFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php', 'PhabricatorElasticSearchSetupCheck' => 'applications/config/check/PhabricatorElasticSearchSetupCheck.php', 'PhabricatorEmailAddressesSettingsPanel' => 'applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php', 'PhabricatorEmailFormatSettingsPanel' => 'applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php', @@ -2310,6 +2324,7 @@ phutil_register_library_map(array( 'PhabricatorFlagController' => 'applications/flag/controller/PhabricatorFlagController.php', 'PhabricatorFlagDAO' => 'applications/flag/storage/PhabricatorFlagDAO.php', 'PhabricatorFlagDeleteController' => 'applications/flag/controller/PhabricatorFlagDeleteController.php', + 'PhabricatorFlagDestructionEngineExtension' => 'applications/flag/engineextension/PhabricatorFlagDestructionEngineExtension.php', 'PhabricatorFlagEditController' => 'applications/flag/controller/PhabricatorFlagEditController.php', 'PhabricatorFlagListController' => 'applications/flag/controller/PhabricatorFlagListController.php', 'PhabricatorFlagQuery' => 'applications/flag/query/PhabricatorFlagQuery.php', @@ -2318,6 +2333,12 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface' => 'applications/flag/interface/PhabricatorFlaggableInterface.php', 'PhabricatorFlagsApplication' => 'applications/flag/application/PhabricatorFlagsApplication.php', 'PhabricatorFlagsUIEventListener' => 'applications/flag/events/PhabricatorFlagsUIEventListener.php', + 'PhabricatorFulltextEngine' => 'applications/search/index/PhabricatorFulltextEngine.php', + 'PhabricatorFulltextEngineExtension' => 'applications/search/index/PhabricatorFulltextEngineExtension.php', + 'PhabricatorFulltextEngineExtensionModule' => 'applications/search/index/PhabricatorFulltextEngineExtensionModule.php', + 'PhabricatorFulltextIndexEngineExtension' => 'applications/search/engineextension/PhabricatorFulltextIndexEngineExtension.php', + 'PhabricatorFulltextInterface' => 'applications/search/interface/PhabricatorFulltextInterface.php', + 'PhabricatorFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php', 'PhabricatorFundApplication' => 'applications/fund/application/PhabricatorFundApplication.php', 'PhabricatorGDSetupCheck' => 'applications/config/check/PhabricatorGDSetupCheck.php', 'PhabricatorGarbageCollector' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php', @@ -2366,6 +2387,9 @@ phutil_register_library_map(array( 'PhabricatorImageMacroRemarkupRule' => 'applications/macro/markup/PhabricatorImageMacroRemarkupRule.php', 'PhabricatorImageTransformer' => 'applications/files/PhabricatorImageTransformer.php', 'PhabricatorImagemagickSetupCheck' => 'applications/config/check/PhabricatorImagemagickSetupCheck.php', + 'PhabricatorIndexEngine' => 'applications/search/index/PhabricatorIndexEngine.php', + 'PhabricatorIndexEngineExtension' => 'applications/search/index/PhabricatorIndexEngineExtension.php', + 'PhabricatorIndexEngineExtensionModule' => 'applications/search/index/PhabricatorIndexEngineExtensionModule.php', 'PhabricatorInfrastructureTestCase' => '__tests__/PhabricatorInfrastructureTestCase.php', 'PhabricatorInlineCommentController' => 'infrastructure/diff/PhabricatorInlineCommentController.php', 'PhabricatorInlineCommentInterface' => 'infrastructure/diff/interface/PhabricatorInlineCommentInterface.php', @@ -2393,6 +2417,7 @@ phutil_register_library_map(array( 'PhabricatorLipsumManagementWorkflow' => 'applications/lipsum/management/PhabricatorLipsumManagementWorkflow.php', 'PhabricatorLipsumMondrianArtist' => 'applications/lipsum/image/PhabricatorLipsumMondrianArtist.php', 'PhabricatorLiskDAO' => 'infrastructure/storage/lisk/PhabricatorLiskDAO.php', + 'PhabricatorLiskFulltextEngineExtension' => 'applications/search/engineextension/PhabricatorLiskFulltextEngineExtension.php', 'PhabricatorLiskSearchEngineExtension' => 'applications/search/engineextension/PhabricatorLiskSearchEngineExtension.php', 'PhabricatorLiskSerializer' => 'infrastructure/storage/lisk/PhabricatorLiskSerializer.php', 'PhabricatorListFilterUIExample' => 'applications/uiexample/examples/PhabricatorListFilterUIExample.php', @@ -2522,17 +2547,20 @@ phutil_register_library_map(array( 'PhabricatorMustVerifyEmailController' => 'applications/auth/controller/PhabricatorMustVerifyEmailController.php', 'PhabricatorMySQLConfigOptions' => 'applications/config/option/PhabricatorMySQLConfigOptions.php', 'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/PhabricatorMySQLFileStorageEngine.php', - 'PhabricatorMySQLSearchEngine' => 'applications/search/engine/PhabricatorMySQLSearchEngine.php', + 'PhabricatorMySQLFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php', 'PhabricatorMySQLSetupCheck' => 'applications/config/check/PhabricatorMySQLSetupCheck.php', 'PhabricatorNamedQuery' => 'applications/search/storage/PhabricatorNamedQuery.php', 'PhabricatorNamedQueryQuery' => 'applications/search/query/PhabricatorNamedQueryQuery.php', 'PhabricatorNavigationRemarkupRule' => 'infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php', 'PhabricatorNeverTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorNeverTriggerClock.php', + 'PhabricatorNgramsIndexEngineExtension' => 'applications/search/engineextension/PhabricatorNgramsIndexEngineExtension.php', + 'PhabricatorNgramsInterface' => 'applications/search/interface/PhabricatorNgramsInterface.php', 'PhabricatorNotificationBuilder' => 'applications/notification/builder/PhabricatorNotificationBuilder.php', 'PhabricatorNotificationClearController' => 'applications/notification/controller/PhabricatorNotificationClearController.php', 'PhabricatorNotificationClient' => 'applications/notification/client/PhabricatorNotificationClient.php', 'PhabricatorNotificationConfigOptions' => 'applications/config/option/PhabricatorNotificationConfigOptions.php', 'PhabricatorNotificationController' => 'applications/notification/controller/PhabricatorNotificationController.php', + 'PhabricatorNotificationDestructionEngineExtension' => 'applications/notification/engineextension/PhabricatorNotificationDestructionEngineExtension.php', 'PhabricatorNotificationIndividualController' => 'applications/notification/controller/PhabricatorNotificationIndividualController.php', 'PhabricatorNotificationListController' => 'applications/notification/controller/PhabricatorNotificationListController.php', 'PhabricatorNotificationPanelController' => 'applications/notification/controller/PhabricatorNotificationPanelController.php', @@ -2615,7 +2643,9 @@ phutil_register_library_map(array( 'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php', 'PhabricatorOwnersPackageDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php', 'PhabricatorOwnersPackageEditEngine' => 'applications/owners/editor/PhabricatorOwnersPackageEditEngine.php', + 'PhabricatorOwnersPackageFulltextEngine' => 'applications/owners/query/PhabricatorOwnersPackageFulltextEngine.php', 'PhabricatorOwnersPackageFunctionDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageFunctionDatasource.php', + 'PhabricatorOwnersPackageNameNgrams' => 'applications/owners/storage/PhabricatorOwnersPackageNameNgrams.php', 'PhabricatorOwnersPackageOwnerDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php', 'PhabricatorOwnersPackagePHIDType' => 'applications/owners/phid/PhabricatorOwnersPackagePHIDType.php', 'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php', @@ -2741,13 +2771,16 @@ phutil_register_library_map(array( 'PhabricatorPhurlURLEditController' => 'applications/phurl/controller/PhabricatorPhurlURLEditController.php', 'PhabricatorPhurlURLEditor' => 'applications/phurl/editor/PhabricatorPhurlURLEditor.php', 'PhabricatorPhurlURLListController' => 'applications/phurl/controller/PhabricatorPhurlURLListController.php', + 'PhabricatorPhurlURLMailReceiver' => 'applications/phurl/mail/PhabricatorPhurlURLMailReceiver.php', 'PhabricatorPhurlURLPHIDType' => 'applications/phurl/phid/PhabricatorPhurlURLPHIDType.php', 'PhabricatorPhurlURLQuery' => 'applications/phurl/query/PhabricatorPhurlURLQuery.php', + 'PhabricatorPhurlURLReplyHandler' => 'applications/phurl/mail/PhabricatorPhurlURLReplyHandler.php', 'PhabricatorPhurlURLSearchEngine' => 'applications/phurl/query/PhabricatorPhurlURLSearchEngine.php', 'PhabricatorPhurlURLTransaction' => 'applications/phurl/storage/PhabricatorPhurlURLTransaction.php', 'PhabricatorPhurlURLTransactionComment' => 'applications/phurl/storage/PhabricatorPhurlURLTransactionComment.php', 'PhabricatorPhurlURLTransactionQuery' => 'applications/phurl/query/PhabricatorPhurlURLTransactionQuery.php', 'PhabricatorPhurlURLViewController' => 'applications/phurl/controller/PhabricatorPhurlURLViewController.php', + 'PhabricatorPirateEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorPirateEnglishTranslation.php', 'PhabricatorPlatformSite' => 'aphront/site/PhabricatorPlatformSite.php', 'PhabricatorPolicies' => 'applications/policy/constants/PhabricatorPolicies.php', 'PhabricatorPolicy' => 'applications/policy/storage/PhabricatorPolicy.php', @@ -2805,6 +2838,7 @@ phutil_register_library_map(array( 'PhabricatorProjectConfigOptions' => 'applications/project/config/PhabricatorProjectConfigOptions.php', 'PhabricatorProjectConfiguredCustomField' => 'applications/project/customfield/PhabricatorProjectConfiguredCustomField.php', 'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php', + 'PhabricatorProjectCoreTestCase' => 'applications/project/__tests__/PhabricatorProjectCoreTestCase.php', 'PhabricatorProjectCustomField' => 'applications/project/customfield/PhabricatorProjectCustomField.php', 'PhabricatorProjectCustomFieldNumericIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php', 'PhabricatorProjectCustomFieldStorage' => 'applications/project/storage/PhabricatorProjectCustomFieldStorage.php', @@ -2814,8 +2848,8 @@ phutil_register_library_map(array( 'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php', 'PhabricatorProjectEditDetailsController' => 'applications/project/controller/PhabricatorProjectEditDetailsController.php', 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', - 'PhabricatorProjectEditorTestCase' => 'applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php', 'PhabricatorProjectFeedController' => 'applications/project/controller/PhabricatorProjectFeedController.php', + 'PhabricatorProjectFulltextEngine' => 'applications/project/search/PhabricatorProjectFulltextEngine.php', 'PhabricatorProjectHeraldAction' => 'applications/project/herald/PhabricatorProjectHeraldAction.php', 'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php', 'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php', @@ -2825,11 +2859,14 @@ phutil_register_library_map(array( 'PhabricatorProjectLogicalOrNotDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php', 'PhabricatorProjectLogicalUserDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalUserDatasource.php', 'PhabricatorProjectLogicalViewerDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php', + 'PhabricatorProjectMaterializedMemberEdgeType' => 'applications/project/edge/PhabricatorProjectMaterializedMemberEdgeType.php', 'PhabricatorProjectMemberOfProjectEdgeType' => 'applications/project/edge/PhabricatorProjectMemberOfProjectEdgeType.php', 'PhabricatorProjectMembersDatasource' => 'applications/project/typeahead/PhabricatorProjectMembersDatasource.php', 'PhabricatorProjectMembersEditController' => 'applications/project/controller/PhabricatorProjectMembersEditController.php', + 'PhabricatorProjectMembersPolicyRule' => 'applications/project/policyrule/PhabricatorProjectMembersPolicyRule.php', 'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php', 'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php', + 'PhabricatorProjectNameContextFreeGrammar' => 'applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php', 'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php', 'PhabricatorProjectObjectHasProjectEdgeType' => 'applications/project/edge/PhabricatorProjectObjectHasProjectEdgeType.php', 'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php', @@ -2844,7 +2881,6 @@ phutil_register_library_map(array( 'PhabricatorProjectSchemaSpec' => 'applications/project/storage/PhabricatorProjectSchemaSpec.php', 'PhabricatorProjectSearchEngine' => 'applications/project/query/PhabricatorProjectSearchEngine.php', 'PhabricatorProjectSearchField' => 'applications/project/searchfield/PhabricatorProjectSearchField.php', - 'PhabricatorProjectSearchIndexer' => 'applications/project/search/PhabricatorProjectSearchIndexer.php', 'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php', 'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php', 'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php', @@ -2859,6 +2895,8 @@ phutil_register_library_map(array( 'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php', 'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php', 'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php', + 'PhabricatorProjectsFulltextEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php', + 'PhabricatorProjectsMembershipIndexEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php', 'PhabricatorProjectsPolicyRule' => 'applications/project/policyrule/PhabricatorProjectsPolicyRule.php', 'PhabricatorProjectsSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineAttachment.php', 'PhabricatorProjectsSearchEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineExtension.php', @@ -2897,7 +2935,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryCommitPHIDType' => 'applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php', 'PhabricatorRepositoryCommitParserWorker' => 'applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php', 'PhabricatorRepositoryCommitRef' => 'applications/repository/engine/PhabricatorRepositoryCommitRef.php', - 'PhabricatorRepositoryCommitSearchIndexer' => 'applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php', 'PhabricatorRepositoryConfigOptions' => 'applications/repository/config/PhabricatorRepositoryConfigOptions.php', 'PhabricatorRepositoryDAO' => 'applications/repository/storage/PhabricatorRepositoryDAO.php', 'PhabricatorRepositoryDiscoveryEngine' => 'applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php', @@ -3009,12 +3046,10 @@ phutil_register_library_map(array( 'PhabricatorSearchDocument' => 'applications/search/storage/document/PhabricatorSearchDocument.php', 'PhabricatorSearchDocumentField' => 'applications/search/storage/document/PhabricatorSearchDocumentField.php', 'PhabricatorSearchDocumentFieldType' => 'applications/search/constants/PhabricatorSearchDocumentFieldType.php', - 'PhabricatorSearchDocumentIndexer' => 'applications/search/index/PhabricatorSearchDocumentIndexer.php', 'PhabricatorSearchDocumentQuery' => 'applications/search/query/PhabricatorSearchDocumentQuery.php', 'PhabricatorSearchDocumentRelationship' => 'applications/search/storage/document/PhabricatorSearchDocumentRelationship.php', 'PhabricatorSearchDocumentTypeDatasource' => 'applications/search/typeahead/PhabricatorSearchDocumentTypeDatasource.php', 'PhabricatorSearchEditController' => 'applications/search/controller/PhabricatorSearchEditController.php', - 'PhabricatorSearchEngine' => 'applications/search/engine/PhabricatorSearchEngine.php', 'PhabricatorSearchEngineAPIMethod' => 'applications/search/engine/PhabricatorSearchEngineAPIMethod.php', 'PhabricatorSearchEngineAttachment' => 'applications/search/engineextension/PhabricatorSearchEngineAttachment.php', 'PhabricatorSearchEngineExtension' => 'applications/search/engineextension/PhabricatorSearchEngineExtension.php', @@ -3022,10 +3057,13 @@ phutil_register_library_map(array( 'PhabricatorSearchEngineTestCase' => 'applications/search/engine/__tests__/PhabricatorSearchEngineTestCase.php', 'PhabricatorSearchField' => 'applications/search/field/PhabricatorSearchField.php', 'PhabricatorSearchHovercardController' => 'applications/search/controller/PhabricatorSearchHovercardController.php', - 'PhabricatorSearchIndexer' => 'applications/search/index/PhabricatorSearchIndexer.php', + 'PhabricatorSearchIndexVersion' => 'applications/search/storage/PhabricatorSearchIndexVersion.php', + 'PhabricatorSearchIndexVersionDestructionEngineExtension' => 'applications/search/engineextension/PhabricatorSearchIndexVersionDestructionEngineExtension.php', 'PhabricatorSearchManagementIndexWorkflow' => 'applications/search/management/PhabricatorSearchManagementIndexWorkflow.php', 'PhabricatorSearchManagementInitWorkflow' => 'applications/search/management/PhabricatorSearchManagementInitWorkflow.php', 'PhabricatorSearchManagementWorkflow' => 'applications/search/management/PhabricatorSearchManagementWorkflow.php', + 'PhabricatorSearchNgrams' => 'applications/search/ngrams/PhabricatorSearchNgrams.php', + 'PhabricatorSearchNgramsDestructionEngineExtension' => 'applications/search/engineextension/PhabricatorSearchNgramsDestructionEngineExtension.php', 'PhabricatorSearchOrderController' => 'applications/search/controller/PhabricatorSearchOrderController.php', 'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php', 'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php', @@ -3159,6 +3197,7 @@ phutil_register_library_map(array( 'PhabricatorSubscriptionsEditController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php', 'PhabricatorSubscriptionsEditEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php', 'PhabricatorSubscriptionsEditor' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php', + 'PhabricatorSubscriptionsFulltextEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsFulltextEngineExtension.php', 'PhabricatorSubscriptionsHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php', 'PhabricatorSubscriptionsListController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsListController.php', 'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php', @@ -3208,6 +3247,7 @@ phutil_register_library_map(array( 'PhabricatorTokenCount' => 'applications/tokens/storage/PhabricatorTokenCount.php', 'PhabricatorTokenCountQuery' => 'applications/tokens/query/PhabricatorTokenCountQuery.php', 'PhabricatorTokenDAO' => 'applications/tokens/storage/PhabricatorTokenDAO.php', + 'PhabricatorTokenDestructionEngineExtension' => 'applications/tokens/engineextension/PhabricatorTokenDestructionEngineExtension.php', 'PhabricatorTokenGiveController' => 'applications/tokens/controller/PhabricatorTokenGiveController.php', 'PhabricatorTokenGiven' => 'applications/tokens/storage/PhabricatorTokenGiven.php', 'PhabricatorTokenGivenController' => 'applications/tokens/controller/PhabricatorTokenGivenController.php', @@ -3226,6 +3266,8 @@ phutil_register_library_map(array( 'PhabricatorTooltipUIExample' => 'applications/uiexample/examples/PhabricatorTooltipUIExample.php', 'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php', 'PhabricatorTransactionsApplication' => 'applications/transactions/application/PhabricatorTransactionsApplication.php', + 'PhabricatorTransactionsDestructionEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsDestructionEngineExtension.php', + 'PhabricatorTransactionsFulltextEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php', 'PhabricatorTransformedFile' => 'applications/files/storage/PhabricatorTransformedFile.php', 'PhabricatorTranslationsConfigOptions' => 'applications/config/option/PhabricatorTranslationsConfigOptions.php', 'PhabricatorTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorTriggerAction.php', @@ -3266,6 +3308,7 @@ phutil_register_library_map(array( 'PhabricatorUserEditorTestCase' => 'applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php', 'PhabricatorUserEmail' => 'applications/people/storage/PhabricatorUserEmail.php', 'PhabricatorUserEmailTestCase' => 'applications/people/storage/__tests__/PhabricatorUserEmailTestCase.php', + 'PhabricatorUserFulltextEngine' => 'applications/people/search/PhabricatorUserFulltextEngine.php', 'PhabricatorUserLog' => 'applications/people/storage/PhabricatorUserLog.php', 'PhabricatorUserLogView' => 'applications/people/view/PhabricatorUserLogView.php', 'PhabricatorUserPHIDResolver' => 'applications/phid/resolver/PhabricatorUserPHIDResolver.php', @@ -3275,7 +3318,6 @@ phutil_register_library_map(array( 'PhabricatorUserRealNameField' => 'applications/people/customfield/PhabricatorUserRealNameField.php', 'PhabricatorUserRolesField' => 'applications/people/customfield/PhabricatorUserRolesField.php', 'PhabricatorUserSchemaSpec' => 'applications/people/storage/PhabricatorUserSchemaSpec.php', - 'PhabricatorUserSearchIndexer' => 'applications/people/search/PhabricatorUserSearchIndexer.php', 'PhabricatorUserSinceField' => 'applications/people/customfield/PhabricatorUserSinceField.php', 'PhabricatorUserStatusField' => 'applications/people/customfield/PhabricatorUserStatusField.php', 'PhabricatorUserTestCase' => 'applications/people/storage/__tests__/PhabricatorUserTestCase.php', @@ -3308,6 +3350,7 @@ phutil_register_library_map(array( 'PhabricatorWorkerBulkJobWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobWorker.php', 'PhabricatorWorkerBulkTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkTask.php', 'PhabricatorWorkerDAO' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerDAO.php', + 'PhabricatorWorkerDestructionEngineExtension' => 'infrastructure/daemon/workers/engineextension/PhabricatorWorkerDestructionEngineExtension.php', 'PhabricatorWorkerLeaseQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php', 'PhabricatorWorkerManagementCancelWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementCancelWorkflow.php', 'PhabricatorWorkerManagementExecuteWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementExecuteWorkflow.php', @@ -3431,6 +3474,7 @@ phutil_register_library_map(array( 'PholioMockEditController' => 'applications/pholio/controller/PholioMockEditController.php', 'PholioMockEditor' => 'applications/pholio/editor/PholioMockEditor.php', 'PholioMockEmbedView' => 'applications/pholio/view/PholioMockEmbedView.php', + 'PholioMockFulltextEngine' => 'applications/pholio/search/PholioMockFulltextEngine.php', 'PholioMockHasTaskEdgeType' => 'applications/pholio/edge/PholioMockHasTaskEdgeType.php', 'PholioMockHeraldField' => 'applications/pholio/herald/PholioMockHeraldField.php', 'PholioMockHeraldFieldGroup' => 'applications/pholio/herald/PholioMockHeraldFieldGroup.php', @@ -3446,7 +3490,6 @@ phutil_register_library_map(array( 'PholioRemarkupRule' => 'applications/pholio/remarkup/PholioRemarkupRule.php', 'PholioReplyHandler' => 'applications/pholio/mail/PholioReplyHandler.php', 'PholioSchemaSpec' => 'applications/pholio/storage/PholioSchemaSpec.php', - 'PholioSearchIndexer' => 'applications/pholio/search/PholioSearchIndexer.php', 'PholioTransaction' => 'applications/pholio/storage/PholioTransaction.php', 'PholioTransactionComment' => 'applications/pholio/storage/PholioTransactionComment.php', 'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php', @@ -3618,6 +3661,7 @@ phutil_register_library_map(array( 'PhrictionDocumentAuthorHeraldField' => 'applications/phriction/herald/PhrictionDocumentAuthorHeraldField.php', 'PhrictionDocumentContentHeraldField' => 'applications/phriction/herald/PhrictionDocumentContentHeraldField.php', 'PhrictionDocumentController' => 'applications/phriction/controller/PhrictionDocumentController.php', + 'PhrictionDocumentFulltextEngine' => 'applications/phriction/search/PhrictionDocumentFulltextEngine.php', 'PhrictionDocumentHeraldAdapter' => 'applications/phriction/herald/PhrictionDocumentHeraldAdapter.php', 'PhrictionDocumentHeraldField' => 'applications/phriction/herald/PhrictionDocumentHeraldField.php', 'PhrictionDocumentHeraldFieldGroup' => 'applications/phriction/herald/PhrictionDocumentHeraldFieldGroup.php', @@ -3638,7 +3682,6 @@ phutil_register_library_map(array( 'PhrictionReplyHandler' => 'applications/phriction/mail/PhrictionReplyHandler.php', 'PhrictionSchemaSpec' => 'applications/phriction/storage/PhrictionSchemaSpec.php', 'PhrictionSearchEngine' => 'applications/phriction/query/PhrictionSearchEngine.php', - 'PhrictionSearchIndexer' => 'applications/phriction/search/PhrictionSearchIndexer.php', 'PhrictionTransaction' => 'applications/phriction/storage/PhrictionTransaction.php', 'PhrictionTransactionComment' => 'applications/phriction/storage/PhrictionTransactionComment.php', 'PhrictionTransactionEditor' => 'applications/phriction/editor/PhrictionTransactionEditor.php', @@ -3673,6 +3716,7 @@ phutil_register_library_map(array( 'PonderQuestionCommentController' => 'applications/ponder/controller/PonderQuestionCommentController.php', 'PonderQuestionEditController' => 'applications/ponder/controller/PonderQuestionEditController.php', 'PonderQuestionEditor' => 'applications/ponder/editor/PonderQuestionEditor.php', + 'PonderQuestionFulltextEngine' => 'applications/ponder/search/PonderQuestionFulltextEngine.php', 'PonderQuestionHistoryController' => 'applications/ponder/controller/PonderQuestionHistoryController.php', 'PonderQuestionListController' => 'applications/ponder/controller/PonderQuestionListController.php', 'PonderQuestionMailReceiver' => 'applications/ponder/mail/PonderQuestionMailReceiver.php', @@ -3688,7 +3732,6 @@ phutil_register_library_map(array( 'PonderQuestionViewController' => 'applications/ponder/controller/PonderQuestionViewController.php', 'PonderRemarkupRule' => 'applications/ponder/remarkup/PonderRemarkupRule.php', 'PonderSchemaSpec' => 'applications/ponder/storage/PonderSchemaSpec.php', - 'PonderSearchIndexer' => 'applications/ponder/search/PonderSearchIndexer.php', 'PonderVotableInterface' => 'applications/ponder/storage/PonderVotableInterface.php', 'PonderVote' => 'applications/ponder/constants/PonderVote.php', 'PonderVoteEditor' => 'applications/ponder/editor/PonderVoteEditor.php', @@ -3922,6 +3965,7 @@ phutil_register_library_map(array( 'AlmanacNetworkTransaction' => 'PhabricatorApplicationTransaction', 'AlmanacNetworkTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacNetworkViewController' => 'AlmanacNetworkController', + 'AlmanacPropertiesDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'AlmanacProperty' => array( 'PhabricatorCustomFieldStorage', 'PhabricatorPolicyInterface', @@ -4162,7 +4206,7 @@ phutil_register_library_map(array( 'PhabricatorMentionableInterface', 'PhabricatorDestructibleInterface', ), - 'ConpherenceThreadIndexer' => 'PhabricatorSearchDocumentIndexer', + 'ConpherenceThreadIndexEngineExtension' => 'PhabricatorIndexEngineExtension', 'ConpherenceThreadListView' => 'AphrontView', 'ConpherenceThreadMailReceiver' => 'PhabricatorObjectMailReceiver', 'ConpherenceThreadMembersPolicyRule' => 'PhabricatorPolicyRule', @@ -4377,6 +4421,7 @@ phutil_register_library_map(array( 'PhabricatorMentionableInterface', 'PhabricatorDestructibleInterface', 'PhabricatorProjectInterface', + 'PhabricatorFulltextInterface', ), 'DifferentialRevisionAffectedFilesHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionAuthorHeraldField' => 'DifferentialRevisionHeraldField', @@ -4390,6 +4435,7 @@ phutil_register_library_map(array( 'DifferentialRevisionDependsOnRevisionEdgeType' => 'PhabricatorEdgeType', 'DifferentialRevisionDetailView' => 'AphrontView', 'DifferentialRevisionEditController' => 'DifferentialController', + 'DifferentialRevisionFulltextEngine' => 'PhabricatorFulltextEngine', 'DifferentialRevisionHasCommitEdgeType' => 'PhabricatorEdgeType', 'DifferentialRevisionHasReviewerEdgeType' => 'PhabricatorEdgeType', 'DifferentialRevisionHasTaskEdgeType' => 'PhabricatorEdgeType', @@ -4415,7 +4461,6 @@ phutil_register_library_map(array( 'DifferentialRevisionUpdateHistoryView' => 'AphrontView', 'DifferentialRevisionViewController' => 'DifferentialController', 'DifferentialSchemaSpec' => 'PhabricatorConfigSchemaSpec', - 'DifferentialSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'DifferentialSetDiffPropertyConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialStoredCustomField' => 'DifferentialCustomField', 'DifferentialSubscribersField' => 'DifferentialCoreCustomField', @@ -4464,6 +4509,7 @@ phutil_register_library_map(array( 'DiffusionCommitDiffContentRemovedHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitDiffEnormousHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitEditController' => 'DiffusionController', + 'DiffusionCommitFulltextEngine' => 'PhabricatorFulltextEngine', 'DiffusionCommitHasRevisionEdgeType' => 'PhabricatorEdgeType', 'DiffusionCommitHasTaskEdgeType' => 'PhabricatorEdgeType', 'DiffusionCommitHash' => 'Phobject', @@ -4678,7 +4724,6 @@ phutil_register_library_map(array( 'DivinerAtomQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DivinerAtomRef' => 'Phobject', 'DivinerAtomSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'DivinerAtomSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'DivinerAtomizeWorkflow' => 'DivinerWorkflow', 'DivinerAtomizer' => 'Phobject', 'DivinerBookController' => 'DivinerController', @@ -4687,7 +4732,6 @@ phutil_register_library_map(array( 'DivinerBookItemView' => 'AphrontTagView', 'DivinerBookPHIDType' => 'PhabricatorPHIDType', 'DivinerBookQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', - 'DivinerBookSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'DivinerController' => 'PhabricatorController', 'DivinerDAO' => 'PhabricatorLiskDAO', 'DivinerDefaultEditCapability' => 'PhabricatorPolicyCapability', @@ -4704,8 +4748,10 @@ phutil_register_library_map(array( 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', 'PhabricatorApplicationTransactionInterface', + 'PhabricatorFulltextInterface', ), 'DivinerLiveBookEditor' => 'PhabricatorApplicationTransactionEditor', + 'DivinerLiveBookFulltextEngine' => 'PhabricatorFulltextEngine', 'DivinerLiveBookTransaction' => 'PhabricatorApplicationTransaction', 'DivinerLiveBookTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'DivinerLivePublisher' => 'DivinerPublisher', @@ -4714,7 +4760,9 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorMarkupInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorFulltextInterface', ), + 'DivinerLiveSymbolFulltextEngine' => 'PhabricatorFulltextEngine', 'DivinerMainController' => 'DivinerController', 'DivinerPHPAtomizer' => 'DivinerAtomizer', 'DivinerParameterTableView' => 'AphrontTagView', @@ -4823,6 +4871,7 @@ phutil_register_library_map(array( 'DrydockLeasePHIDType' => 'PhabricatorPHIDType', 'DrydockLeaseQuery' => 'DrydockQuery', 'DrydockLeaseQueuedLogType' => 'DrydockLogType', + 'DrydockLeaseReclaimLogType' => 'DrydockLogType', 'DrydockLeaseReleaseController' => 'DrydockLeaseController', 'DrydockLeaseReleasedLogType' => 'DrydockLogType', 'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine', @@ -4843,6 +4892,7 @@ phutil_register_library_map(array( 'DrydockLogType' => 'Phobject', 'DrydockManagementCommandWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow', + 'DrydockManagementReclaimWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementReleaseLeaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementReleaseResourceWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementUpdateLeaseWorkflow' => 'DrydockManagementWorkflow', @@ -4875,6 +4925,7 @@ phutil_register_library_map(array( 'DrydockResourceListView' => 'AphrontView', 'DrydockResourcePHIDType' => 'PhabricatorPHIDType', 'DrydockResourceQuery' => 'DrydockQuery', + 'DrydockResourceReclaimLogType' => 'DrydockLogType', 'DrydockResourceReleaseController' => 'DrydockResourceController', 'DrydockResourceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockResourceStatus' => 'DrydockConstants', @@ -4940,12 +4991,13 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorFulltextInterface', ), 'FundInitiativeBackController' => 'FundController', 'FundInitiativeCloseController' => 'FundController', 'FundInitiativeEditController' => 'FundController', 'FundInitiativeEditor' => 'PhabricatorApplicationTransactionEditor', - 'FundInitiativeIndexer' => 'PhabricatorSearchDocumentIndexer', + 'FundInitiativeFulltextEngine' => 'PhabricatorFulltextEngine', 'FundInitiativeListController' => 'FundController', 'FundInitiativePHIDType' => 'PhabricatorPHIDType', 'FundInitiativeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', @@ -5204,6 +5256,7 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', ), 'HeraldTranscriptController' => 'HeraldController', + 'HeraldTranscriptDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'HeraldTranscriptGarbageCollector' => 'PhabricatorGarbageCollector', 'HeraldTranscriptListController' => 'HeraldController', 'HeraldTranscriptQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', @@ -5316,9 +5369,9 @@ phutil_register_library_map(array( 'ManiphestHovercardEventListener' => 'PhabricatorEventListener', 'ManiphestInfoConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestNameIndex' => 'ManiphestDAO', - 'ManiphestNameIndexEventListener' => 'PhabricatorEventListener', 'ManiphestPriorityConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'ManiphestPriorityEmailCommand' => 'ManiphestEmailCommand', + 'ManiphestProjectNameFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 'ManiphestQueryConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestQueryStatusesConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestRemarkupRule' => 'PhabricatorObjectRemarkupRule', @@ -5345,6 +5398,7 @@ phutil_register_library_map(array( 'PhabricatorProjectInterface', 'PhabricatorSpacesInterface', 'PhabricatorConduitResultInterface', + 'PhabricatorFulltextInterface', ), 'ManiphestTaskAssignHeraldAction' => 'HeraldAction', 'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction', @@ -5359,6 +5413,7 @@ phutil_register_library_map(array( 'ManiphestTaskDetailController' => 'ManiphestController', 'ManiphestTaskEditBulkJobType' => 'PhabricatorWorkerBulkJobType', 'ManiphestTaskEditController' => 'ManiphestController', + 'ManiphestTaskFulltextEngine' => 'PhabricatorFulltextEngine', 'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskHasMockEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskHasRevisionEdgeType' => 'PhabricatorEdgeType', @@ -5499,6 +5554,7 @@ phutil_register_library_map(array( 'PHUIBadgeExample' => 'PhabricatorUIExample', 'PHUIBadgeMiniView' => 'AphrontTagView', 'PHUIBadgeView' => 'AphrontTagView', + 'PHUIBigInfoView' => 'AphrontTagView', 'PHUIBoxExample' => 'PhabricatorUIExample', 'PHUIBoxView' => 'AphrontTagView', 'PHUIButtonBarExample' => 'PhabricatorUIExample', @@ -5590,6 +5646,7 @@ phutil_register_library_map(array( 'PhabricatorSubscribableInterface', 'PhabricatorDestructibleInterface', 'PhabricatorSpacesInterface', + 'PhabricatorFulltextInterface', ), 'PassphraseCredentialAuthorPolicyRule' => 'PhabricatorPolicyRule', 'PassphraseCredentialConduitController' => 'PassphraseController', @@ -5597,6 +5654,7 @@ phutil_register_library_map(array( 'PassphraseCredentialCreateController' => 'PassphraseController', 'PassphraseCredentialDestroyController' => 'PassphraseController', 'PassphraseCredentialEditController' => 'PassphraseController', + 'PassphraseCredentialFulltextEngine' => 'PhabricatorFulltextEngine', 'PassphraseCredentialListController' => 'PassphraseController', 'PassphraseCredentialLockController' => 'PassphraseController', 'PassphraseCredentialPHIDType' => 'PhabricatorPHIDType', @@ -5624,7 +5682,6 @@ phutil_register_library_map(array( 'PassphraseSSHPrivateKeyFileCredentialType' => 'PassphraseSSHPrivateKeyCredentialType', 'PassphraseSSHPrivateKeyTextCredentialType' => 'PassphraseSSHPrivateKeyCredentialType', 'PassphraseSchemaSpec' => 'PhabricatorConfigSchemaSpec', - 'PassphraseSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PassphraseSecret' => 'PassphraseDAO', 'PasteConduitAPIMethod' => 'ConduitAPIMethod', 'PasteCreateConduitAPIMethod' => 'PasteConduitAPIMethod', @@ -5716,6 +5773,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionNoEffectResponse' => 'AphrontProxyResponse', 'PhabricatorApplicationTransactionPublishWorker' => 'PhabricatorWorker', 'PhabricatorApplicationTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorApplicationTransactionRemarkupPreviewController' => 'PhabricatorApplicationTransactionController', 'PhabricatorApplicationTransactionReplyHandler' => 'PhabricatorMailReplyHandler', 'PhabricatorApplicationTransactionResponse' => 'AphrontProxyResponse', 'PhabricatorApplicationTransactionShowOlderController' => 'PhabricatorApplicationTransactionController', @@ -5833,11 +5891,13 @@ phutil_register_library_map(array( 'PhabricatorAuthSSHKey' => array( 'PhabricatorAuthDAO', 'PhabricatorPolicyInterface', + 'PhabricatorDestructibleInterface', ), 'PhabricatorAuthSSHKeyController' => 'PhabricatorAuthController', 'PhabricatorAuthSSHKeyDeleteController' => 'PhabricatorAuthSSHKeyController', 'PhabricatorAuthSSHKeyEditController' => 'PhabricatorAuthSSHKeyController', 'PhabricatorAuthSSHKeyGenerateController' => 'PhabricatorAuthSSHKeyController', + 'PhabricatorAuthSSHKeyPHIDType' => 'PhabricatorPHIDType', 'PhabricatorAuthSSHKeyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthSSHKeyTableView' => 'AphrontView', 'PhabricatorAuthSSHPublicKey' => 'Phobject', @@ -5946,6 +6006,7 @@ phutil_register_library_map(array( 'PhabricatorMentionableInterface', 'PhabricatorFlaggableInterface', 'PhabricatorSpacesInterface', + 'PhabricatorFulltextInterface', ), 'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventCommentController' => 'PhabricatorCalendarController', @@ -5953,6 +6014,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorCalendarEventEmailCommand' => 'MetaMTAEmailTransactionCommand', + 'PhabricatorCalendarEventFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorCalendarEventInvitee' => array( 'PhabricatorCalendarDAO', 'PhabricatorPolicyInterface', @@ -5965,7 +6027,6 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorCalendarEventRSVPEmailCommand' => 'PhabricatorCalendarEventEmailCommand', 'PhabricatorCalendarEventSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhabricatorCalendarEventSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorCalendarEventTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorCalendarEventTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorCalendarEventTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -6160,6 +6221,7 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorCustomFieldEditField' => 'PhabricatorEditField', 'PhabricatorCustomFieldEditType' => 'PhabricatorEditType', + 'PhabricatorCustomFieldFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 'PhabricatorCustomFieldHeraldField' => 'HeraldField', 'PhabricatorCustomFieldHeraldFieldGroup' => 'HeraldFieldGroup', 'PhabricatorCustomFieldImplementationIncompleteException' => 'Exception', @@ -6290,6 +6352,8 @@ phutil_register_library_map(array( 'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDestructionEngine' => 'Phobject', + 'PhabricatorDestructionEngineExtension' => 'Phobject', + 'PhabricatorDestructionEngineExtensionModule' => 'PhabricatorConfigModule', 'PhabricatorDeveloperConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorDeveloperPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDiffInlineCommentQuery' => 'PhabricatorApplicationTransactionCommentQuery', @@ -6318,6 +6382,7 @@ phutil_register_library_map(array( 'PhabricatorEdgeTestCase' => 'PhabricatorTestCase', 'PhabricatorEdgeType' => 'Phobject', 'PhabricatorEdgeTypeTestCase' => 'PhabricatorTestCase', + 'PhabricatorEdgesDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'PhabricatorEditEngine' => array( 'Phobject', 'PhabricatorPolicyInterface', @@ -6358,7 +6423,7 @@ phutil_register_library_map(array( 'PhabricatorEditField' => 'Phobject', 'PhabricatorEditType' => 'Phobject', 'PhabricatorEditor' => 'Phobject', - 'PhabricatorElasticSearchEngine' => 'PhabricatorSearchEngine', + 'PhabricatorElasticFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine', 'PhabricatorElasticSearchSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorEmailAddressesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorEmailFormatSettingsPanel' => 'PhabricatorSettingsPanel', @@ -6514,6 +6579,7 @@ phutil_register_library_map(array( 'PhabricatorFlagController' => 'PhabricatorController', 'PhabricatorFlagDAO' => 'PhabricatorLiskDAO', 'PhabricatorFlagDeleteController' => 'PhabricatorFlagController', + 'PhabricatorFlagDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'PhabricatorFlagEditController' => 'PhabricatorFlagController', 'PhabricatorFlagListController' => 'PhabricatorFlagController', 'PhabricatorFlagQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', @@ -6522,6 +6588,11 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface' => 'PhabricatorPHIDInterface', 'PhabricatorFlagsApplication' => 'PhabricatorApplication', 'PhabricatorFlagsUIEventListener' => 'PhabricatorEventListener', + 'PhabricatorFulltextEngine' => 'Phobject', + 'PhabricatorFulltextEngineExtension' => 'Phobject', + 'PhabricatorFulltextEngineExtensionModule' => 'PhabricatorConfigModule', + 'PhabricatorFulltextIndexEngineExtension' => 'PhabricatorIndexEngineExtension', + 'PhabricatorFulltextStorageEngine' => 'Phobject', 'PhabricatorFundApplication' => 'PhabricatorApplication', 'PhabricatorGDSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorGarbageCollector' => 'Phobject', @@ -6575,6 +6646,9 @@ phutil_register_library_map(array( 'PhabricatorImageMacroRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorImageTransformer' => 'Phobject', 'PhabricatorImagemagickSetupCheck' => 'PhabricatorSetupCheck', + 'PhabricatorIndexEngine' => 'Phobject', + 'PhabricatorIndexEngineExtension' => 'Phobject', + 'PhabricatorIndexEngineExtensionModule' => 'PhabricatorConfigModule', 'PhabricatorInfrastructureTestCase' => 'PhabricatorTestCase', 'PhabricatorInlineCommentController' => 'PhabricatorController', 'PhabricatorInlineCommentInterface' => 'PhabricatorMarkupInterface', @@ -6602,6 +6676,7 @@ phutil_register_library_map(array( 'PhabricatorLipsumManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorLipsumMondrianArtist' => 'PhabricatorLipsumArtist', 'PhabricatorLiskDAO' => 'LiskDAO', + 'PhabricatorLiskFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 'PhabricatorLiskSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'PhabricatorLiskSerializer' => 'Phobject', 'PhabricatorListFilterUIExample' => 'PhabricatorUIExample', @@ -6741,7 +6816,7 @@ phutil_register_library_map(array( 'PhabricatorMustVerifyEmailController' => 'PhabricatorAuthController', 'PhabricatorMySQLConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine', - 'PhabricatorMySQLSearchEngine' => 'PhabricatorSearchEngine', + 'PhabricatorMySQLFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine', 'PhabricatorMySQLSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorNamedQuery' => array( 'PhabricatorSearchDAO', @@ -6750,11 +6825,13 @@ phutil_register_library_map(array( 'PhabricatorNamedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorNavigationRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorNeverTriggerClock' => 'PhabricatorTriggerClock', + 'PhabricatorNgramsIndexEngineExtension' => 'PhabricatorIndexEngineExtension', 'PhabricatorNotificationBuilder' => 'Phobject', 'PhabricatorNotificationClearController' => 'PhabricatorNotificationController', 'PhabricatorNotificationClient' => 'Phobject', 'PhabricatorNotificationConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorNotificationController' => 'PhabricatorController', + 'PhabricatorNotificationDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'PhabricatorNotificationIndividualController' => 'PhabricatorNotificationController', 'PhabricatorNotificationListController' => 'PhabricatorNotificationController', 'PhabricatorNotificationPanelController' => 'PhabricatorNotificationController', @@ -6854,10 +6931,14 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldInterface', 'PhabricatorDestructibleInterface', 'PhabricatorConduitResultInterface', + 'PhabricatorFulltextInterface', + 'PhabricatorNgramsInterface', ), 'PhabricatorOwnersPackageDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorOwnersPackageEditEngine' => 'PhabricatorEditEngine', + 'PhabricatorOwnersPackageFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorOwnersPackageFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', + 'PhabricatorOwnersPackageNameNgrams' => 'PhabricatorSearchNgrams', 'PhabricatorOwnersPackageOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType', 'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', @@ -7005,13 +7086,16 @@ phutil_register_library_map(array( 'PhabricatorPhurlURLEditController' => 'PhabricatorPhurlController', 'PhabricatorPhurlURLEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorPhurlURLListController' => 'PhabricatorPhurlController', + 'PhabricatorPhurlURLMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorPhurlURLPHIDType' => 'PhabricatorPHIDType', 'PhabricatorPhurlURLQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorPhurlURLReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorPhurlURLSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorPhurlURLTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorPhurlURLTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorPhurlURLTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorPhurlURLViewController' => 'PhabricatorPhurlController', + 'PhabricatorPirateEnglishTranslation' => 'PhutilTranslation', 'PhabricatorPlatformSite' => 'PhabricatorSite', 'PhabricatorPolicies' => 'PhabricatorPolicyConstants', 'PhabricatorPolicy' => array( @@ -7060,9 +7144,11 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorFlaggableInterface', 'PhabricatorPolicyInterface', + 'PhabricatorExtendedPolicyInterface', 'PhabricatorSubscribableInterface', 'PhabricatorCustomFieldInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorFulltextInterface', ), 'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction', 'PhabricatorProjectApplication' => 'PhabricatorApplication', @@ -7096,6 +7182,7 @@ phutil_register_library_map(array( 'PhabricatorStandardCustomFieldInterface', ), 'PhabricatorProjectController' => 'PhabricatorController', + 'PhabricatorProjectCoreTestCase' => 'PhabricatorTestCase', 'PhabricatorProjectCustomField' => 'PhabricatorCustomField', 'PhabricatorProjectCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', 'PhabricatorProjectCustomFieldStorage' => 'PhabricatorCustomFieldStorage', @@ -7105,8 +7192,8 @@ phutil_register_library_map(array( 'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField', 'PhabricatorProjectEditDetailsController' => 'PhabricatorProjectController', 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', - 'PhabricatorProjectEditorTestCase' => 'PhabricatorTestCase', 'PhabricatorProjectFeedController' => 'PhabricatorProjectController', + 'PhabricatorProjectFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorProjectHeraldAction' => 'HeraldAction', 'PhabricatorProjectIconSet' => 'PhabricatorIconSet', 'PhabricatorProjectListController' => 'PhabricatorProjectController', @@ -7115,11 +7202,14 @@ phutil_register_library_map(array( 'PhabricatorProjectLogicalOrNotDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalViewerDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorProjectMaterializedMemberEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectMemberOfProjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectMembersDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController', + 'PhabricatorProjectMembersPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController', 'PhabricatorProjectMoveController' => 'PhabricatorProjectController', + 'PhabricatorProjectNameContextFreeGrammar' => 'PhutilContextFreeGrammar', 'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorProjectObjectHasProjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', @@ -7134,7 +7224,6 @@ phutil_register_library_map(array( 'PhabricatorProjectSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorProjectSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorProjectSearchField' => 'PhabricatorSearchTokenizerField', - 'PhabricatorProjectSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorProjectSlug' => 'PhabricatorProjectDAO', 'PhabricatorProjectStandardCustomField' => array( 'PhabricatorProjectCustomField', @@ -7152,6 +7241,8 @@ phutil_register_library_map(array( 'PhabricatorProjectWatchController' => 'PhabricatorProjectController', 'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField', + 'PhabricatorProjectsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', + 'PhabricatorProjectsMembershipIndexEngineExtension' => 'PhabricatorIndexEngineExtension', 'PhabricatorProjectsPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorProjectsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'PhabricatorProjectsSearchEngineExtension' => 'PhabricatorSearchEngineExtension', @@ -7207,6 +7298,7 @@ phutil_register_library_map(array( 'HarbormasterBuildableInterface', 'PhabricatorCustomFieldInterface', 'PhabricatorApplicationTransactionInterface', + 'PhabricatorFulltextInterface', ), 'PhabricatorRepositoryCommitChangeParserWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitData' => 'PhabricatorRepositoryDAO', @@ -7216,7 +7308,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryCommitPHIDType' => 'PhabricatorPHIDType', 'PhabricatorRepositoryCommitParserWorker' => 'PhabricatorWorker', 'PhabricatorRepositoryCommitRef' => 'Phobject', - 'PhabricatorRepositoryCommitSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorRepositoryConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorRepositoryDiscoveryEngine' => 'PhabricatorRepositoryEngine', @@ -7342,12 +7433,10 @@ phutil_register_library_map(array( 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentFieldType' => 'Phobject', - 'PhabricatorSearchDocumentIndexer' => 'Phobject', 'PhabricatorSearchDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentTypeDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorSearchEditController' => 'PhabricatorSearchBaseController', - 'PhabricatorSearchEngine' => 'Phobject', 'PhabricatorSearchEngineAPIMethod' => 'ConduitAPIMethod', 'PhabricatorSearchEngineAttachment' => 'Phobject', 'PhabricatorSearchEngineExtension' => 'Phobject', @@ -7355,10 +7444,13 @@ phutil_register_library_map(array( 'PhabricatorSearchEngineTestCase' => 'PhabricatorTestCase', 'PhabricatorSearchField' => 'Phobject', 'PhabricatorSearchHovercardController' => 'PhabricatorSearchBaseController', - 'PhabricatorSearchIndexer' => 'Phobject', + 'PhabricatorSearchIndexVersion' => 'PhabricatorSearchDAO', + 'PhabricatorSearchIndexVersionDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'PhabricatorSearchManagementIndexWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementInitWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementWorkflow' => 'PhabricatorManagementWorkflow', + 'PhabricatorSearchNgrams' => 'PhabricatorSearchDAO', + 'PhabricatorSearchNgramsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'PhabricatorSearchOrderController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchOrderField' => 'PhabricatorSearchField', 'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', @@ -7508,6 +7600,7 @@ phutil_register_library_map(array( 'PhabricatorSubscriptionsEditController' => 'PhabricatorController', 'PhabricatorSubscriptionsEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorSubscriptionsEditor' => 'PhabricatorEditor', + 'PhabricatorSubscriptionsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 'PhabricatorSubscriptionsHeraldAction' => 'HeraldAction', 'PhabricatorSubscriptionsListController' => 'PhabricatorController', 'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', @@ -7560,6 +7653,7 @@ phutil_register_library_map(array( 'PhabricatorTokenCount' => 'PhabricatorTokenDAO', 'PhabricatorTokenCountQuery' => 'PhabricatorOffsetPagedQuery', 'PhabricatorTokenDAO' => 'PhabricatorLiskDAO', + 'PhabricatorTokenDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'PhabricatorTokenGiveController' => 'PhabricatorTokenController', 'PhabricatorTokenGiven' => array( 'PhabricatorTokenDAO', @@ -7580,6 +7674,8 @@ phutil_register_library_map(array( 'PhabricatorTooltipUIExample' => 'PhabricatorUIExample', 'PhabricatorTransactions' => 'Phobject', 'PhabricatorTransactionsApplication' => 'PhabricatorApplication', + 'PhabricatorTransactionsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', + 'PhabricatorTransactionsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 'PhabricatorTransformedFile' => 'PhabricatorFileDAO', 'PhabricatorTranslationsConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorTriggerAction' => 'Phobject', @@ -7616,6 +7712,7 @@ phutil_register_library_map(array( 'PhabricatorSSHPublicKeyInterface', 'PhabricatorFlaggableInterface', 'PhabricatorApplicationTransactionInterface', + 'PhabricatorFulltextInterface', ), 'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField', 'PhabricatorUserConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -7632,6 +7729,7 @@ phutil_register_library_map(array( 'PhabricatorUserEditorTestCase' => 'PhabricatorTestCase', 'PhabricatorUserEmail' => 'PhabricatorUserDAO', 'PhabricatorUserEmailTestCase' => 'PhabricatorTestCase', + 'PhabricatorUserFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorUserLog' => array( 'PhabricatorUserDAO', 'PhabricatorPolicyInterface', @@ -7644,7 +7742,6 @@ phutil_register_library_map(array( 'PhabricatorUserRealNameField' => 'PhabricatorUserCustomField', 'PhabricatorUserRolesField' => 'PhabricatorUserCustomField', 'PhabricatorUserSchemaSpec' => 'PhabricatorConfigSchemaSpec', - 'PhabricatorUserSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorUserSinceField' => 'PhabricatorUserCustomField', 'PhabricatorUserStatusField' => 'PhabricatorUserCustomField', 'PhabricatorUserTestCase' => 'PhabricatorTestCase', @@ -7683,6 +7780,7 @@ phutil_register_library_map(array( 'PhabricatorWorkerBulkJobWorker' => 'PhabricatorWorker', 'PhabricatorWorkerBulkTask' => 'PhabricatorWorkerDAO', 'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO', + 'PhabricatorWorkerDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'PhabricatorWorkerLeaseQuery' => 'PhabricatorQuery', 'PhabricatorWorkerManagementCancelWorkflow' => 'PhabricatorWorkerManagementWorkflow', 'PhabricatorWorkerManagementExecuteWorkflow' => 'PhabricatorWorkerManagementWorkflow', @@ -7842,6 +7940,7 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', 'PhabricatorSpacesInterface', 'PhabricatorMentionableInterface', + 'PhabricatorFulltextInterface', ), 'PholioMockArchiveController' => 'PholioController', 'PholioMockAuthorHeraldField' => 'PholioMockHeraldField', @@ -7850,6 +7949,7 @@ phutil_register_library_map(array( 'PholioMockEditController' => 'PholioController', 'PholioMockEditor' => 'PhabricatorApplicationTransactionEditor', 'PholioMockEmbedView' => 'AphrontView', + 'PholioMockFulltextEngine' => 'PhabricatorFulltextEngine', 'PholioMockHasTaskEdgeType' => 'PhabricatorEdgeType', 'PholioMockHeraldField' => 'HeraldField', 'PholioMockHeraldFieldGroup' => 'HeraldFieldGroup', @@ -7865,7 +7965,6 @@ phutil_register_library_map(array( 'PholioRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PholioReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PholioSchemaSpec' => 'PhabricatorConfigSchemaSpec', - 'PholioSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PholioTransaction' => 'PhabricatorApplicationTransaction', 'PholioTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PholioTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -8088,10 +8187,12 @@ phutil_register_library_map(array( 'PhabricatorTokenReceiverInterface', 'PhabricatorDestructibleInterface', 'PhabricatorApplicationTransactionInterface', + 'PhabricatorFulltextInterface', ), 'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentController' => 'PhrictionController', + 'PhrictionDocumentFulltextEngine' => 'PhabricatorFulltextEngine', 'PhrictionDocumentHeraldAdapter' => 'HeraldAdapter', 'PhrictionDocumentHeraldField' => 'HeraldField', 'PhrictionDocumentHeraldFieldGroup' => 'HeraldFieldGroup', @@ -8112,7 +8213,6 @@ phutil_register_library_map(array( 'PhrictionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhrictionSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhrictionSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhrictionSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhrictionTransaction' => 'PhabricatorApplicationTransaction', 'PhrictionTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhrictionTransactionEditor' => 'PhabricatorApplicationTransactionEditor', @@ -8163,10 +8263,12 @@ phutil_register_library_map(array( 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', 'PhabricatorSpacesInterface', + 'PhabricatorFulltextInterface', ), 'PonderQuestionCommentController' => 'PonderController', 'PonderQuestionEditController' => 'PonderController', 'PonderQuestionEditor' => 'PonderEditor', + 'PonderQuestionFulltextEngine' => 'PhabricatorFulltextEngine', 'PonderQuestionHistoryController' => 'PonderController', 'PonderQuestionListController' => 'PonderController', 'PonderQuestionMailReceiver' => 'PhabricatorObjectMailReceiver', @@ -8182,7 +8284,6 @@ phutil_register_library_map(array( 'PonderQuestionViewController' => 'PonderController', 'PonderRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PonderSchemaSpec' => 'PhabricatorConfigSchemaSpec', - 'PonderSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PonderVote' => 'PonderConstants', 'PonderVoteEditor' => 'PhabricatorEditor', 'PonderVotingUserHasAnswerEdgeType' => 'PhabricatorEdgeType', diff --git a/src/applications/almanac/engineextension/AlmanacPropertiesDestructionEngineExtension.php b/src/applications/almanac/engineextension/AlmanacPropertiesDestructionEngineExtension.php new file mode 100644 index 0000000000..48209cf4ca --- /dev/null +++ b/src/applications/almanac/engineextension/AlmanacPropertiesDestructionEngineExtension.php @@ -0,0 +1,32 @@ +establishConnection('w'); + + queryfx( + $conn_w, + 'DELETE FROM %T WHERE objectPHID = %s', + $table->getTableName(), + $object->getPHID()); + } + +} diff --git a/src/applications/audit/query/PhabricatorCommitSearchEngine.php b/src/applications/audit/query/PhabricatorCommitSearchEngine.php index af0194bc79..b0c5ec1d2c 100644 --- a/src/applications/audit/query/PhabricatorCommitSearchEngine.php +++ b/src/applications/audit/query/PhabricatorCommitSearchEngine.php @@ -178,4 +178,16 @@ final class PhabricatorCommitSearchEngine return $result; } + protected function getNewUserBody() { + + $view = id(new PHUIBigInfoView()) + ->setIcon('fa-check-circle-o') + ->setTitle(pht('Welcome to Audit')) + ->setDescription( + pht('Post-commit code review and auditing. Audits you are assigned '. + 'to will appear here.')); + + return $view; + } + } diff --git a/src/applications/auth/conduit/PhabricatorAuthQueryPublicKeysConduitAPIMethod.php b/src/applications/auth/conduit/PhabricatorAuthQueryPublicKeysConduitAPIMethod.php index e7d036aee4..be91af7863 100644 --- a/src/applications/auth/conduit/PhabricatorAuthQueryPublicKeysConduitAPIMethod.php +++ b/src/applications/auth/conduit/PhabricatorAuthQueryPublicKeysConduitAPIMethod.php @@ -14,6 +14,7 @@ final class PhabricatorAuthQueryPublicKeysConduitAPIMethod protected function defineParamTypes() { return array( 'ids' => 'optional list', + 'phids' => 'optional list', 'objectPHIDs' => 'optional list', 'keys' => 'optional list', ) + self::getPagerParamTypes(); @@ -34,6 +35,11 @@ final class PhabricatorAuthQueryPublicKeysConduitAPIMethod $query->withIDs($ids); } + $phids = $request->getValue('phids'); + if ($phids !== null) { + $query->withPHIDs($phids); + } + $object_phids = $request->getValue('objectPHIDs'); if ($object_phids !== null) { $query->withObjectPHIDs($object_phids); @@ -57,6 +63,7 @@ final class PhabricatorAuthQueryPublicKeysConduitAPIMethod $data[] = array( 'id' => $public_key->getID(), 'name' => $public_key->getName(), + 'phid' => $public_key->getPHID(), 'objectPHID' => $public_key->getObjectPHID(), 'isTrusted' => (bool)$public_key->getIsTrusted(), 'key' => $public_key->getEntireKey(), diff --git a/src/applications/auth/phid/PhabricatorAuthSSHKeyPHIDType.php b/src/applications/auth/phid/PhabricatorAuthSSHKeyPHIDType.php new file mode 100644 index 0000000000..10ab2fdfb6 --- /dev/null +++ b/src/applications/auth/phid/PhabricatorAuthSSHKeyPHIDType.php @@ -0,0 +1,38 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + foreach ($handles as $phid => $handle) { + $key = $objects[$phid]; + $handle->setName(pht('SSH Key %d', $key->getID())); + } + } + +} diff --git a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php index 4a14456f35..f68969d0e8 100644 --- a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php +++ b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php @@ -4,6 +4,7 @@ final class PhabricatorAuthSSHKeyQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $ids; + private $phids; private $objectPHIDs; private $keys; @@ -12,6 +13,11 @@ final class PhabricatorAuthSSHKeyQuery return $this; } + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + public function withObjectPHIDs(array $object_phids) { $this->objectPHIDs = $object_phids; return $this; @@ -23,19 +29,12 @@ final class PhabricatorAuthSSHKeyQuery return $this; } + public function newResultObject() { + return new PhabricatorAuthSSHKey(); + } + protected function loadPage() { - $table = new PhabricatorAuthSSHKey(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $keys) { @@ -54,6 +53,7 @@ final class PhabricatorAuthSSHKeyQuery // We must have an object, and that object must be a valid object for // SSH keys. if (!$object || !($object instanceof PhabricatorSSHPublicKeyInterface)) { + $this->didRejectResult($ssh_key); unset($keys[$key]); continue; } @@ -64,19 +64,26 @@ final class PhabricatorAuthSSHKeyQuery return $keys; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'phid IN (%Ls)', + $this->phids); + } + if ($this->objectPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'objectPHID IN (%Ls)', $this->objectPHIDs); } @@ -85,7 +92,7 @@ final class PhabricatorAuthSSHKeyQuery $sql = array(); foreach ($this->keys as $key) { $sql[] = qsprintf( - $conn_r, + $conn, '(keyType = %s AND keyIndex = %s)', $key->getType(), $key->getHash()); @@ -93,9 +100,8 @@ final class PhabricatorAuthSSHKeyQuery $where[] = implode(' OR ', $sql); } - $where[] = $this->buildPagingClause($conn_r); + return $where; - return $this->formatWhereClause($where); } public function getQueryApplicationClass() { diff --git a/src/applications/auth/storage/PhabricatorAuthSSHKey.php b/src/applications/auth/storage/PhabricatorAuthSSHKey.php index c87c9b50b6..3e77c1bca1 100644 --- a/src/applications/auth/storage/PhabricatorAuthSSHKey.php +++ b/src/applications/auth/storage/PhabricatorAuthSSHKey.php @@ -2,7 +2,9 @@ final class PhabricatorAuthSSHKey extends PhabricatorAuthDAO - implements PhabricatorPolicyInterface { + implements + PhabricatorPolicyInterface, + PhabricatorDestructibleInterface { protected $objectPHID; protected $name; @@ -16,6 +18,7 @@ final class PhabricatorAuthSSHKey protected function getConfiguration() { return array( + self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text255', 'keyType' => 'text255', @@ -63,8 +66,10 @@ final class PhabricatorAuthSSHKey return $this; } - - + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorAuthSSHKeyPHIDType::TYPECONST); + } /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -89,4 +94,15 @@ final class PhabricatorAuthSSHKey 'SSH keys inherit the policies of the user or object they authenticate.'); } +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + + $this->openTransaction(); + $this->delete(); + $this->saveTransaction(); + } + } diff --git a/src/applications/badges/editor/PhabricatorBadgesEditor.php b/src/applications/badges/editor/PhabricatorBadgesEditor.php index ad4ccca3d2..fd0b14ad46 100644 --- a/src/applications/badges/editor/PhabricatorBadgesEditor.php +++ b/src/applications/badges/editor/PhabricatorBadgesEditor.php @@ -194,7 +194,7 @@ final class PhabricatorBadgesEditor $body = parent::buildMailBody($object, $xactions); if (strlen($description)) { - $body->addRemarkupSeciton( + $body->addRemarkupSection( pht('BADGE DESCRIPTION'), $object->getDescription()); } diff --git a/src/applications/badges/query/PhabricatorBadgesSearchEngine.php b/src/applications/badges/query/PhabricatorBadgesSearchEngine.php index f352c6b5df..ab8432833e 100644 --- a/src/applications/badges/query/PhabricatorBadgesSearchEngine.php +++ b/src/applications/badges/query/PhabricatorBadgesSearchEngine.php @@ -140,4 +140,24 @@ final class PhabricatorBadgesSearchEngine } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Badge')) + ->setHref('/badges/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Badges let you award and distinguish special users '. + 'throughout your instance.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php b/src/applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php new file mode 100644 index 0000000000..8447a6a30d --- /dev/null +++ b/src/applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php @@ -0,0 +1,39 @@ +setDocumentTitle($event->getName()); + + $document->addField( + PhabricatorSearchDocumentFieldType::FIELD_BODY, + $event->getDescription()); + + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, + $event->getUserPHID(), + PhabricatorPeopleUserPHIDType::TYPECONST, + $event->getDateCreated()); + + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_OWNER, + $event->getUserPHID(), + PhabricatorPeopleUserPHIDType::TYPECONST, + $event->getDateCreated()); + + $document->addRelationship( + $event->getIsCancelled() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $event->getPHID(), + PhabricatorCalendarEventPHIDType::TYPECONST, + PhabricatorTime::getNow()); + } + +} diff --git a/src/applications/calendar/search/PhabricatorCalendarEventSearchIndexer.php b/src/applications/calendar/search/PhabricatorCalendarEventSearchIndexer.php deleted file mode 100644 index 95bd85267c..0000000000 --- a/src/applications/calendar/search/PhabricatorCalendarEventSearchIndexer.php +++ /dev/null @@ -1,52 +0,0 @@ -loadDocumentByPHID($phid); - - $doc = new PhabricatorSearchAbstractDocument(); - $doc->setPHID($event->getPHID()); - $doc->setDocumentType(PhabricatorCalendarEventPHIDType::TYPECONST); - $doc->setDocumentTitle($event->getName()); - $doc->setDocumentCreated($event->getDateCreated()); - $doc->setDocumentModified($event->getDateModified()); - - $doc->addField( - PhabricatorSearchDocumentFieldType::FIELD_BODY, - $event->getDescription()); - - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, - $event->getUserPHID(), - PhabricatorPeopleUserPHIDType::TYPECONST, - $event->getDateCreated()); - - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_OWNER, - $event->getUserPHID(), - PhabricatorPeopleUserPHIDType::TYPECONST, - $event->getDateCreated()); - - $doc->addRelationship( - $event->getIsCancelled() - ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED - : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, - $event->getPHID(), - PhabricatorCalendarEventPHIDType::TYPECONST, - time()); - - $this->indexTransactions( - $doc, - new PhabricatorCalendarEventTransactionQuery(), - array($phid)); - - return $doc; - } - -} diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 6f0ff98b3c..1b4c1a760c 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -10,7 +10,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO PhabricatorDestructibleInterface, PhabricatorMentionableInterface, PhabricatorFlaggableInterface, - PhabricatorSpacesInterface { + PhabricatorSpacesInterface, + PhabricatorFulltextInterface { protected $name; protected $userPHID; @@ -562,4 +563,13 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO public function getSpacePHID() { return $this->spacePHID; } + + +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + + public function newFulltextEngine() { + return new PhabricatorCalendarEventFulltextEngine(); + } + } diff --git a/src/applications/config/check/PhabricatorElasticSearchSetupCheck.php b/src/applications/config/check/PhabricatorElasticSearchSetupCheck.php index 47a3cb4b18..f137f2527f 100644 --- a/src/applications/config/check/PhabricatorElasticSearchSetupCheck.php +++ b/src/applications/config/check/PhabricatorElasticSearchSetupCheck.php @@ -11,7 +11,7 @@ final class PhabricatorElasticSearchSetupCheck extends PhabricatorSetupCheck { return; } - $engine = new PhabricatorElasticSearchEngine(); + $engine = new PhabricatorElasticFulltextStorageEngine(); $index_exists = null; $index_sane = null; @@ -70,8 +70,8 @@ final class PhabricatorElasticSearchSetupCheck extends PhabricatorSetupCheck { } protected function shouldUseElasticSearchEngine() { - $search_engine = PhabricatorSearchEngine::loadEngine(); - return ($search_engine instanceof PhabricatorElasticSearchEngine); + $search_engine = PhabricatorFulltextStorageEngine::loadEngine(); + return ($search_engine instanceof PhabricatorElasticFulltextStorageEngine); } } diff --git a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php index de087096fb..a37e655853 100644 --- a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php +++ b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php @@ -84,6 +84,8 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck { $issue->addPhabricatorConfig($key); } } + + $this->executeManiphestFieldChecks(); } /** @@ -300,4 +302,70 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck { return $ancient_config; } + + private function executeManiphestFieldChecks() { + $maniphest_appclass = 'PhabricatorManiphestApplication'; + if (!PhabricatorApplication::isClassInstalled($maniphest_appclass)) { + return; + } + + $capabilities = array( + ManiphestEditAssignCapability::CAPABILITY, + ManiphestEditPoliciesCapability::CAPABILITY, + ManiphestEditPriorityCapability::CAPABILITY, + ManiphestEditProjectsCapability::CAPABILITY, + ManiphestEditStatusCapability::CAPABILITY, + ); + + // Check for any of these capabilities set to anything other than + // "All Users". + + $any_set = false; + $app = new PhabricatorManiphestApplication(); + foreach ($capabilities as $capability) { + $setting = $app->getPolicy($capability); + if ($setting != PhabricatorPolicies::POLICY_USER) { + $any_set = true; + break; + } + } + + if (!$any_set) { + return; + } + + $issue_summary = pht( + 'Maniphest is currently configured with deprecated policy settings '. + 'which will be removed in a future version of Phabricator.'); + + + $message = pht( + 'Some policy settings in Maniphest are now deprecated and will be '. + 'removed in a future version of Phabricator. You are currently using '. + 'at least one of these settings.'. + "\n\n". + 'The deprecated settings are "Can Assign Tasks", '. + '"Can Edit Task Policies", "Can Prioritize Tasks", '. + '"Can Edit Task Projects", and "Can Edit Task Status". You can '. + 'find these settings in Applications, or follow the link below.'. + "\n\n". + 'You can find discussion of this change (including rationale and '. + 'recommendations on how to configure similar features) in the upstream, '. + 'at the link below.'. + "\n\n". + 'To resolve this issue, set all of these policies to "All Users" after '. + 'making any necessary form customization changes.'); + + $more_href = 'https://secure.phabricator.com/T10003'; + $edit_href = '/applications/view/PhabricatorManiphestApplication/'; + + $issue = $this->newIssue('maniphest.T10003-per-field-policies') + ->setShortName(pht('Deprecated Policies')) + ->setName(pht('Deprecated Maniphest Field Policies')) + ->setSummary($issue_summary) + ->setMessage($message) + ->addLink($more_href, pht('Learn More: Upstream Discussion')) + ->addLink($edit_href, pht('Edit These Settings')); + } + } diff --git a/src/applications/config/check/PhabricatorMySQLSetupCheck.php b/src/applications/config/check/PhabricatorMySQLSetupCheck.php index cc792358ad..bcda6280f5 100644 --- a/src/applications/config/check/PhabricatorMySQLSetupCheck.php +++ b/src/applications/config/check/PhabricatorMySQLSetupCheck.php @@ -366,8 +366,8 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck { } protected function shouldUseMySQLSearchEngine() { - $search_engine = PhabricatorSearchEngine::loadEngine(); - return $search_engine instanceof PhabricatorMySQLSearchEngine; + $search_engine = PhabricatorFulltextStorageEngine::loadEngine(); + return ($search_engine instanceof PhabricatorMySQLFulltextStorageEngine); } } diff --git a/src/applications/config/option/PhabricatorPHDConfigOptions.php b/src/applications/config/option/PhabricatorPHDConfigOptions.php index 12c29da616..6fecd7117f 100644 --- a/src/applications/config/option/PhabricatorPHDConfigOptions.php +++ b/src/applications/config/option/PhabricatorPHDConfigOptions.php @@ -22,12 +22,15 @@ final class PhabricatorPHDConfigOptions public function getOptions() { return array( $this->newOption('phd.pid-directory', 'string', '/var/tmp/phd/pid') + ->setLocked(true) ->setDescription( pht('Directory that phd should use to track running daemons.')), $this->newOption('phd.log-directory', 'string', '/var/tmp/phd/log') + ->setLocked(true) ->setDescription( pht('Directory that the daemons should use to store log files.')), $this->newOption('phd.taskmasters', 'int', 4) + ->setLocked(true) ->setSummary(pht('Maximum taskmaster daemon pool size.')) ->setDescription( pht( @@ -35,6 +38,7 @@ final class PhabricatorPHDConfigOptions 'this can increase the maximum throughput of the task queue. The '. 'pool will automatically scale down when unutilized.')), $this->newOption('phd.verbose', 'bool', false) + ->setLocked(true) ->setBoolOptions( array( pht('Verbose mode'), @@ -59,6 +63,7 @@ final class PhabricatorPHDConfigOptions 'Phabricator imports or manages. This option is new and '. 'experimental.')), $this->newOption('phd.trace', 'bool', false) + ->setLocked(true) ->setBoolOptions( array( pht('Trace mode'), diff --git a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php index bfb9fdd582..5b48fcbd03 100644 --- a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php @@ -201,7 +201,8 @@ abstract class PhabricatorConfigSchemaSpec extends Phobject { $is_binary = ($this->getUTF8Charset() == 'binary'); $matches = null; - if (preg_match('/^(fulltext|sort|text)(\d+)?\z/', $data_type, $matches)) { + $pattern = '/^(fulltext|sort|text|char)(\d+)?\z/'; + if (preg_match($pattern, $data_type, $matches)) { // Limit the permitted column lengths under the theory that it would // be nice to eventually reduce this to a small set of standard lengths. @@ -220,6 +221,7 @@ abstract class PhabricatorConfigSchemaSpec extends Phobject { 'text8' => true, 'text4' => true, 'text' => true, + 'char3' => true, 'sort255' => true, 'sort128' => true, 'sort64' => true, @@ -266,10 +268,14 @@ abstract class PhabricatorConfigSchemaSpec extends Phobject { // the majority of cases. $column_type = 'longtext'; break; + case 'char': + $column_type = 'char('.$size.')'; + break; } switch ($type) { case 'text': + case 'char': if ($is_binary) { // We leave collation and character set unspecified in order to // generate valid SQL. @@ -315,6 +321,7 @@ abstract class PhabricatorConfigSchemaSpec extends Phobject { break; case 'phid': case 'policy'; + case 'hashpath64': $column_type = 'varbinary(64)'; break; case 'bytes64': diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 7d3aa02d2f..9f2e3e2b2a 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -601,22 +601,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return true; } - protected function getSearchContextParameter( - PhabricatorLiskDAO $object, - array $xactions) { - - $comment_phids = array(); - foreach ($xactions as $xaction) { - if ($xaction->hasComment()) { - $comment_phids[] = $xaction->getPHID(); - } - } - - return array( - 'commentPHIDs' => $comment_phids, - ); - } - protected function extractFilePHIDsFromCustomTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { diff --git a/src/applications/conpherence/search/ConpherenceThreadIndexer.php b/src/applications/conpherence/engineextension/ConpherenceThreadIndexEngineExtension.php similarity index 53% rename from src/applications/conpherence/search/ConpherenceThreadIndexer.php rename to src/applications/conpherence/engineextension/ConpherenceThreadIndexEngineExtension.php index 2d4f303fdc..d45e347729 100644 --- a/src/applications/conpherence/search/ConpherenceThreadIndexer.php +++ b/src/applications/conpherence/engineextension/ConpherenceThreadIndexEngineExtension.php @@ -1,58 +1,50 @@ setViewer($this->getViewer()) - ->withPHIDs(array($phid)) - ->executeOne(); - - if (!$object) { - throw new Exception(pht('No thread "%s" exists!', $phid)); - } - - return $object; + public function shouldIndexObject($object) { + return ($object instanceof ConpherenceThread); } - protected function buildAbstractDocumentByPHID($phid) { - $thread = $this->loadDocumentByPHID($phid); + public function indexObject( + PhabricatorIndexEngine $engine, + $object) { - // NOTE: We're explicitly not building a document here, only rebuilding - // the Conpherence search index. + $force = $this->shouldForceFullReindex(); - $context = nonempty($this->getContext(), array()); - $comment_phids = idx($context, 'commentPHIDs'); - - if (is_array($comment_phids) && !$comment_phids) { - // If this property is set, but empty, the transaction did not - // include any chat text. For example, a user might have left the - // conversation. - return null; + if (!$force) { + $xaction_phids = $this->getParameter('transactionPHIDs'); + if (!$xaction_phids) { + return; + } } $query = id(new ConpherenceTransactionQuery()) ->setViewer($this->getViewer()) - ->withObjectPHIDs(array($thread->getPHID())) + ->withObjectPHIDs(array($object->getPHID())) ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT)) ->needComments(true); - if ($comment_phids !== null) { - $query->withPHIDs($comment_phids); + if (!$force) { + $query->withPHIDs($xaction_phids); } $xactions = $query->execute(); - foreach ($xactions as $xaction) { - $this->indexComment($thread, $xaction); + if (!$xactions) { + return; } - return null; + foreach ($xactions as $xaction) { + $this->indexComment($object, $xaction); + } } private function indexComment( diff --git a/src/applications/countdown/application/PhabricatorCountdownApplication.php b/src/applications/countdown/application/PhabricatorCountdownApplication.php index 14ef34d2cb..5daaa5021f 100644 --- a/src/applications/countdown/application/PhabricatorCountdownApplication.php +++ b/src/applications/countdown/application/PhabricatorCountdownApplication.php @@ -48,6 +48,8 @@ final class PhabricatorCountdownApplication extends PhabricatorApplication { => 'PhabricatorCountdownCommentController', 'edit/(?:(?P[1-9]\d*)/)?' => 'PhabricatorCountdownEditController', + 'create/' + => 'PhabricatorCountdownEditController', 'delete/(?P[1-9]\d*)/' => 'PhabricatorCountdownDeleteController', ), diff --git a/src/applications/countdown/controller/PhabricatorCountdownController.php b/src/applications/countdown/controller/PhabricatorCountdownController.php index e22ce95a47..37b0e49a68 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownController.php @@ -13,7 +13,7 @@ abstract class PhabricatorCountdownController extends PhabricatorController { $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Countdown')) - ->setHref($this->getApplicationURI('edit/')) + ->setHref($this->getApplicationURI('create/')) ->setIcon('fa-plus-square')); return $crumbs; diff --git a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php index b9f438ece8..ae90e5f3e7 100644 --- a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php +++ b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php @@ -145,4 +145,24 @@ final class PhabricatorCountdownSearchEngine return $result; } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Countdown')) + ->setHref('/countdown/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Keep track of upcoming launch dates with '. + 'embeddable counters.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php index e612c26d2a..4136cad358 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelListController.php @@ -48,4 +48,23 @@ final class PhabricatorDashboardPanelListController return $crumbs; } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Panel')) + ->setHref('/dashboard/panel/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Build individual panels to display on your homepage dashboard.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php b/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php index e9e5b29772..83557b141a 100644 --- a/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php +++ b/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php @@ -174,4 +174,24 @@ final class PhabricatorDashboardSearchEngine return $result; } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Dashboard')) + ->setHref('/dashboard/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Customize your homepage with different panels and '. + 'search queries.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/differential/customfield/DifferentialBranchField.php b/src/applications/differential/customfield/DifferentialBranchField.php index 62f47a5e1a..16be0ce0c9 100644 --- a/src/applications/differential/customfield/DifferentialBranchField.php +++ b/src/applications/differential/customfield/DifferentialBranchField.php @@ -45,7 +45,7 @@ final class DifferentialBranchField return pht('%s (bookmark)', $bookmark); } else if (strlen($branch)) { $onto = $diff->loadTargetBranch(); - if (strlen($onto)) { + if (strlen($onto) && ($onto !== $branch)) { return pht( '%s (branched from %s)', $branch, diff --git a/src/applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php b/src/applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php index ec346f26ae..737db1aef8 100644 --- a/src/applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php +++ b/src/applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php @@ -3,7 +3,11 @@ final class PhabricatorDifferentialRevisionTestDataGenerator extends PhabricatorTestDataGenerator { - public function generate() { + public function getGeneratorName() { + return pht('Differential Revisions'); + } + + public function generateObject() { $author = $this->loadPhabrictorUser(); $revision = DifferentialRevision::initializeNewRevision($author); @@ -23,13 +27,10 @@ final class PhabricatorDifferentialRevisionTestDataGenerator ->setTransactionType(DifferentialTransaction::TYPE_UPDATE) ->setNewValue($diff->getPHID()); - $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LIPSUM, - array()); id(new DifferentialTransactionEditor()) ->setActor($author) - ->setContentSource($content_source) + ->setContentSource($this->getLipsumContentSource()) ->applyTransactions($revision, $xactions); return $revision; diff --git a/src/applications/differential/query/DifferentialRevisionSearchEngine.php b/src/applications/differential/query/DifferentialRevisionSearchEngine.php index b696db63b2..5d3f5bee55 100644 --- a/src/applications/differential/query/DifferentialRevisionSearchEngine.php +++ b/src/applications/differential/query/DifferentialRevisionSearchEngine.php @@ -338,4 +338,24 @@ final class DifferentialRevisionSearchEngine return $result; } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Diff')) + ->setHref('/differential/diff/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Pre-commit code review. Revisions that are waiting on your input '. + 'will appear here.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/differential/search/DifferentialRevisionFulltextEngine.php b/src/applications/differential/search/DifferentialRevisionFulltextEngine.php new file mode 100644 index 0000000000..60d267fe71 --- /dev/null +++ b/src/applications/differential/search/DifferentialRevisionFulltextEngine.php @@ -0,0 +1,64 @@ +setViewer($this->getViewer()) + ->withPHIDs(array($object->getPHID())) + ->needReviewerStatus(true) + ->executeOne(); + + // TODO: This isn't very clean, but custom fields currently rely on it. + $object->attachReviewerStatus($revision->getReviewerStatus()); + + $document->setDocumentTitle($revision->getTitle()); + + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, + $revision->getAuthorPHID(), + PhabricatorPeopleUserPHIDType::TYPECONST, + $revision->getDateCreated()); + + $document->addRelationship( + $revision->isClosed() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $revision->getPHID(), + DifferentialRevisionPHIDType::TYPECONST, + PhabricatorTime::getNow()); + + // If a revision needs review, the owners are the reviewers. Otherwise, the + // owner is the author (e.g., accepted, rejected, closed). + $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; + if ($revision->getStatus() == $status_review) { + $reviewers = $revision->getReviewerStatus(); + $reviewers = mpull($reviewers, 'getReviewerPHID', 'getReviewerPHID'); + if ($reviewers) { + foreach ($reviewers as $phid) { + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_OWNER, + $phid, + PhabricatorPeopleUserPHIDType::TYPECONST, + $revision->getDateModified()); // Bogus timestamp. + } + } else { + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED, + $revision->getPHID(), + PhabricatorPeopleUserPHIDType::TYPECONST, + $revision->getDateModified()); // Bogus timestamp. + } + } else { + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_OWNER, + $revision->getAuthorPHID(), + PhabricatorPHIDConstants::PHID_TYPE_VOID, + $revision->getDateCreated()); + } + } +} diff --git a/src/applications/differential/search/DifferentialSearchIndexer.php b/src/applications/differential/search/DifferentialSearchIndexer.php deleted file mode 100644 index 9976503127..0000000000 --- a/src/applications/differential/search/DifferentialSearchIndexer.php +++ /dev/null @@ -1,81 +0,0 @@ -setViewer($this->getViewer()) - ->withPHIDs(array($phid)) - ->needReviewerStatus(true) - ->executeOne(); - if (!$object) { - throw new Exception(pht("Unable to load object by PHID '%s'!", $phid)); - } - return $object; - } - - protected function buildAbstractDocumentByPHID($phid) { - $rev = $this->loadDocumentByPHID($phid); - - $doc = new PhabricatorSearchAbstractDocument(); - $doc->setPHID($rev->getPHID()); - $doc->setDocumentType(DifferentialRevisionPHIDType::TYPECONST); - $doc->setDocumentTitle($rev->getTitle()); - $doc->setDocumentCreated($rev->getDateCreated()); - $doc->setDocumentModified($rev->getDateModified()); - - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, - $rev->getAuthorPHID(), - PhabricatorPeopleUserPHIDType::TYPECONST, - $rev->getDateCreated()); - - $doc->addRelationship( - $rev->isClosed() - ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED - : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, - $rev->getPHID(), - DifferentialRevisionPHIDType::TYPECONST, - time()); - - $this->indexTransactions( - $doc, - new DifferentialTransactionQuery(), - array($rev->getPHID())); - - // If a revision needs review, the owners are the reviewers. Otherwise, the - // owner is the author (e.g., accepted, rejected, closed). - if ($rev->getStatus() == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) { - $reviewers = $rev->getReviewerStatus(); - $reviewers = mpull($reviewers, 'getReviewerPHID', 'getReviewerPHID'); - if ($reviewers) { - foreach ($reviewers as $phid) { - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_OWNER, - $phid, - PhabricatorPeopleUserPHIDType::TYPECONST, - $rev->getDateModified()); // Bogus timestamp. - } - } else { - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED, - $rev->getPHID(), - PhabricatorPeopleUserPHIDType::TYPECONST, - $rev->getDateModified()); // Bogus timestamp. - } - } else { - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_OWNER, - $rev->getAuthorPHID(), - PhabricatorPHIDConstants::PHID_TYPE_VOID, - $rev->getDateCreated()); - } - - return $doc; - } -} diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 84b77f0c48..e534ad9ca7 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -13,7 +13,8 @@ final class DifferentialRevision extends DifferentialDAO PhabricatorApplicationTransactionInterface, PhabricatorMentionableInterface, PhabricatorDestructibleInterface, - PhabricatorProjectInterface { + PhabricatorProjectInterface, + PhabricatorFulltextInterface { protected $title = ''; protected $originalTitle; @@ -629,4 +630,13 @@ final class DifferentialRevision extends DifferentialDAO $this->saveTransaction(); } + +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + + public function newFulltextEngine() { + return new DifferentialRevisionFulltextEngine(); + } + + } diff --git a/src/applications/diviner/publisher/DivinerLivePublisher.php b/src/applications/diviner/publisher/DivinerLivePublisher.php index f2ad34fd5e..80f3bd7a1e 100644 --- a/src/applications/diviner/publisher/DivinerLivePublisher.php +++ b/src/applications/diviner/publisher/DivinerLivePublisher.php @@ -40,8 +40,7 @@ final class DivinerLivePublisher extends DivinerPublisher { $conn_w->saveTransaction(); $this->book = $book; - id(new PhabricatorSearchIndexer()) - ->queueDocumentForIndexing($book->getPHID()); + PhabricatorSearchWorker::queueDocumentForIndexing($book->getPHID()); } return $this->book; @@ -147,8 +146,7 @@ final class DivinerLivePublisher extends DivinerPublisher { $symbol->save(); - id(new PhabricatorSearchIndexer()) - ->queueDocumentForIndexing($symbol->getPHID()); + PhabricatorSearchWorker::queueDocumentForIndexing($symbol->getPHID()); // TODO: We probably need a finer-grained sense of what "documentable" // atoms are. Neither files nor methods are currently considered diff --git a/src/applications/diviner/search/DivinerBookSearchIndexer.php b/src/applications/diviner/search/DivinerBookSearchIndexer.php deleted file mode 100644 index 3cc819e275..0000000000 --- a/src/applications/diviner/search/DivinerBookSearchIndexer.php +++ /dev/null @@ -1,36 +0,0 @@ -loadDocumentByPHID($phid); - - $doc = $this->newDocument($phid) - ->setDocumentTitle($book->getTitle()) - ->setDocumentCreated($book->getDateCreated()) - ->setDocumentModified($book->getDateModified()); - - $doc->addField( - PhabricatorSearchDocumentFieldType::FIELD_BODY, - $book->getPreface()); - - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY, - $book->getRepositoryPHID(), - PhabricatorRepositoryRepositoryPHIDType::TYPECONST, - $book->getDateCreated()); - - $this->indexTransactions( - $doc, - new DivinerLiveBookTransactionQuery(), - array($phid)); - - return $doc; - } - - -} diff --git a/src/applications/diviner/search/DivinerLiveBookFulltextEngine.php b/src/applications/diviner/search/DivinerLiveBookFulltextEngine.php new file mode 100644 index 0000000000..35ff454d53 --- /dev/null +++ b/src/applications/diviner/search/DivinerLiveBookFulltextEngine.php @@ -0,0 +1,26 @@ +setDocumentTitle($book->getTitle()); + + $document->addField( + PhabricatorSearchDocumentFieldType::FIELD_BODY, + $book->getPreface()); + + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY, + $book->getRepositoryPHID(), + PhabricatorRepositoryRepositoryPHIDType::TYPECONST, + $book->getDateCreated()); + } + + +} diff --git a/src/applications/diviner/search/DivinerAtomSearchIndexer.php b/src/applications/diviner/search/DivinerLiveSymbolFulltextEngine.php similarity index 64% rename from src/applications/diviner/search/DivinerAtomSearchIndexer.php rename to src/applications/diviner/search/DivinerLiveSymbolFulltextEngine.php index 0719f807f6..694af0a27b 100644 --- a/src/applications/diviner/search/DivinerAtomSearchIndexer.php +++ b/src/applications/diviner/search/DivinerLiveSymbolFulltextEngine.php @@ -1,49 +1,43 @@ loadDocumentByPHID($phid); + $atom = $object; $book = $atom->getBook(); - if (!$atom->getIsDocumentable()) { - return null; - } - - $doc = $this->newDocument($phid) + $document ->setDocumentTitle($atom->getTitle()) ->setDocumentCreated($book->getDateCreated()) ->setDocumentModified($book->getDateModified()); - $doc->addField( + $document->addField( PhabricatorSearchDocumentFieldType::FIELD_BODY, $atom->getSummary()); - $doc->addRelationship( + $document->addRelationship( PhabricatorSearchRelationship::RELATIONSHIP_BOOK, $atom->getBookPHID(), DivinerBookPHIDType::TYPECONST, PhabricatorTime::getNow()); - $doc->addRelationship( + $document->addRelationship( PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY, $atom->getRepositoryPHID(), PhabricatorRepositoryRepositoryPHIDType::TYPECONST, PhabricatorTime::getNow()); - $doc->addRelationship( + $document->addRelationship( $atom->getGraphHash() ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, $atom->getBookPHID(), DivinerBookPHIDType::TYPECONST, PhabricatorTime::getNow()); - - return $doc; } } diff --git a/src/applications/diviner/storage/DivinerLiveBook.php b/src/applications/diviner/storage/DivinerLiveBook.php index 872dcb6eeb..079c5a9f4c 100644 --- a/src/applications/diviner/storage/DivinerLiveBook.php +++ b/src/applications/diviner/storage/DivinerLiveBook.php @@ -5,7 +5,8 @@ final class DivinerLiveBook extends DivinerDAO PhabricatorPolicyInterface, PhabricatorProjectInterface, PhabricatorDestructibleInterface, - PhabricatorApplicationTransactionInterface { + PhabricatorApplicationTransactionInterface, + PhabricatorFulltextInterface { protected $name; protected $repositoryPHID; @@ -161,4 +162,12 @@ final class DivinerLiveBook extends DivinerDAO return $timeline; } +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + + public function newFulltextEngine() { + return new DivinerLiveBookFulltextEngine(); + } + + } diff --git a/src/applications/diviner/storage/DivinerLiveSymbol.php b/src/applications/diviner/storage/DivinerLiveSymbol.php index 75ea3bcd7b..38b99208a7 100644 --- a/src/applications/diviner/storage/DivinerLiveSymbol.php +++ b/src/applications/diviner/storage/DivinerLiveSymbol.php @@ -4,7 +4,8 @@ final class DivinerLiveSymbol extends DivinerDAO implements PhabricatorPolicyInterface, PhabricatorMarkupInterface, - PhabricatorDestructibleInterface { + PhabricatorDestructibleInterface, + PhabricatorFulltextInterface { protected $bookPHID; protected $repositoryPHID; @@ -280,4 +281,16 @@ final class DivinerLiveSymbol extends DivinerDAO $this->saveTransaction(); } + +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + + public function newFulltextEngine() { + if (!$this->getIsDocumentable()) { + return null; + } + + return new DivinerLiveSymbolFulltextEngine(); + } + } diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index f39353a8f4..d60a65580b 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -249,11 +249,15 @@ final class DrydockWorkingCopyBlueprintImplementation $ref = idx($spec, 'ref'); + // Reset things first, in case previous builds left anything staged or + // dirty. + $cmd[] = 'git reset --hard HEAD'; + if ($commit !== null) { - $cmd[] = 'git reset --hard %s'; + $cmd[] = 'git checkout %s --'; $arg[] = $commit; } else if ($branch !== null) { - $cmd[] = 'git checkout %s'; + $cmd[] = 'git checkout %s --'; $arg[] = $branch; $cmd[] = 'git reset --hard origin/%s'; @@ -267,13 +271,8 @@ final class DrydockWorkingCopyBlueprintImplementation $arg[] = $ref_ref; $arg[] = $ref_ref; - $cmd[] = 'git checkout %s'; + $cmd[] = 'git checkout %s --'; $arg[] = $ref_ref; - - $cmd[] = 'git reset --hard %s'; - $arg[] = $ref_ref; - } else { - $cmd[] = 'git reset --hard HEAD'; } $cmd = implode(' && ', $cmd); diff --git a/src/applications/drydock/logtype/DrydockLeaseReclaimLogType.php b/src/applications/drydock/logtype/DrydockLeaseReclaimLogType.php new file mode 100644 index 0000000000..8dbc13d9d4 --- /dev/null +++ b/src/applications/drydock/logtype/DrydockLeaseReclaimLogType.php @@ -0,0 +1,25 @@ +getViewer(); + + $resource_phids = idx($data, 'resourcePHIDs', array()); + + return pht( + 'Reclaimed resource %s.', + $viewer->renderHandleList($resource_phids)->render()); + } + +} diff --git a/src/applications/drydock/logtype/DrydockResourceReclaimLogType.php b/src/applications/drydock/logtype/DrydockResourceReclaimLogType.php new file mode 100644 index 0000000000..9e9d5fdef3 --- /dev/null +++ b/src/applications/drydock/logtype/DrydockResourceReclaimLogType.php @@ -0,0 +1,24 @@ +getViewer(); + $reclaimer_phid = idx($data, 'reclaimerPHID'); + + return pht( + 'Resource reclaimed by %s.', + $viewer->renderHandle($reclaimer_phid)->render()); + } + +} diff --git a/src/applications/drydock/management/DrydockManagementReclaimWorkflow.php b/src/applications/drydock/management/DrydockManagementReclaimWorkflow.php new file mode 100644 index 0000000000..a312f109f2 --- /dev/null +++ b/src/applications/drydock/management/DrydockManagementReclaimWorkflow.php @@ -0,0 +1,63 @@ +setName('reclaim') + ->setSynopsis(pht('Reclaim unused resources.')) + ->setArguments(array()); + } + + public function execute(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); + + PhabricatorWorker::setRunAllTasksInProcess(true); + + $resources = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withStatuses( + array( + DrydockResourceStatus::STATUS_ACTIVE, + )) + ->execute(); + foreach ($resources as $resource) { + $command = DrydockCommand::initializeNewCommand($viewer) + ->setTargetPHID($resource->getPHID()) + ->setAuthorPHID($drydock_phid) + ->setCommand(DrydockCommand::COMMAND_RECLAIM) + ->save(); + + $resource->scheduleUpdate(); + + $resource = $resource->reload(); + + $name = pht( + 'Resource %d: %s', + $resource->getID(), + $resource->getResourceName()); + + switch ($resource->getStatus()) { + case DrydockResourceStatus::STATUS_RELEASED: + case DrydockResourceStatus::STATUS_DESTROYED: + echo tsprintf( + "%s\n", + pht( + 'Resource "%s" was reclaimed.', + $name)); + break; + default: + echo tsprintf( + "%s\n", + pht( + 'Resource "%s" could not be reclaimed.', + $name)); + break; + } + } + + } + +} diff --git a/src/applications/drydock/storage/DrydockCommand.php b/src/applications/drydock/storage/DrydockCommand.php index 60cb363ecb..e7d003bdd6 100644 --- a/src/applications/drydock/storage/DrydockCommand.php +++ b/src/applications/drydock/storage/DrydockCommand.php @@ -5,6 +5,7 @@ final class DrydockCommand implements PhabricatorPolicyInterface { const COMMAND_RELEASE = 'release'; + const COMMAND_RECLAIM = 'reclaim'; protected $authorPHID; protected $targetPHID; diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php index 8f34a88342..a41e57fbf8 100644 --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -26,6 +26,7 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { $this->handleUpdate($lease); } catch (Exception $ex) { $lock->unlock(); + $this->flushDrydockTaskQueue(); throw $ex; } @@ -178,6 +179,23 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { // satisfy the lease, just not right now. This is a temporary failure, // and we expect allocation to succeed eventually. if (!$usable_blueprints) { + $blueprints = $this->rankBlueprints($blueprints, $lease); + + // Try to actively reclaim unused resources. If we succeed, jump back + // into the queue in an effort to claim it. + foreach ($blueprints as $blueprint) { + $reclaimed = $this->reclaimResources($blueprint, $lease); + if ($reclaimed) { + $lease->logEvent( + DrydockLeaseReclaimLogType::LOGCONST, + array( + 'resourcePHIDs' => array($reclaimed->getPHID()), + )); + + throw new PhabricatorWorkerYieldException(15); + } + } + $lease->logEvent( DrydockLeaseWaitingForResourcesLogType::LOGCONST, array( @@ -438,6 +456,7 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { assert_instances_of($blueprints, 'DrydockBlueprint'); $keep = array(); + foreach ($blueprints as $key => $blueprint) { if (!$blueprint->canAllocateResourceForLease($lease)) { continue; @@ -572,6 +591,35 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { } } + private function reclaimResources( + DrydockBlueprint $blueprint, + DrydockLease $lease) { + $viewer = $this->getViewer(); + + $resources = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withBlueprintPHIDs(array($blueprint->getPHID())) + ->withStatuses( + array( + DrydockResourceStatus::STATUS_ACTIVE, + )) + ->execute(); + + // TODO: We could be much smarter about this and try to release long-unused + // resources, resources with many similar copies, old resources, resources + // that are cheap to rebuild, etc. + shuffle($resources); + + foreach ($resources as $resource) { + if ($this->canReclaimResource($resource)) { + $this->reclaimResource($resource, $lease); + return $resource; + } + } + + return null; + } + /* -( Acquiring Leases )--------------------------------------------------- */ diff --git a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php index e0deabdb1b..baa80f90d8 100644 --- a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php @@ -24,6 +24,7 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { $this->handleUpdate($resource); } catch (Exception $ex) { $lock->unlock(); + $this->flushDrydockTaskQueue(); throw $ex; } @@ -142,7 +143,11 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { switch ($command->getCommand()) { case DrydockCommand::COMMAND_RELEASE: - $this->releaseResource($resource); + $this->releaseResource($resource, null); + break; + case DrydockCommand::COMMAND_RECLAIM: + $reclaimer_phid = $command->getAuthorPHID(); + $this->releaseResource($resource, $reclaimer_phid); break; } } @@ -187,7 +192,22 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { /** * @task release */ - private function releaseResource(DrydockResource $resource) { + private function releaseResource( + DrydockResource $resource, + $reclaimer_phid) { + + if ($reclaimer_phid) { + if (!$this->canReclaimResource($resource)) { + return; + } + + $resource->logEvent( + DrydockResourceReclaimLogType::LOGCONST, + array( + 'reclaimerPHID' => $reclaimer_phid, + )); + } + $viewer = $this->getViewer(); $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); diff --git a/src/applications/drydock/worker/DrydockWorker.php b/src/applications/drydock/worker/DrydockWorker.php index 029cbf1c84..f167fef5d0 100644 --- a/src/applications/drydock/worker/DrydockWorker.php +++ b/src/applications/drydock/worker/DrydockWorker.php @@ -170,4 +170,86 @@ abstract class DrydockWorker extends PhabricatorWorker { return 15; } + protected function flushDrydockTaskQueue() { + // NOTE: By default, queued tasks are not scheduled if the current task + // fails. This is a good, safe default behavior. For example, it can + // protect us from executing side effect tasks too many times, like + // sending extra email. + + // However, it is not the behavior we want in Drydock, because we queue + // followup tasks after lease and resource failures and want them to + // execute in order to clean things up. + + // At least for now, we just explicitly flush the queue before exiting + // with a failure to make sure tasks get queued up properly. + try { + $this->flushTaskQueue(); + } catch (Exception $ex) { + // If this fails, we want to swallow the exception so the caller throws + // the original error, since we're more likely to be able to understand + // and fix the problem if we have the original error than if we replace + // it with this one. + phlog($ex); + } + + return $this; + } + + protected function canReclaimResource(DrydockResource $resource) { + $viewer = $this->getViewer(); + + // Don't reclaim a resource if it has been updated recently. If two + // leases are fighting, we don't want them to keep reclaming resources + // from one another forever without making progress, so make resources + // immune to reclamation for a little while after they activate or update. + + // TODO: It would be nice to use a more narrow time here, like "last + // activation or lease release", but we don't currently store that + // anywhere. + + $updated = $resource->getDateModified(); + $now = PhabricatorTime::getNow(); + $ago = ($now - $updated); + if ($ago < phutil_units('3 minutes in seconds')) { + return false; + } + + $statuses = array( + DrydockLeaseStatus::STATUS_PENDING, + DrydockLeaseStatus::STATUS_ACQUIRED, + DrydockLeaseStatus::STATUS_ACTIVE, + DrydockLeaseStatus::STATUS_RELEASED, + DrydockLeaseStatus::STATUS_BROKEN, + ); + + // Don't reclaim resources that have any active leases. + $leases = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withResourcePHIDs(array($resource->getPHID())) + ->withStatuses($statuses) + ->setLimit(1) + ->execute(); + if ($leases) { + return false; + } + + return true; + } + + protected function reclaimResource( + DrydockResource $resource, + DrydockLease $lease) { + $viewer = $this->getViewer(); + + $command = DrydockCommand::initializeNewCommand($viewer) + ->setTargetPHID($resource->getPHID()) + ->setAuthorPHID($lease->getPHID()) + ->setCommand(DrydockCommand::COMMAND_RECLAIM) + ->save(); + + $resource->scheduleUpdate(); + + return $this; + } + } diff --git a/src/applications/files/lipsum/PhabricatorFileTestDataGenerator.php b/src/applications/files/lipsum/PhabricatorFileTestDataGenerator.php index cc5e6fe195..cf084d32a7 100644 --- a/src/applications/files/lipsum/PhabricatorFileTestDataGenerator.php +++ b/src/applications/files/lipsum/PhabricatorFileTestDataGenerator.php @@ -3,7 +3,11 @@ final class PhabricatorFileTestDataGenerator extends PhabricatorTestDataGenerator { - public function generate() { + public function getGeneratorName() { + return pht('Files'); + } + + public function generateObject() { $author_phid = $this->loadPhabrictorUserPHID(); $dimension = 1 << rand(5, 12); $image = id(new PhabricatorLipsumMondrianArtist()) diff --git a/src/applications/files/query/PhabricatorFileSearchEngine.php b/src/applications/files/query/PhabricatorFileSearchEngine.php index 70dfaba23f..31e03f02b9 100644 --- a/src/applications/files/query/PhabricatorFileSearchEngine.php +++ b/src/applications/files/query/PhabricatorFileSearchEngine.php @@ -178,4 +178,23 @@ final class PhabricatorFileSearchEngine return $result; } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Upload a File')) + ->setHref('/file/upload/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Just a place for files.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/flag/engineextension/PhabricatorFlagDestructionEngineExtension.php b/src/applications/flag/engineextension/PhabricatorFlagDestructionEngineExtension.php new file mode 100644 index 0000000000..20bc19d4aa --- /dev/null +++ b/src/applications/flag/engineextension/PhabricatorFlagDestructionEngineExtension.php @@ -0,0 +1,35 @@ +getPHID(); + + if ($object instanceof PhabricatorFlaggableInterface) { + $flags = id(new PhabricatorFlag())->loadAllWhere( + 'objectPHID = %s', + $object_phid); + foreach ($flags as $flag) { + $flag->delete(); + } + } + + $flags = id(new PhabricatorFlag())->loadAllWhere( + 'ownerPHID = %s', + $object_phid); + foreach ($flags as $flag) { + $flag->delete(); + } + } + +} diff --git a/src/applications/fund/search/FundInitiativeFulltextEngine.php b/src/applications/fund/search/FundInitiativeFulltextEngine.php new file mode 100644 index 0000000000..177d76bd4d --- /dev/null +++ b/src/applications/fund/search/FundInitiativeFulltextEngine.php @@ -0,0 +1,34 @@ +setDocumentTitle($initiative->getName()); + + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, + $initiative->getOwnerPHID(), + PhabricatorPeopleUserPHIDType::TYPECONST, + $initiative->getDateCreated()); + + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_OWNER, + $initiative->getOwnerPHID(), + PhabricatorPeopleUserPHIDType::TYPECONST, + $initiative->getDateCreated()); + + $document->addRelationship( + $initiative->isClosed() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $initiative->getPHID(), + FundInitiativePHIDType::TYPECONST, + PhabricatorTime::getNow()); + } +} diff --git a/src/applications/fund/search/FundInitiativeIndexer.php b/src/applications/fund/search/FundInitiativeIndexer.php deleted file mode 100644 index 7816af04bd..0000000000 --- a/src/applications/fund/search/FundInitiativeIndexer.php +++ /dev/null @@ -1,61 +0,0 @@ -setViewer($this->getViewer()) - ->withPHIDs(array($phid)) - ->executeOne(); - if (!$object) { - throw new Exception( - pht( - "Unable to load object by PHID '%s'!", - $phid)); - } - return $object; - } - - protected function buildAbstractDocumentByPHID($phid) { - $initiative = $this->loadDocumentByPHID($phid); - - $doc = id(new PhabricatorSearchAbstractDocument()) - ->setPHID($initiative->getPHID()) - ->setDocumentType(FundInitiativePHIDType::TYPECONST) - ->setDocumentTitle($initiative->getName()) - ->setDocumentCreated($initiative->getDateCreated()) - ->setDocumentModified($initiative->getDateModified()); - - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, - $initiative->getOwnerPHID(), - PhabricatorPeopleUserPHIDType::TYPECONST, - $initiative->getDateCreated()); - - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_OWNER, - $initiative->getOwnerPHID(), - PhabricatorPeopleUserPHIDType::TYPECONST, - $initiative->getDateCreated()); - - $doc->addRelationship( - $initiative->isClosed() - ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED - : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, - $initiative->getPHID(), - FundInitiativePHIDType::TYPECONST, - time()); - - $this->indexTransactions( - $doc, - new FundInitiativeTransactionQuery(), - array($initiative->getPHID())); - - return $doc; - } -} diff --git a/src/applications/fund/storage/FundInitiative.php b/src/applications/fund/storage/FundInitiative.php index 6acc19f5fc..57e86beccf 100644 --- a/src/applications/fund/storage/FundInitiative.php +++ b/src/applications/fund/storage/FundInitiative.php @@ -9,7 +9,8 @@ final class FundInitiative extends FundDAO PhabricatorMentionableInterface, PhabricatorFlaggableInterface, PhabricatorTokenReceiverInterface, - PhabricatorDestructibleInterface { + PhabricatorDestructibleInterface, + PhabricatorFulltextInterface { protected $name; protected $ownerPHID; @@ -207,4 +208,12 @@ final class FundInitiative extends FundDAO $this->saveTransaction(); } + +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + + public function newFulltextEngine() { + return new FundInitiativeFulltextEngine(); + } + } diff --git a/src/applications/herald/application/PhabricatorHeraldApplication.php b/src/applications/herald/application/PhabricatorHeraldApplication.php index bef94b6ac4..d6f0fd47b6 100644 --- a/src/applications/herald/application/PhabricatorHeraldApplication.php +++ b/src/applications/herald/application/PhabricatorHeraldApplication.php @@ -51,6 +51,7 @@ final class PhabricatorHeraldApplication extends PhabricatorApplication { '/herald/' => array( '(?:query/(?P[^/]+)/)?' => 'HeraldRuleListController', 'new/' => 'HeraldNewController', + 'create/' => 'HeraldNewController', 'edit/(?:(?P[1-9]\d*)/)?' => 'HeraldRuleController', 'disable/(?P[1-9]\d*)/(?P\w+)/' => 'HeraldDisableController', diff --git a/src/applications/herald/controller/HeraldController.php b/src/applications/herald/controller/HeraldController.php index 2151c62aa9..355d3b90f9 100644 --- a/src/applications/herald/controller/HeraldController.php +++ b/src/applications/herald/controller/HeraldController.php @@ -12,7 +12,7 @@ abstract class HeraldController extends PhabricatorController { $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Herald Rule')) - ->setHref($this->getApplicationURI('new/')) + ->setHref($this->getApplicationURI('create/')) ->setIcon('fa-plus-square')); return $crumbs; diff --git a/src/applications/herald/engineextension/HeraldTranscriptDestructionEngineExtension.php b/src/applications/herald/engineextension/HeraldTranscriptDestructionEngineExtension.php new file mode 100644 index 0000000000..e03391537d --- /dev/null +++ b/src/applications/herald/engineextension/HeraldTranscriptDestructionEngineExtension.php @@ -0,0 +1,26 @@ +getPHID(); + + $transcripts = id(new HeraldTranscript())->loadAllWhere( + 'objectPHID = %s', + $object_phid); + foreach ($transcripts as $transcript) { + $engine->destroyObject($transcript); + } + } + +} diff --git a/src/applications/herald/query/HeraldRuleSearchEngine.php b/src/applications/herald/query/HeraldRuleSearchEngine.php index ad4c9a7ad2..b71f8e6278 100644 --- a/src/applications/herald/query/HeraldRuleSearchEngine.php +++ b/src/applications/herald/query/HeraldRuleSearchEngine.php @@ -211,4 +211,24 @@ final class HeraldRuleSearchEngine extends PhabricatorApplicationSearchEngine { } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create Herald Rule')) + ->setHref('/herald/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('A flexible rules engine that can notify and act on '. + 'other actions such as tasks, diffs, and commits.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php index 8beb7fa05c..2d94b23193 100644 --- a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php +++ b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php @@ -216,4 +216,24 @@ final class LegalpadDocumentSearchEngine return $result; } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Document')) + ->setHref('/legalpad/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Create documents and track signatures. Can also be re-used in '. + 'other areas of Phabricator, like CLAs.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/lipsum/generator/PhabricatorTestDataGenerator.php b/src/applications/lipsum/generator/PhabricatorTestDataGenerator.php index 9bff25a068..4e8a9b5985 100644 --- a/src/applications/lipsum/generator/PhabricatorTestDataGenerator.php +++ b/src/applications/lipsum/generator/PhabricatorTestDataGenerator.php @@ -2,10 +2,81 @@ abstract class PhabricatorTestDataGenerator extends Phobject { - public function generate() { - return; + private $viewer; + + abstract public function getGeneratorName(); + abstract public function generateObject(); + + final public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; } + final public function getViewer() { + return $this->viewer; + } + + protected function loadRandomPHID($table) { + $conn_r = $table->establishConnection('r'); + + $row = queryfx_one( + $conn_r, + 'SELECT phid FROM %T ORDER BY RAND() LIMIT 1', + $table->getTableName()); + + if (!$row) { + return null; + } + + return $row['phid']; + } + + protected function loadRandomUser() { + $viewer = $this->getViewer(); + + $user_phid = $this->loadRandomPHID(new PhabricatorUser()); + + $user = null; + if ($user_phid) { + $user = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($user_phid)) + ->executeOne(); + } + + if (!$user) { + throw new Exception( + pht( + 'Failed to load a random user. You may need to generate more '. + 'test users first.')); + } + + return $user; + } + + protected function getLipsumContentSource() { + return PhabricatorContentSource::newForSource( + PhabricatorContentSource::SOURCE_LIPSUM, + array()); + } + + /** + * Roll `n` dice with `d` sides each, then add `bonus` and return the sum. + */ + protected function roll($n, $d, $bonus = 0) { + $sum = 0; + for ($ii = 0; $ii < $n; $ii++) { + $sum += mt_rand(1, $d); + } + + $sum += $bonus; + + return $sum; + } + + + + public function loadOneRandom($classname) { try { return newv($classname, array()) @@ -27,4 +98,5 @@ abstract class PhabricatorTestDataGenerator extends Phobject { return $this->loadOneRandom('PhabricatorUser'); } + } diff --git a/src/applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php b/src/applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php index ca0b63e263..866e8b1cb0 100644 --- a/src/applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php +++ b/src/applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php @@ -7,7 +7,7 @@ final class PhabricatorLipsumGenerateWorkflow $this ->setName('generate') ->setExamples('**generate**') - ->setSynopsis(pht('Generate some lipsum.')) + ->setSynopsis(pht('Generate synthetic test objects.')) ->setArguments( array( array( @@ -18,77 +18,144 @@ final class PhabricatorLipsumGenerateWorkflow } public function execute(PhutilArgumentParser $args) { - $console = PhutilConsole::getConsole(); + $config_key = 'phabricator.developer-mode'; + if (!PhabricatorEnv::getEnvConfig($config_key)) { + throw new PhutilArgumentUsageException( + pht( + 'lipsum is a development and testing tool and may only be run '. + 'on installs in developer mode. Enable "%s" in your configuration '. + 'to enable lipsum.', + $config_key)); + } - $supported_types = id(new PhutilClassMapQuery()) + $all_generators = id(new PhutilClassMapQuery()) ->setAncestorClass('PhabricatorTestDataGenerator') ->execute(); - $console->writeOut( - "%s:\n\t%s\n", - pht('These are the types of data you can generate'), - implode("\n\t", array_keys($supported_types))); + $argv = $args->getArg('args'); + $all = 'all'; - $prompt = pht('Are you sure you want to generate lots of test data?'); + if (!$argv) { + $names = mpull($all_generators, 'getGeneratorName'); + sort($names); + + $list = id(new PhutilConsoleList()) + ->setWrap(false) + ->addItems($names); + + id(new PhutilConsoleBlock()) + ->addParagraph( + pht( + 'Choose which type or types of test data you want to generate, '. + 'or select "%s".', + $all)) + ->addList($list) + ->draw(); + + return 0; + } + + $generators = array(); + foreach ($argv as $arg_original) { + $arg = phutil_utf8_strtolower($arg_original); + + $match = false; + foreach ($all_generators as $generator) { + $name = phutil_utf8_strtolower($generator->getGeneratorName()); + + if ($arg == $all) { + $generators[] = $generator; + $match = true; + break; + } + + if (strpos($name, $arg) !== false) { + $generators[] = $generator; + $match = true; + break; + } + } + + if (!$match) { + throw new PhutilArgumentUsageException( + pht( + 'Argument "%s" does not match the name of any generators.', + $arg_original)); + } + } + + echo tsprintf( + "** %s ** %s\n", + pht('GENERATORS'), + pht( + 'Selected generators: %s.', + implode(', ', mpull($generators, 'getGeneratorName')))); + + echo tsprintf( + "** %s ** %s\n", + pht('WARNING'), + pht( + 'This command generates synthetic test data, including user '. + 'accounts. It is intended for use in development environments '. + 'so you can test features more easily. There is no easy way to '. + 'delete this data or undo the effects of this command. If you run '. + 'it in a production environment, it will pollute your data with '. + 'large amounts of meaningless garbage that you can not get rid of.')); + + $prompt = pht('Are you sure you want to generate piles of garbage?'); if (!phutil_console_confirm($prompt, true)) { return; } - $argv = $args->getArg('args'); - if (count($argv) == 0 || (count($argv) == 1 && $argv[0] == 'all')) { - $this->infinitelyGenerate($supported_types); - } else { - $new_supported_types = array(); - for ($i = 0; $i < count($argv); $i++) { - $arg = $argv[$i]; - if (array_key_exists($arg, $supported_types)) { - $new_supported_types[$arg] = $supported_types[$arg]; - } else { - $console->writeErr( - "%s\n", - pht( - 'The type %s is not supported by the lipsum generator.', - $arg)); - } - } - $this->infinitelyGenerate($new_supported_types); - } + echo tsprintf( + "** %s ** %s\n", + pht('LIPSUM'), + pht( + 'Generating synthetic test objects forever. '. + 'Use ^C to stop when satisfied.')); - $console->writeOut( - "%s\n%s:\n%s\n", - pht('None of the input types were supported.'), - pht('The supported types are'), - implode("\n", array_keys($supported_types))); + $this->generate($generators); } - protected function infinitelyGenerate(array $supported_types) { - $console = PhutilConsole::getConsole(); + protected function generate(array $generators) { + $viewer = $this->getViewer(); - if (count($supported_types) == 0) { - return; + foreach ($generators as $generator) { + $generator->setViewer($this->getViewer()); } - $console->writeOut( - "%s: %s\n", - pht('GENERATING'), - implode(', ', array_keys($supported_types))); while (true) { - $type = $supported_types[array_rand($supported_types)]; - $admin = $this->getViewer(); + $generator = $generators[array_rand($generators)]; - $taskgen = newv($type, array()); - $object = $taskgen->generate(); - $handle = id(new PhabricatorHandleQuery()) - ->setViewer($admin) - ->withPHIDs(array($object->getPHID())) - ->executeOne(); + try { + $object = $generator->generateObject(); + } catch (Exception $ex) { + echo tsprintf( + "** %s ** %s\n", + pht('OOPS'), + pht( + 'Generator ("%s") was unable to generate an object.', + $generator->getGeneratorName())); - $console->writeOut( - "%s: %s\n", - pht('Generated %s', $handle->getTypeName()), - $handle->getFullName()); + echo tsprintf( + "%B\n", + $ex->getMessage()); - usleep(200000); + continue; + } + + $object_phid = $object->getPHID(); + + $handles = $viewer->loadHandles(array($object_phid)); + + echo tsprintf( + "%s\n", + pht( + 'Generated "%s": %s', + $handles[$object_phid]->getTypeName(), + $handles[$object_phid]->getFullName())); + + sleep(1); } } diff --git a/src/applications/macro/query/PhabricatorMacroSearchEngine.php b/src/applications/macro/query/PhabricatorMacroSearchEngine.php index 039f396d5f..1619f8185a 100644 --- a/src/applications/macro/query/PhabricatorMacroSearchEngine.php +++ b/src/applications/macro/query/PhabricatorMacroSearchEngine.php @@ -189,4 +189,23 @@ final class PhabricatorMacroSearchEngine return $result; } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Macro')) + ->setHref('/macro/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Create easy to remember shortcuts to images and memes.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/maniphest/application/PhabricatorManiphestApplication.php b/src/applications/maniphest/application/PhabricatorManiphestApplication.php index 3a47501c9b..7f2a05d537 100644 --- a/src/applications/maniphest/application/PhabricatorManiphestApplication.php +++ b/src/applications/maniphest/application/PhabricatorManiphestApplication.php @@ -38,7 +38,6 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication { public function getEventListeners() { return array( - new ManiphestNameIndexEventListener(), new ManiphestHovercardEventListener(), ); } diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index d30f01a34e..aac21ea7ac 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -66,10 +66,8 @@ final class ManiphestEditEngine $priority_map = $this->getTaskPriorityMap($object); if ($object->isClosed()) { - $priority_label = null; $default_status = ManiphestTaskStatus::getDefaultStatus(); } else { - $priority_label = pht('Change Priority'); $default_status = ManiphestTaskStatus::getDefaultClosedStatus(); } @@ -150,7 +148,7 @@ final class ManiphestEditEngine ->setIsCopyable(true) ->setValue($object->getPriority()) ->setOptions($priority_map) - ->setCommentActionLabel($priority_label), + ->setCommentActionLabel(pht('Change Priority')), id(new PhabricatorRemarkupEditField()) ->setKey('description') ->setLabel(pht('Description')) diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index 037286244a..42170d4c53 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -371,20 +371,22 @@ final class ManiphestTransactionEditor $new = $unblock_xaction->getNewValue(); foreach ($blocked_tasks as $blocked_task) { - $unblock_xactions = array(); - - $unblock_xactions[] = id(new ManiphestTransaction()) + $parent_xaction = id(new ManiphestTransaction()) ->setTransactionType(ManiphestTransaction::TYPE_UNBLOCK) ->setOldValue(array($object->getPHID() => $old)) ->setNewValue(array($object->getPHID() => $new)); + if ($this->getIsNewObject()) { + $parent_xaction->setMetadataValue('blocker.new', true); + } + id(new ManiphestTransactionEditor()) ->setActor($this->getActor()) ->setActingAsPHID($this->getActingAsPHID()) ->setContentSource($this->getContentSource()) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) - ->applyTransactions($blocked_task, $unblock_xactions); + ->applyTransactions($blocked_task, array($parent_xaction)); } } } diff --git a/src/applications/maniphest/engineextension/ManiphestProjectNameFulltextEngineExtension.php b/src/applications/maniphest/engineextension/ManiphestProjectNameFulltextEngineExtension.php new file mode 100644 index 0000000000..32ee3fcab2 --- /dev/null +++ b/src/applications/maniphest/engineextension/ManiphestProjectNameFulltextEngineExtension.php @@ -0,0 +1,25 @@ +getPHID(), + $object->getName()); + } + +} diff --git a/src/applications/maniphest/event/ManiphestNameIndexEventListener.php b/src/applications/maniphest/event/ManiphestNameIndexEventListener.php deleted file mode 100644 index 15cb072f07..0000000000 --- a/src/applications/maniphest/event/ManiphestNameIndexEventListener.php +++ /dev/null @@ -1,25 +0,0 @@ -listen(PhabricatorEventType::TYPE_SEARCH_DIDUPDATEINDEX); - } - - public function handleEvent(PhutilEvent $event) { - $phid = $event->getValue('phid'); - $type = phid_get_type($phid); - - // For now, we only index projects. - if ($type != PhabricatorProjectProjectPHIDType::TYPECONST) { - return; - } - - $document = $event->getValue('document'); - - ManiphestNameIndex::updateIndex( - $phid, - $document->getDocumentTitle()); - } - -} diff --git a/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php b/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php index 0e6a94beba..e4147ad223 100644 --- a/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php +++ b/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php @@ -3,7 +3,11 @@ final class PhabricatorManiphestTaskTestDataGenerator extends PhabricatorTestDataGenerator { - public function generate() { + public function getGeneratorName() { + return pht('Maniphest Tasks'); + } + + public function generateObject() { $author_phid = $this->loadPhabrictorUserPHID(); $author = id(new PhabricatorUser()) ->loadOneWhere('phid = %s', $author_phid); diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index 134280a3a1..2861dc64af 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -531,7 +531,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { $fulltext_query->setParameter('types', array(ManiphestTaskPHIDType::TYPECONST)); - $engine = PhabricatorSearchEngine::loadEngine(); + $engine = PhabricatorFulltextStorageEngine::loadEngine(); $fulltext_results = $engine->executeSearch($fulltext_query); if (empty($fulltext_results)) { diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php index 5ed3757f46..b8069f92ed 100644 --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -437,4 +437,24 @@ final class ManiphestTaskSearchEngine $saved->setParameter('projectPHIDs', $project_phids); } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Task')) + ->setHref('/maniphest/task/edit/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Use Maniphest to track bugs, features, todos, or anything else '. + 'you need to get done. Tasks assigned to you will appear here.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/maniphest/search/ManiphestSearchIndexer.php b/src/applications/maniphest/search/ManiphestTaskFulltextEngine.php similarity index 50% rename from src/applications/maniphest/search/ManiphestSearchIndexer.php rename to src/applications/maniphest/search/ManiphestTaskFulltextEngine.php index 4ac9a51608..7d73c53e87 100644 --- a/src/applications/maniphest/search/ManiphestSearchIndexer.php +++ b/src/applications/maniphest/search/ManiphestTaskFulltextEngine.php @@ -1,60 +1,48 @@ loadDocumentByPHID($phid); + $task = $object; - $doc = new PhabricatorSearchAbstractDocument(); - $doc->setPHID($task->getPHID()); - $doc->setDocumentType(ManiphestTaskPHIDType::TYPECONST); - $doc->setDocumentTitle($task->getTitle()); - $doc->setDocumentCreated($task->getDateCreated()); - $doc->setDocumentModified($task->getDateModified()); + $document->setDocumentTitle($task->getTitle()); - $doc->addField( + $document->addField( PhabricatorSearchDocumentFieldType::FIELD_BODY, $task->getDescription()); - $doc->addRelationship( + $document->addRelationship( PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, $task->getAuthorPHID(), PhabricatorPeopleUserPHIDType::TYPECONST, $task->getDateCreated()); - $doc->addRelationship( + $document->addRelationship( $task->isClosed() ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, $task->getPHID(), ManiphestTaskPHIDType::TYPECONST, - time()); - - $this->indexTransactions( - $doc, - new ManiphestTransactionQuery(), - array($phid)); + PhabricatorTime::getNow()); $owner = $task->getOwnerPHID(); if ($owner) { - $doc->addRelationship( + $document->addRelationship( PhabricatorSearchRelationship::RELATIONSHIP_OWNER, $owner, PhabricatorPeopleUserPHIDType::TYPECONST, time()); } else { - $doc->addRelationship( + $document->addRelationship( PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED, $task->getPHID(), PhabricatorPHIDConstants::PHID_TYPE_VOID, $task->getDateCreated()); } - - return $doc; } } diff --git a/src/applications/maniphest/storage/ManiphestTask.php b/src/applications/maniphest/storage/ManiphestTask.php index c4fe177268..9f8dad1cd8 100644 --- a/src/applications/maniphest/storage/ManiphestTask.php +++ b/src/applications/maniphest/storage/ManiphestTask.php @@ -14,7 +14,8 @@ final class ManiphestTask extends ManiphestDAO PhabricatorApplicationTransactionInterface, PhabricatorProjectInterface, PhabricatorSpacesInterface, - PhabricatorConduitResultInterface { + PhabricatorConduitResultInterface, + PhabricatorFulltextInterface { const MARKUP_FIELD_DESCRIPTION = 'markup:desc'; @@ -452,4 +453,12 @@ final class ManiphestTask extends ManiphestDAO return array(); } + +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + + public function newFulltextEngine() { + return new ManiphestTaskFulltextEngine(); + } + } diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php index f83b1faf10..6c2c8c306f 100644 --- a/src/applications/maniphest/storage/ManiphestTransaction.php +++ b/src/applications/maniphest/storage/ManiphestTransaction.php @@ -167,6 +167,21 @@ final class ManiphestTransaction return parent::shouldHide(); } + public function shouldHideForFeed() { + switch ($this->getTransactionType()) { + case self::TYPE_UNBLOCK: + // Hide "alice created X, a task blocking Y." from feed because it + // will almost always appear adjacent to "alice created Y". + $is_new = $this->getMetadataValue('blocker.new'); + if ($is_new) { + return true; + } + break; + } + + return parent::shouldHideForFeed(); + } + public function getActionStrength() { switch ($this->getTransactionType()) { case self::TYPE_TITLE: @@ -381,6 +396,11 @@ final class ManiphestTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s created this task.', + $this->renderHandleLink($author_phid)); + case self::TYPE_TITLE: if ($old === null) { return pht( @@ -474,7 +494,12 @@ final class ManiphestTransaction $old_name = ManiphestTaskStatus::getTaskStatusName($old_status); $new_name = ManiphestTaskStatus::getTaskStatusName($new_status); - if ($old_closed && !$new_closed) { + if ($this->getMetadataValue('blocker.new')) { + return pht( + '%s created blocking task %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($blocker_phid)); + } else if ($old_closed && !$new_closed) { return pht( '%s reopened blocking task %s as "%s".', $this->renderHandleLink($author_phid), diff --git a/src/applications/metamta/receiver/__tests__/PhabricatorObjectMailReceiverTestCase.php b/src/applications/metamta/receiver/__tests__/PhabricatorObjectMailReceiverTestCase.php index 31ad44f974..2c2e0561cf 100644 --- a/src/applications/metamta/receiver/__tests__/PhabricatorObjectMailReceiverTestCase.php +++ b/src/applications/metamta/receiver/__tests__/PhabricatorObjectMailReceiverTestCase.php @@ -76,14 +76,12 @@ final class PhabricatorObjectMailReceiverTestCase } private function buildMail($style) { - - // TODO: Clean up test data generators so that we don't need to guarantee - // the existence of a user. - $this->generateNewTestUser(); - - $task = id(new PhabricatorManiphestTaskTestDataGenerator())->generate(); $user = $this->generateNewTestUser(); + $task = id(new PhabricatorManiphestTaskTestDataGenerator()) + ->setViewer($user) + ->generateObject(); + $is_public = ($style === 'public'); $is_bad_hash = ($style == 'badhash'); $is_bad_user = ($style == 'baduser'); diff --git a/src/applications/notification/engineextension/PhabricatorNotificationDestructionEngineExtension.php b/src/applications/notification/engineextension/PhabricatorNotificationDestructionEngineExtension.php new file mode 100644 index 0000000000..9c0bf52df6 --- /dev/null +++ b/src/applications/notification/engineextension/PhabricatorNotificationDestructionEngineExtension.php @@ -0,0 +1,26 @@ +establishConnection('w'); + + queryfx( + $conn_w, + 'DELETE FROM %T WHERE primaryObjectPHID = %s', + $table->getTableName(), + $object->getPHID()); + } + +} diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php index a622129e2d..539bdffa62 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php @@ -334,4 +334,8 @@ final class PhabricatorOwnersPackageTransactionEditor return $body; } + protected function supportsSearch() { + return true; + } + } diff --git a/src/applications/owners/query/PhabricatorOwnersPackageFulltextEngine.php b/src/applications/owners/query/PhabricatorOwnersPackageFulltextEngine.php new file mode 100644 index 0000000000..8a7046720b --- /dev/null +++ b/src/applications/owners/query/PhabricatorOwnersPackageFulltextEngine.php @@ -0,0 +1,26 @@ +setDocumentTitle($package->getName()); + + // TODO: These are bogus, but not currently stored on packages. + $document->setDocumentCreated(PhabricatorTime::getNow()); + $document->setDocumentModified(PhabricatorTime::getNow()); + + $document->addRelationship( + $package->isArchived() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $package->getPHID(), + PhabricatorOwnersPackagePHIDType::TYPECONST, + PhabricatorTime::getNow()); + } + +} diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php index 4c2569ad5d..86bcb54f9d 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php @@ -9,7 +9,6 @@ final class PhabricatorOwnersPackageQuery private $authorityPHIDs; private $repositoryPHIDs; private $paths; - private $namePrefix; private $statuses; private $controlMap = array(); @@ -78,9 +77,10 @@ final class PhabricatorOwnersPackageQuery return $this; } - public function withNamePrefix($prefix) { - $this->namePrefix = $prefix; - return $this; + public function withNameNgrams($ngrams) { + return $this->withNgramsConstraint( + new PhabricatorOwnersPackageNameNgrams(), + $ngrams); } public function needPaths($need_paths) { @@ -208,15 +208,6 @@ final class PhabricatorOwnersPackageQuery $this->statuses); } - if (strlen($this->namePrefix)) { - // NOTE: This is a hacky mess, but this column is currently case - // sensitive and unique. - $where[] = qsprintf( - $conn, - 'LOWER(p.name) LIKE %>', - phutil_utf8_strtolower($this->namePrefix)); - } - if ($this->controlMap) { $clauses = array(); foreach ($this->controlMap as $repository_phid => $paths) { diff --git a/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php b/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php index ec84b6793f..f67b82b7b5 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php @@ -25,6 +25,10 @@ final class PhabricatorOwnersPackageSearchEngine ->setDescription( pht('Search for packages with specific owners.')) ->setDatasource(new PhabricatorProjectOrUserDatasource()), + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('name') + ->setDescription(pht('Search for packages by name substrings.')), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Repositories')) ->setKey('repositoryPHIDs') @@ -69,6 +73,10 @@ final class PhabricatorOwnersPackageSearchEngine $query->withStatuses($map['statuses']); } + if (strlen($map['name'])) { + $query->withNameNgrams($map['name']); + } + return $query; } @@ -146,4 +154,25 @@ final class PhabricatorOwnersPackageSearchEngine return $result; } + + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Package')) + ->setHref('/owners/edit/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Group sections of a codebase into packages for re-use in other '. + 'areas of Phabricator, like Herald rules.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index 82bfa1cba7..23d540375d 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -7,7 +7,9 @@ final class PhabricatorOwnersPackage PhabricatorApplicationTransactionInterface, PhabricatorCustomFieldInterface, PhabricatorDestructibleInterface, - PhabricatorConduitResultInterface { + PhabricatorConduitResultInterface, + PhabricatorFulltextInterface, + PhabricatorNgramsInterface { protected $name; protected $originalName; @@ -46,7 +48,7 @@ final class PhabricatorOwnersPackage self::CONFIG_TIMESTAMPS => false, self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( - 'name' => 'text128', + 'name' => 'sort128', 'originalName' => 'text255', 'description' => 'text', 'primaryOwnerPHID' => 'phid?', @@ -54,17 +56,6 @@ final class PhabricatorOwnersPackage 'mailKey' => 'bytes20', 'status' => 'text32', ), - self::CONFIG_KEY_SCHEMA => array( - 'key_phid' => null, - 'phid' => array( - 'columns' => array('phid'), - 'unique' => true, - ), - 'name' => array( - 'columns' => array('name'), - 'unique' => true, - ), - ), ) + parent::getConfiguration(); } @@ -433,4 +424,23 @@ final class PhabricatorOwnersPackage ); } + +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + + public function newFulltextEngine() { + return new PhabricatorOwnersPackageFulltextEngine(); + } + + +/* -( PhabricatorNgramInterface )------------------------------------------ */ + + + public function newNgrams() { + return array( + id(new PhabricatorOwnersPackageNameNgrams()) + ->setValue($this->getName()), + ); + } + } diff --git a/src/applications/owners/storage/PhabricatorOwnersPackageNameNgrams.php b/src/applications/owners/storage/PhabricatorOwnersPackageNameNgrams.php new file mode 100644 index 0000000000..4038ecdc78 --- /dev/null +++ b/src/applications/owners/storage/PhabricatorOwnersPackageNameNgrams.php @@ -0,0 +1,18 @@ +getTransactionType()) { case self::TYPE_OWNERS: + if (!is_array($old)) { + $old = array(); + } + + if (!is_array($new)) { + $new = array(); + } + $add = array_diff($new, $old); foreach ($add as $phid) { $phids[] = $phid; @@ -47,11 +55,16 @@ final class PhabricatorOwnersPackageTransaction switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: - return ($old === null); + if ($old === null) { + return true; + } + break; case self::TYPE_PRIMARY: // TODO: Eventually, remove these transactions entirely. return true; } + + return parent::shouldHide(); } public function getTitle() { @@ -60,6 +73,10 @@ final class PhabricatorOwnersPackageTransaction $author_phid = $this->getAuthorPHID(); switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s created this package.', + $this->renderHandleLink($author_phid)); case self::TYPE_NAME: if ($old === null) { return pht( diff --git a/src/applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php b/src/applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php index 061cf91b64..9230ce270e 100644 --- a/src/applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php +++ b/src/applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php @@ -22,7 +22,7 @@ final class PhabricatorOwnersPackageDatasource $results = array(); $query = id(new PhabricatorOwnersPackageQuery()) - ->withNamePrefix($raw_query) + ->withNameNgrams($raw_query) ->setOrder('name'); $packages = $this->executeQuery($query); diff --git a/src/applications/passphrase/query/PassphraseCredentialSearchEngine.php b/src/applications/passphrase/query/PassphraseCredentialSearchEngine.php index a8a80e3e6b..d6d34baef1 100644 --- a/src/applications/passphrase/query/PassphraseCredentialSearchEngine.php +++ b/src/applications/passphrase/query/PassphraseCredentialSearchEngine.php @@ -111,4 +111,24 @@ final class PassphraseCredentialSearchEngine return $result; } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Credential')) + ->setHref('/passphrase/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Credential management for re-use in other areas of Phabricator '. + 'or general storage of shared secrets.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/passphrase/search/PassphraseCredentialFulltextEngine.php b/src/applications/passphrase/search/PassphraseCredentialFulltextEngine.php new file mode 100644 index 0000000000..72f11007bf --- /dev/null +++ b/src/applications/passphrase/search/PassphraseCredentialFulltextEngine.php @@ -0,0 +1,27 @@ +setDocumentTitle($credential->getName()); + + $document->addField( + PhabricatorSearchDocumentFieldType::FIELD_BODY, + $credential->getDescription()); + + $document->addRelationship( + $credential->getIsDestroyed() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $credential->getPHID(), + PassphraseCredentialPHIDType::TYPECONST, + PhabricatorTime::getNow()); + } + +} diff --git a/src/applications/passphrase/search/PassphraseSearchIndexer.php b/src/applications/passphrase/search/PassphraseSearchIndexer.php deleted file mode 100644 index 2f8da05a77..0000000000 --- a/src/applications/passphrase/search/PassphraseSearchIndexer.php +++ /dev/null @@ -1,39 +0,0 @@ -loadDocumentByPHID($phid); - - $doc = new PhabricatorSearchAbstractDocument(); - $doc->setPHID($credential->getPHID()); - $doc->setDocumentType(PassphraseCredentialPHIDType::TYPECONST); - $doc->setDocumentTitle($credential->getName()); - $doc->setDocumentCreated($credential->getDateCreated()); - $doc->setDocumentModified($credential->getDateModified()); - - $doc->addField( - PhabricatorSearchDocumentFieldType::FIELD_BODY, - $credential->getDescription()); - - $doc->addRelationship( - $credential->getIsDestroyed() - ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED - : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, - $credential->getPHID(), - PassphraseCredentialPHIDType::TYPECONST, - time()); - - $this->indexTransactions( - $doc, - new PassphraseCredentialTransactionQuery(), - array($phid)); - - return $doc; - } - -} diff --git a/src/applications/passphrase/storage/PassphraseCredential.php b/src/applications/passphrase/storage/PassphraseCredential.php index 1c58ced82d..4d705aff83 100644 --- a/src/applications/passphrase/storage/PassphraseCredential.php +++ b/src/applications/passphrase/storage/PassphraseCredential.php @@ -7,7 +7,8 @@ final class PassphraseCredential extends PassphraseDAO PhabricatorFlaggableInterface, PhabricatorSubscribableInterface, PhabricatorDestructibleInterface, - PhabricatorSpacesInterface { + PhabricatorSpacesInterface, + PhabricatorFulltextInterface { protected $name; protected $credentialType; @@ -189,4 +190,13 @@ final class PassphraseCredential extends PassphraseDAO return $this->spacePHID; } + +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + + public function newFulltextEngine() { + return new PassphraseCredentialFulltextEngine(); + } + + } diff --git a/src/applications/paste/lipsum/PhabricatorPasteTestDataGenerator.php b/src/applications/paste/lipsum/PhabricatorPasteTestDataGenerator.php index 60c75083b6..4647d72a30 100644 --- a/src/applications/paste/lipsum/PhabricatorPasteTestDataGenerator.php +++ b/src/applications/paste/lipsum/PhabricatorPasteTestDataGenerator.php @@ -3,13 +3,17 @@ final class PhabricatorPasteTestDataGenerator extends PhabricatorTestDataGenerator { + public function getGeneratorName() { + return pht('Pastes'); + } + // Better Support for this in the future public $supportedLanguages = array( 'Java' => 'java', 'PHP' => 'php', ); - public function generate() { + public function generateObject() { $author = $this->loadPhabrictorUser(); $authorphid = $author->getPHID(); $language = $this->generateLanguage(); diff --git a/src/applications/paste/query/PhabricatorPasteSearchEngine.php b/src/applications/paste/query/PhabricatorPasteSearchEngine.php index 38aaface64..fdf7824f07 100644 --- a/src/applications/paste/query/PhabricatorPasteSearchEngine.php +++ b/src/applications/paste/query/PhabricatorPasteSearchEngine.php @@ -202,4 +202,23 @@ final class PhabricatorPasteSearchEngine return $result; } + + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Paste')) + ->setHref('/paste/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Store, share, and embed snippets of code.')) + ->addAction($create_button); + + return $view; + } } diff --git a/src/applications/paste/storage/PhabricatorPasteTransaction.php b/src/applications/paste/storage/PhabricatorPasteTransaction.php index d6b962298b..1402f4c14c 100644 --- a/src/applications/paste/storage/PhabricatorPasteTransaction.php +++ b/src/applications/paste/storage/PhabricatorPasteTransaction.php @@ -41,7 +41,10 @@ final class PhabricatorPasteTransaction switch ($this->getTransactionType()) { case self::TYPE_TITLE: case self::TYPE_LANGUAGE: - return ($old === null); + if ($old === null) { + return true; + } + break; } return parent::shouldHide(); } @@ -77,6 +80,10 @@ final class PhabricatorPasteTransaction $type = $this->getTransactionType(); switch ($type) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s created this paste.', + $this->renderHandleLink($author_phid)); case self::TYPE_CONTENT: if ($old === null) { return pht( diff --git a/src/applications/people/lipsum/PhabricatorPeopleTestDataGenerator.php b/src/applications/people/lipsum/PhabricatorPeopleTestDataGenerator.php index bb7e73ceef..9b05b4acb1 100644 --- a/src/applications/people/lipsum/PhabricatorPeopleTestDataGenerator.php +++ b/src/applications/people/lipsum/PhabricatorPeopleTestDataGenerator.php @@ -3,7 +3,11 @@ final class PhabricatorPeopleTestDataGenerator extends PhabricatorTestDataGenerator { - public function generate() { + public function getGeneratorName() { + return pht('User Accounts'); + } + + public function generateObject() { while (true) { try { diff --git a/src/applications/people/search/PhabricatorUserFulltextEngine.php b/src/applications/people/search/PhabricatorUserFulltextEngine.php new file mode 100644 index 0000000000..577c2322e2 --- /dev/null +++ b/src/applications/people/search/PhabricatorUserFulltextEngine.php @@ -0,0 +1,22 @@ +setDocumentTitle($user->getFullName()); + + $document->addRelationship( + $user->isUserActivated() + ? PhabricatorSearchRelationship::RELATIONSHIP_OPEN + : PhabricatorSearchRelationship::RELATIONSHIP_CLOSED, + $user->getPHID(), + PhabricatorPeopleUserPHIDType::TYPECONST, + PhabricatorTime::getNow()); + } +} diff --git a/src/applications/people/search/PhabricatorUserSearchIndexer.php b/src/applications/people/search/PhabricatorUserSearchIndexer.php deleted file mode 100644 index 17c74ad4f9..0000000000 --- a/src/applications/people/search/PhabricatorUserSearchIndexer.php +++ /dev/null @@ -1,30 +0,0 @@ -loadDocumentByPHID($phid); - - $doc = new PhabricatorSearchAbstractDocument(); - $doc->setPHID($user->getPHID()); - $doc->setDocumentType(PhabricatorPeopleUserPHIDType::TYPECONST); - $doc->setDocumentTitle($user->getFullName()); - $doc->setDocumentCreated($user->getDateCreated()); - $doc->setDocumentModified($user->getDateModified()); - - $doc->addRelationship( - $user->isUserActivated() - ? PhabricatorSearchRelationship::RELATIONSHIP_OPEN - : PhabricatorSearchRelationship::RELATIONSHIP_CLOSED, - $user->getPHID(), - PhabricatorPeopleUserPHIDType::TYPECONST, - time()); - - return $doc; - } -} diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index c0a1c871a1..770b6a91b4 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -15,7 +15,8 @@ final class PhabricatorUser PhabricatorDestructibleInterface, PhabricatorSSHPublicKeyInterface, PhabricatorFlaggableInterface, - PhabricatorApplicationTransactionInterface { + PhabricatorApplicationTransactionInterface, + PhabricatorFulltextInterface { const SESSION_TABLE = 'phabricator_session'; const NAMETOKEN_TABLE = 'user_nametoken'; @@ -343,8 +344,7 @@ final class PhabricatorUser $this->updateNameTokens(); - id(new PhabricatorSearchIndexer()) - ->queueDocumentForIndexing($this->getPHID()); + PhabricatorSearchWorker::queueDocumentForIndexing($this->getPHID()); return $result; } @@ -1381,4 +1381,12 @@ final class PhabricatorUser return $timeline; } + +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + + public function newFulltextEngine() { + return new PhabricatorUserFulltextEngine(); + } + } diff --git a/src/applications/phame/controller/PhameHomeController.php b/src/applications/phame/controller/PhameHomeController.php index 6383a65894..c547071e67 100644 --- a/src/applications/phame/controller/PhameHomeController.php +++ b/src/applications/phame/controller/PhameHomeController.php @@ -15,16 +15,37 @@ final class PhameHomeController extends PhamePostController { ->needProfileImage(true) ->execute(); - $blog_phids = mpull($blogs, 'getPHID'); + $post_list = null; + if ($blogs) { + $blog_phids = mpull($blogs, 'getPHID'); - $pager = id(new AphrontCursorPagerView()) - ->readFromRequest($request); + $pager = id(new AphrontCursorPagerView()) + ->readFromRequest($request); - $posts = id(new PhamePostQuery()) - ->setViewer($viewer) - ->withBlogPHIDs($blog_phids) - ->withVisibility(PhameConstants::VISIBILITY_PUBLISHED) - ->executeWithCursorPager($pager); + $posts = id(new PhamePostQuery()) + ->setViewer($viewer) + ->withBlogPHIDs($blog_phids) + ->withVisibility(PhameConstants::VISIBILITY_PUBLISHED) + ->executeWithCursorPager($pager); + + $post_list = id(new PhamePostListView()) + ->setPosts($posts) + ->setViewer($viewer) + ->showBlog(true); + } else { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Blog')) + ->setHref('/phame/blog/new/') + ->setColor(PHUIButtonView::GREEN); + + $post_list = id(new PHUIBigInfoView()) + ->setIcon('fa-star') + ->setTitle('Welcome to Phame') + ->setDescription( + pht('There aren\'t any visible Blog Posts.')) + ->addAction($create_button); + } $actions = $this->renderActions($viewer); $action_button = id(new PHUIButtonView()) @@ -41,12 +62,6 @@ final class PhameHomeController extends PhamePostController { ->setHeader($title) ->addActionLink($action_button); - $post_list = id(new PhamePostListView()) - ->setPosts($posts) - ->setViewer($viewer) - ->showBlog(true) - ->setNodata(pht('No Recent Visible Posts.')); - $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); $crumbs->addTextCrumb( diff --git a/src/applications/phame/controller/blog/PhameBlogViewController.php b/src/applications/phame/controller/blog/PhameBlogViewController.php index dd963a327b..c387152019 100644 --- a/src/applications/phame/controller/blog/PhameBlogViewController.php +++ b/src/applications/phame/controller/blog/PhameBlogViewController.php @@ -57,12 +57,35 @@ final class PhameBlogViewController extends PhameLiveController { $header->setPolicyObject($blog); } - $post_list = id(new PhamePostListView()) - ->setPosts($posts) - ->setViewer($viewer) - ->setIsExternal($is_external) - ->setIsLive($is_live) - ->setNodata(pht('This blog has no visible posts.')); + if ($posts) { + $post_list = id(new PhamePostListView()) + ->setPosts($posts) + ->setViewer($viewer) + ->setIsExternal($is_external) + ->setIsLive($is_live) + ->setNodata(pht('This blog has no visible posts.')); + } else { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Write a Post')) + ->setHref($this->getApplicationURI('post/edit/?blog='.$blog->getID())) + ->setColor(PHUIButtonView::GREEN); + + $post_list = id(new PHUIBigInfoView()) + ->setIcon('fa-star') + ->setTitle($blog->getName()) + ->setDescription( + pht('No one has written any blog posts yet.')); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $blog, + PhabricatorPolicyCapability::CAN_EDIT); + + if ($can_edit) { + $post_list->addAction($create_button); + } + } $page = id(new PHUIDocumentViewPro()) ->setHeader($header) diff --git a/src/applications/phame/storage/PhameBlogTransaction.php b/src/applications/phame/storage/PhameBlogTransaction.php index e7cf87a6f5..6a89b936f6 100644 --- a/src/applications/phame/storage/PhameBlogTransaction.php +++ b/src/applications/phame/storage/PhameBlogTransaction.php @@ -24,7 +24,9 @@ final class PhameBlogTransaction $old = $this->getOldValue(); switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: - return ($old === null); + if ($old === null) { + return true; + } } return parent::shouldHide(); } @@ -98,6 +100,10 @@ final class PhameBlogTransaction $type = $this->getTransactionType(); switch ($type) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s created this blog.', + $this->renderHandleLink($author_phid)); case self::TYPE_NAME: if ($old === null) { return pht( diff --git a/src/applications/pholio/application/PhabricatorPholioApplication.php b/src/applications/pholio/application/PhabricatorPholioApplication.php index 2ed1dff750..16b868b9a1 100644 --- a/src/applications/pholio/application/PhabricatorPholioApplication.php +++ b/src/applications/pholio/application/PhabricatorPholioApplication.php @@ -44,6 +44,7 @@ final class PhabricatorPholioApplication extends PhabricatorApplication { '/pholio/' => array( '(?:query/(?P[^/]+)/)?' => 'PholioMockListController', 'new/' => 'PholioMockEditController', + 'create/' => 'PholioMockEditController', 'edit/(?P\d+)/' => 'PholioMockEditController', 'archive/(?P\d+)/' => 'PholioMockArchiveController', 'comment/(?P\d+)/' => 'PholioMockCommentController', @@ -64,7 +65,7 @@ final class PhabricatorPholioApplication extends PhabricatorApplication { $item = id(new PHUIListItemView()) ->setName(pht('Pholio Mock')) ->setIcon('fa-picture-o') - ->setHref($this->getBaseURI().'new/'); + ->setHref($this->getBaseURI().'create/'); $items[] = $item; return $items; diff --git a/src/applications/pholio/controller/PholioController.php b/src/applications/pholio/controller/PholioController.php index 533945c7c3..99a73e8933 100644 --- a/src/applications/pholio/controller/PholioController.php +++ b/src/applications/pholio/controller/PholioController.php @@ -13,7 +13,7 @@ abstract class PholioController extends PhabricatorController { $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Mock')) - ->setHref($this->getApplicationURI('new/')) + ->setHref($this->getApplicationURI('create/')) ->setIcon('fa-plus-square')); return $crumbs; diff --git a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php index a2fb3b38fb..ce620a0d3a 100644 --- a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php +++ b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php @@ -3,7 +3,11 @@ final class PhabricatorPholioMockTestDataGenerator extends PhabricatorTestDataGenerator { - public function generate() { + public function getGeneratorName() { + return pht('Pholio Mocks'); + } + + public function generateObject() { $author_phid = $this->loadPhabrictorUserPHID(); $author = id(new PhabricatorUser()) ->loadOneWhere('phid = %s', $author_phid); diff --git a/src/applications/pholio/query/PholioMockSearchEngine.php b/src/applications/pholio/query/PholioMockSearchEngine.php index 05f0d08923..3c9d0b0d6b 100644 --- a/src/applications/pholio/query/PholioMockSearchEngine.php +++ b/src/applications/pholio/query/PholioMockSearchEngine.php @@ -130,4 +130,24 @@ final class PholioMockSearchEngine extends PhabricatorApplicationSearchEngine { return $result; } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Mock')) + ->setHref('/pholio/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Upload sets of images for review with revision history and '. + 'inline comments.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/pholio/search/PholioMockFulltextEngine.php b/src/applications/pholio/search/PholioMockFulltextEngine.php new file mode 100644 index 0000000000..9a162168db --- /dev/null +++ b/src/applications/pholio/search/PholioMockFulltextEngine.php @@ -0,0 +1,25 @@ +setDocumentTitle($mock->getName()); + + $document->addField( + PhabricatorSearchDocumentFieldType::FIELD_BODY, + $mock->getDescription()); + + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, + $mock->getAuthorPHID(), + PhabricatorPeopleUserPHIDType::TYPECONST, + $mock->getDateCreated()); + } + +} diff --git a/src/applications/pholio/search/PholioSearchIndexer.php b/src/applications/pholio/search/PholioSearchIndexer.php deleted file mode 100644 index 6c1458b799..0000000000 --- a/src/applications/pholio/search/PholioSearchIndexer.php +++ /dev/null @@ -1,35 +0,0 @@ -loadDocumentByPHID($phid); - - $doc = $this->newDocument($phid) - ->setDocumentTitle($mock->getName()) - ->setDocumentCreated($mock->getDateCreated()) - ->setDocumentModified($mock->getDateModified()); - - $doc->addField( - PhabricatorSearchDocumentFieldType::FIELD_BODY, - $mock->getDescription()); - - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, - $mock->getAuthorPHID(), - PhabricatorPeopleUserPHIDType::TYPECONST, - $mock->getDateCreated()); - - $this->indexTransactions( - $doc, - new PholioTransactionQuery(), - array($phid)); - - return $doc; - } - -} diff --git a/src/applications/pholio/storage/PholioMock.php b/src/applications/pholio/storage/PholioMock.php index 71d1857383..6316d92cb9 100644 --- a/src/applications/pholio/storage/PholioMock.php +++ b/src/applications/pholio/storage/PholioMock.php @@ -11,7 +11,8 @@ final class PholioMock extends PholioDAO PhabricatorProjectInterface, PhabricatorDestructibleInterface, PhabricatorSpacesInterface, - PhabricatorMentionableInterface { + PhabricatorMentionableInterface, + PhabricatorFulltextInterface { const MARKUP_FIELD_DESCRIPTION = 'markup:description'; @@ -321,4 +322,12 @@ final class PholioMock extends PholioDAO } +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + + public function newFulltextEngine() { + return new PholioMockFulltextEngine(); + } + + } diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewStreamController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewStreamController.php index f5cedf225a..3846b9cf41 100644 --- a/src/applications/phpast/controller/PhabricatorXHPASTViewStreamController.php +++ b/src/applications/phpast/controller/PhabricatorXHPASTViewStreamController.php @@ -22,7 +22,7 @@ final class PhabricatorXHPASTViewStreamController foreach ($tree->getRawTokenStream() as $id => $token) { $seq = $id; $name = $token->getTypeName(); - $title = pht('Token %s: %s', $seq, $name); + $title = pht('Token %d: %s', $seq, $name); $tokens[] = phutil_tag( 'span', diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewTreeController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewTreeController.php index c15bdc2928..5b94323c9c 100644 --- a/src/applications/phpast/controller/PhabricatorXHPASTViewTreeController.php +++ b/src/applications/phpast/controller/PhabricatorXHPASTViewTreeController.php @@ -29,7 +29,7 @@ final class PhabricatorXHPASTViewTreeController protected function buildTree($root) { try { $name = $root->getTypeName(); - $title = $root->getDescription(); + $title = pht('Node %d: %s', $root->getID(), $name); } catch (Exception $ex) { $name = '???'; $title = '???'; diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php index 49e7f29bff..d7db7f6548 100644 --- a/src/applications/phriction/controller/PhrictionDocumentController.php +++ b/src/applications/phriction/controller/PhrictionDocumentController.php @@ -38,20 +38,33 @@ final class PhrictionDocumentController $document = PhrictionDocument::initializeNewDocument($viewer, $slug); + if ($slug == '/') { + $title = pht('Welcome to Phriction'); + $subtitle = pht('Phriction is a simple and easy to use wiki for '. + 'keeping track of documents and their changes.'); + $page_title = pht('Welcome'); + $create_text = pht('Edit this Document'); + + } else { + $title = pht('No Document Here'); + $subtitle = pht('There is no document here, but you may create it.'); + $page_title = pht('Page Not Found'); + $create_text = pht('Create this Document'); + } + $create_uri = '/phriction/edit/?slug='.$slug; + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText($create_text) + ->setHref($create_uri) + ->setColor(PHUIButtonView::GREEN); - $notice = new PHUIInfoView(); - $notice->setSeverity(PHUIInfoView::SEVERITY_WARNING); - $notice->setTitle(pht('No content here!')); - $notice->appendChild( - pht( - 'No document found at %s. You can '. - 'create a new document here.', - phutil_tag('tt', array(), $slug), - $create_uri)); - $core_content = $notice; + $core_content = id(new PHUIBigInfoView()) + ->setIcon('fa-book') + ->setTitle($title) + ->setDescription($subtitle) + ->addAction($create_button); - $page_title = pht('Page Not Found'); } else { $version = $request->getInt('v'); diff --git a/src/applications/phriction/search/PhrictionDocumentFulltextEngine.php b/src/applications/phriction/search/PhrictionDocumentFulltextEngine.php new file mode 100644 index 0000000000..a09994c0c3 --- /dev/null +++ b/src/applications/phriction/search/PhrictionDocumentFulltextEngine.php @@ -0,0 +1,44 @@ +setViewer($this->getViewer()) + ->withPHIDs(array($document->getPHID())) + ->needContent(true) + ->executeOne(); + + $content = $wiki->getContent(); + + $document->setDocumentTitle($content->getTitle()); + + // TODO: These are not quite correct, but we don't currently store the + // proper dates in a way that's easy to get to. + $document + ->setDocumentCreated($content->getDateCreated()) + ->setDocumentModified($content->getDateModified()); + + $document->addField( + PhabricatorSearchDocumentFieldType::FIELD_BODY, + $content->getContent()); + + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, + $content->getAuthorPHID(), + PhabricatorPeopleUserPHIDType::TYPECONST, + $content->getDateCreated()); + + $document->addRelationship( + ($wiki->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) + ? PhabricatorSearchRelationship::RELATIONSHIP_OPEN + : PhabricatorSearchRelationship::RELATIONSHIP_CLOSED, + $wiki->getPHID(), + PhrictionDocumentPHIDType::TYPECONST, + PhabricatorTime::getNow()); + } +} diff --git a/src/applications/phriction/search/PhrictionSearchIndexer.php b/src/applications/phriction/search/PhrictionSearchIndexer.php deleted file mode 100644 index 28b7e34bbf..0000000000 --- a/src/applications/phriction/search/PhrictionSearchIndexer.php +++ /dev/null @@ -1,47 +0,0 @@ -loadDocumentByPHID($phid); - - $content = id(new PhrictionContent())->load($document->getContentID()); - $document->attachContent($content); - - $content = $document->getContent(); - - $doc = new PhabricatorSearchAbstractDocument(); - $doc->setPHID($document->getPHID()); - $doc->setDocumentType(PhrictionDocumentPHIDType::TYPECONST); - $doc->setDocumentTitle($content->getTitle()); - - // TODO: This isn't precisely correct, denormalize into the Document table? - $doc->setDocumentCreated($content->getDateCreated()); - $doc->setDocumentModified($content->getDateModified()); - - $doc->addField( - PhabricatorSearchDocumentFieldType::FIELD_BODY, - $content->getContent()); - - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, - $content->getAuthorPHID(), - PhabricatorPeopleUserPHIDType::TYPECONST, - $content->getDateCreated()); - - $doc->addRelationship( - ($document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) - ? PhabricatorSearchRelationship::RELATIONSHIP_OPEN - : PhabricatorSearchRelationship::RELATIONSHIP_CLOSED, - $document->getPHID(), - PhrictionDocumentPHIDType::TYPECONST, - time()); - - return $doc; - } -} diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php index f274d87ad4..3c883625b0 100644 --- a/src/applications/phriction/storage/PhrictionDocument.php +++ b/src/applications/phriction/storage/PhrictionDocument.php @@ -7,7 +7,8 @@ final class PhrictionDocument extends PhrictionDAO PhabricatorFlaggableInterface, PhabricatorTokenReceiverInterface, PhabricatorDestructibleInterface, - PhabricatorApplicationTransactionInterface { + PhabricatorApplicationTransactionInterface, + PhabricatorFulltextInterface { protected $slug; protected $depth; @@ -252,4 +253,12 @@ final class PhrictionDocument extends PhrictionDAO $this->saveTransaction(); } + +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + + public function newFulltextEngine() { + return new PhrictionDocumentFulltextEngine(); + } + } diff --git a/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php b/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php index c36100f12b..64cfe6046b 100644 --- a/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php +++ b/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php @@ -262,4 +262,9 @@ final class PhabricatorPhurlURLEditor throw new PhabricatorApplicationTransactionValidationException($errors); } + protected function buildReplyHandler(PhabricatorLiskDAO $object) { + return id(new PhabricatorPhurlURLReplyHandler()) + ->setMailReceiver($object); + } + } diff --git a/src/applications/phurl/mail/PhabricatorPhurlURLMailReceiver.php b/src/applications/phurl/mail/PhabricatorPhurlURLMailReceiver.php new file mode 100644 index 0000000000..14a953bc31 --- /dev/null +++ b/src/applications/phurl/mail/PhabricatorPhurlURLMailReceiver.php @@ -0,0 +1,28 @@ +setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + } + + protected function getTransactionReplyHandler() { + return new PhabricatorPhurlURLReplyHandler(); + } + +} diff --git a/src/applications/phurl/mail/PhabricatorPhurlURLReplyHandler.php b/src/applications/phurl/mail/PhabricatorPhurlURLReplyHandler.php new file mode 100644 index 0000000000..c15394ffcf --- /dev/null +++ b/src/applications/phurl/mail/PhabricatorPhurlURLReplyHandler.php @@ -0,0 +1,19 @@ +setTag('a') + ->setText(pht('Shorten a URL')) + ->setHref('/phurl/url/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Create reusable, memorable, shorter URLs for easy accessibility.')) + ->addAction($create_button); + + return $view; + } } diff --git a/src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php b/src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php index f68eae2b49..ad44b6a56e 100644 --- a/src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php +++ b/src/applications/policy/__tests__/PhabricatorPolicyDataTestCase.php @@ -11,19 +11,11 @@ final class PhabricatorPolicyDataTestCase extends PhabricatorTestCase { public function testProjectPolicyMembership() { $author = $this->generateNewTestUser(); - $proj_a = id(new PhabricatorProject()) + $proj_a = PhabricatorProject::initializeNewProject($author) ->setName('A') - ->setAuthorPHID($author->getPHID()) - ->setIcon(PhabricatorProject::DEFAULT_ICON) - ->setColor(PhabricatorProject::DEFAULT_COLOR) - ->setIsMembershipLocked(0) ->save(); - $proj_b = id(new PhabricatorProject()) + $proj_b = PhabricatorProject::initializeNewProject($author) ->setName('B') - ->setAuthorPHID($author->getPHID()) - ->setIcon(PhabricatorProject::DEFAULT_ICON) - ->setColor(PhabricatorProject::DEFAULT_COLOR) - ->setIsMembershipLocked(0) ->save(); $proj_a->setViewPolicy($proj_b->getPHID())->save(); diff --git a/src/applications/policy/editor/PhabricatorPolicyEditEngineExtension.php b/src/applications/policy/editor/PhabricatorPolicyEditEngineExtension.php index ce534195a5..fd973c0da7 100644 --- a/src/applications/policy/editor/PhabricatorPolicyEditEngineExtension.php +++ b/src/applications/policy/editor/PhabricatorPolicyEditEngineExtension.php @@ -45,6 +45,7 @@ final class PhabricatorPolicyEditEngineExtension 'capability' => PhabricatorPolicyCapability::CAN_VIEW, 'label' => pht('View Policy'), 'description' => pht('Controls who can view the object.'), + 'description.conduit' => pht('Change the view policy of the object.'), 'edit' => 'view', ), PhabricatorTransactions::TYPE_EDIT_POLICY => array( @@ -53,6 +54,7 @@ final class PhabricatorPolicyEditEngineExtension 'capability' => PhabricatorPolicyCapability::CAN_EDIT, 'label' => pht('Edit Policy'), 'description' => pht('Controls who can edit the object.'), + 'description.conduit' => pht('Change the edit policy of the object.'), 'edit' => 'edit', ), PhabricatorTransactions::TYPE_JOIN_POLICY => array( @@ -61,6 +63,7 @@ final class PhabricatorPolicyEditEngineExtension 'capability' => PhabricatorPolicyCapability::CAN_JOIN, 'label' => pht('Join Policy'), 'description' => pht('Controls who can join the object.'), + 'description.conduit' => pht('Change the join policy of the object.'), 'edit' => 'join', ), ); @@ -76,6 +79,7 @@ final class PhabricatorPolicyEditEngineExtension $aliases = $spec['aliases']; $label = $spec['label']; $description = $spec['description']; + $conduit_description = $spec['description.conduit']; $edit = $spec['edit']; $policy_field = id(new PhabricatorPolicyEditField()) @@ -87,7 +91,8 @@ final class PhabricatorPolicyEditEngineExtension ->setPolicies($policies) ->setTransactionType($type) ->setEditTypeKey($edit) - ->setConduitDescription($description) + ->setDescription($description) + ->setConduitDescription($conduit_description) ->setConduitTypeDescription(pht('New policy PHID or constant.')) ->setValue($object->getPolicy($capability)); $fields[] = $policy_field; @@ -101,6 +106,7 @@ final class PhabricatorPolicyEditEngineExtension ->setLabel(pht('Space')) ->setEditTypeKey('space') ->setIsCopyable(true) + ->setIsLockable(false) ->setIsReorderable(false) ->setAliases(array('space', 'policy.space')) ->setTransactionType($type_space) @@ -111,6 +117,7 @@ final class PhabricatorPolicyEditEngineExtension ->setValue($object->getSpacePHID()); $fields[] = $space_field; + $space_field->setPolicyField($policy_field); $policy_field->setSpaceField($space_field); } } diff --git a/src/applications/policy/filter/PhabricatorPolicyFilter.php b/src/applications/policy/filter/PhabricatorPolicyFilter.php index f9be22ad52..ded757f305 100644 --- a/src/applications/policy/filter/PhabricatorPolicyFilter.php +++ b/src/applications/policy/filter/PhabricatorPolicyFilter.php @@ -243,50 +243,6 @@ final class PhabricatorPolicyFilter extends Phobject { } private function applyExtendedPolicyChecks(array $extended_objects) { - // First, we're going to detect cycles and reject any objects which are - // part of a cycle. We don't want to loop forever if an object has a - // self-referential or nonsense policy. - - static $in_flight = array(); - - $all_phids = array(); - foreach ($extended_objects as $key => $object) { - $phid = $object->getPHID(); - if (isset($in_flight[$phid])) { - // TODO: This could be more user-friendly. - $this->rejectObject($extended_objects[$key], false, ''); - unset($extended_objects[$key]); - continue; - } - - // We might throw from rejectObject(), so we don't want to actually mark - // anything as in-flight until we survive this entire step. - $all_phids[$phid] = $phid; - } - - foreach ($all_phids as $phid) { - $in_flight[$phid] = true; - } - - $caught = null; - try { - $extended_objects = $this->executeExtendedPolicyChecks($extended_objects); - } catch (Exception $ex) { - $caught = $ex; - } - - foreach ($all_phids as $phid) { - unset($in_flight[$phid]); - } - - if ($caught) { - throw $caught; - } - - return $extended_objects; - } - - private function executeExtendedPolicyChecks(array $extended_objects) { $viewer = $this->viewer; $filter_capabilities = $this->capabilities; @@ -396,10 +352,11 @@ final class PhabricatorPolicyFilter extends Phobject { } if ($objects_in) { - $objects_out = id(new PhabricatorPolicyFilter()) - ->setViewer($viewer) - ->requireCapabilities($capabilities) - ->apply($objects_in); + $objects_out = $this->executeExtendedPolicyChecks( + $viewer, + $capabilities, + $objects_in, + $key_map); $objects_out = mpull($objects_out, null, 'getPHID'); } else { $objects_out = array(); @@ -435,6 +392,53 @@ final class PhabricatorPolicyFilter extends Phobject { return $extended_objects; } + private function executeExtendedPolicyChecks( + PhabricatorUser $viewer, + array $capabilities, + array $objects, + array $key_map) { + + // Do crude cycle detection by seeing if we have a huge stack depth. + // Although more sophisticated cycle detection is possible in theory, + // it is difficult with hierarchical objects like subprojects. Many other + // checks make it difficult to create cycles normally, so just do a + // simple check here to limit damage. + + static $depth; + + $depth++; + + if ($depth > 32) { + foreach ($objects as $key => $object) { + $this->rejectObject($objects[$key], false, ''); + unset($objects[$key]); + continue; + } + } + + if (!$objects) { + return array(); + } + + $caught = null; + try { + $result = id(new PhabricatorPolicyFilter()) + ->setViewer($viewer) + ->requireCapabilities($capabilities) + ->apply($objects); + } catch (Exception $ex) { + $caught = $ex; + } + + $depth--; + + if ($caught) { + throw $caught; + } + + return $result; + } + private function checkCapability( PhabricatorPolicyInterface $object, $capability) { @@ -604,6 +608,11 @@ final class PhabricatorPolicyFilter extends Phobject { $rules = PhabricatorPolicyQuery::getObjectPolicyRules(null); + // Make sure we have clean, empty policy rule objects. + foreach ($rules as $key => $rule) { + $rules[$key] = clone $rule; + } + $results = array(); foreach ($map as $key => $object_list) { $rule = idx($rules, $key); diff --git a/src/applications/policy/rule/PhabricatorPolicyRule.php b/src/applications/policy/rule/PhabricatorPolicyRule.php index 59fcc954c7..bd5af9a1b8 100644 --- a/src/applications/policy/rule/PhabricatorPolicyRule.php +++ b/src/applications/policy/rule/PhabricatorPolicyRule.php @@ -11,10 +11,6 @@ abstract class PhabricatorPolicyRule extends Phobject { const CONTROL_TYPE_NONE = 'none'; abstract public function getRuleDescription(); - abstract public function applyRule( - PhabricatorUser $viewer, - $value, - PhabricatorPolicyInterface $object); public function willApplyRules( PhabricatorUser $viewer, @@ -23,6 +19,11 @@ abstract class PhabricatorPolicyRule extends Phobject { return; } + abstract public function applyRule( + PhabricatorUser $viewer, + $value, + PhabricatorPolicyInterface $object); + public function getValueControlType() { return self::CONTROL_TYPE_TEXT; } @@ -35,7 +36,7 @@ abstract class PhabricatorPolicyRule extends Phobject { * Return `true` if this rule can be applied to the given object. * * Some policy rules may only operation on certain kinds of objects. For - * example, a "task author" rule + * example, a "task author" rule can only operate on tasks. */ public function canApplyToObject(PhabricatorPolicyInterface $object) { return true; @@ -43,6 +44,7 @@ abstract class PhabricatorPolicyRule extends Phobject { protected function getDatasourceTemplate( PhabricatorTypeaheadDatasource $datasource) { + return array( 'markup' => new AphrontTokenizerTemplateView(), 'uri' => $datasource->getDatasourceURI(), @@ -65,6 +67,7 @@ abstract class PhabricatorPolicyRule extends Phobject { public function getRequiredHandlePHIDsForSummary($value) { $phids = array(); + switch ($this->getValueControlType()) { case self::CONTROL_TYPE_TOKENIZER: $phids = $value; @@ -86,7 +89,7 @@ abstract class PhabricatorPolicyRule extends Phobject { } /** - * Return true if the given value creates a rule with a meaningful effect. + * Return `true` if the given value creates a rule with a meaningful effect. * An example of a rule with no meaningful effect is a "users" rule with no * users specified. * @@ -131,7 +134,7 @@ abstract class PhabricatorPolicyRule extends Phobject { $cache->setKey(self::getObjectPolicyCacheKey($object, $rule), $hint); } - protected function getTransactionHint( + final protected function getTransactionHint( PhabricatorPolicyInterface $object) { $cache = PhabricatorCaches::getRequestCache(); @@ -164,7 +167,7 @@ abstract class PhabricatorPolicyRule extends Phobject { return null; } - public function getObjectPolicyFullKey() { + final public function getObjectPolicyFullKey() { $key = $this->getObjectPolicyKey(); if (!$key) { diff --git a/src/applications/ponder/query/PonderQuestionSearchEngine.php b/src/applications/ponder/query/PonderQuestionSearchEngine.php index c16c5442db..7e08a3fdf4 100644 --- a/src/applications/ponder/query/PonderQuestionSearchEngine.php +++ b/src/applications/ponder/query/PonderQuestionSearchEngine.php @@ -180,4 +180,23 @@ final class PonderQuestionSearchEngine return $result; } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Ask a Question')) + ->setHref('/ponder/question/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('A simple questions and answers application for your teams.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/ponder/search/PonderQuestionFulltextEngine.php b/src/applications/ponder/search/PonderQuestionFulltextEngine.php new file mode 100644 index 0000000000..6312d265d1 --- /dev/null +++ b/src/applications/ponder/search/PonderQuestionFulltextEngine.php @@ -0,0 +1,24 @@ +setDocumentTitle($question->getTitle()); + + $document->addField( + PhabricatorSearchDocumentFieldType::FIELD_BODY, + $question->getContent()); + + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, + $question->getAuthorPHID(), + PhabricatorPeopleUserPHIDType::TYPECONST, + $question->getDateCreated()); + } +} diff --git a/src/applications/ponder/search/PonderSearchIndexer.php b/src/applications/ponder/search/PonderSearchIndexer.php deleted file mode 100644 index 1b9e6d3f84..0000000000 --- a/src/applications/ponder/search/PonderSearchIndexer.php +++ /dev/null @@ -1,51 +0,0 @@ -loadDocumentByPHID($phid); - - $doc = $this->newDocument($phid) - ->setDocumentTitle($question->getTitle()) - ->setDocumentCreated($question->getDateCreated()) - ->setDocumentModified($question->getDateModified()); - - $doc->addField( - PhabricatorSearchDocumentFieldType::FIELD_BODY, - $question->getContent()); - - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, - $question->getAuthorPHID(), - PhabricatorPeopleUserPHIDType::TYPECONST, - $question->getDateCreated()); - - $answers = id(new PonderAnswerQuery()) - ->setViewer($this->getViewer()) - ->withQuestionIDs(array($question->getID())) - ->execute(); - foreach ($answers as $answer) { - if (strlen($answer->getContent())) { - $doc->addField( - PhabricatorSearchDocumentFieldType::FIELD_COMMENT, - $answer->getContent()); - } - } - - $this->indexTransactions( - $doc, - new PonderQuestionTransactionQuery(), - array($phid)); - $this->indexTransactions( - $doc, - new PonderAnswerTransactionQuery(), - mpull($answers, 'getPHID')); - - return $doc; - } -} diff --git a/src/applications/ponder/storage/PonderQuestion.php b/src/applications/ponder/storage/PonderQuestion.php index f02823b273..bbce081d88 100644 --- a/src/applications/ponder/storage/PonderQuestion.php +++ b/src/applications/ponder/storage/PonderQuestion.php @@ -10,7 +10,8 @@ final class PonderQuestion extends PonderDAO PhabricatorTokenReceiverInterface, PhabricatorProjectInterface, PhabricatorDestructibleInterface, - PhabricatorSpacesInterface { + PhabricatorSpacesInterface, + PhabricatorFulltextInterface { const MARKUP_FIELD_CONTENT = 'markup:content'; @@ -291,4 +292,12 @@ final class PonderQuestion extends PonderDAO return $this->spacePHID; } + +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + + public function newFulltextEngine() { + return new PonderQuestionFulltextEngine(); + } + } diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php new file mode 100644 index 0000000000..5b8e797bc2 --- /dev/null +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -0,0 +1,798 @@ + true, + ); + } + + public function testViewProject() { + $user = $this->createUser(); + $user->save(); + + $user2 = $this->createUser(); + $user2->save(); + + $proj = $this->createProject($user); + + $proj = $this->refreshProject($proj, $user, true); + + $this->joinProject($proj, $user); + $proj->setViewPolicy(PhabricatorPolicies::POLICY_USER); + $proj->save(); + + $can_view = PhabricatorPolicyCapability::CAN_VIEW; + + // When the view policy is set to "users", any user can see the project. + $this->assertTrue((bool)$this->refreshProject($proj, $user)); + $this->assertTrue((bool)$this->refreshProject($proj, $user2)); + + + // When the view policy is set to "no one", members can still see the + // project. + $proj->setViewPolicy(PhabricatorPolicies::POLICY_NOONE); + $proj->save(); + + $this->assertTrue((bool)$this->refreshProject($proj, $user)); + $this->assertFalse((bool)$this->refreshProject($proj, $user2)); + } + + public function testIsViewerMemberOrWatcher() { + $user1 = $this->createUser() + ->save(); + + $user2 = $this->createUser() + ->save(); + + $user3 = $this->createUser() + ->save(); + + $proj1 = $this->createProject($user1); + $proj1 = $this->refreshProject($proj1, $user1); + + $this->joinProject($proj1, $user1); + $this->joinProject($proj1, $user3); + $this->watchProject($proj1, $user3); + + $proj1 = $this->refreshProject($proj1, $user1); + + $this->assertTrue($proj1->isUserMember($user1->getPHID())); + + $proj1 = $this->refreshProject($proj1, $user1, false, true); + + $this->assertTrue($proj1->isUserMember($user1->getPHID())); + $this->assertFalse($proj1->isUserWatcher($user1->getPHID())); + + $proj1 = $this->refreshProject($proj1, $user1, true, false); + + $this->assertTrue($proj1->isUserMember($user1->getPHID())); + $this->assertFalse($proj1->isUserMember($user2->getPHID())); + $this->assertTrue($proj1->isUserMember($user3->getPHID())); + + $proj1 = $this->refreshProject($proj1, $user1, true, true); + + $this->assertTrue($proj1->isUserMember($user1->getPHID())); + $this->assertFalse($proj1->isUserMember($user2->getPHID())); + $this->assertTrue($proj1->isUserMember($user3->getPHID())); + + $this->assertFalse($proj1->isUserWatcher($user1->getPHID())); + $this->assertFalse($proj1->isUserWatcher($user2->getPHID())); + $this->assertTrue($proj1->isUserWatcher($user3->getPHID())); + } + + public function testEditProject() { + $user = $this->createUser(); + $user->save(); + + $user2 = $this->createUser(); + $user2->save(); + + $proj = $this->createProject($user); + + + // When edit and view policies are set to "user", anyone can edit. + $proj->setViewPolicy(PhabricatorPolicies::POLICY_USER); + $proj->setEditPolicy(PhabricatorPolicies::POLICY_USER); + $proj->save(); + + $this->assertTrue($this->attemptProjectEdit($proj, $user)); + + + // When edit policy is set to "no one", no one can edit. + $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE); + $proj->save(); + + $caught = null; + try { + $this->attemptProjectEdit($proj, $user); + } catch (Exception $ex) { + $caught = $ex; + } + $this->assertTrue($caught instanceof Exception); + } + + public function testAncestryQueries() { + $user = $this->createUser(); + $user->save(); + + $ancestor = $this->createProject($user); + $parent = $this->createProject($user, $ancestor); + $child = $this->createProject($user, $parent); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withAncestorProjectPHIDs(array($ancestor->getPHID())) + ->execute(); + $this->assertEqual(2, count($projects)); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withParentProjectPHIDs(array($ancestor->getPHID())) + ->execute(); + $this->assertEqual(1, count($projects)); + $this->assertEqual( + $parent->getPHID(), + head($projects)->getPHID()); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withAncestorProjectPHIDs(array($ancestor->getPHID())) + ->withDepthBetween(2, null) + ->execute(); + $this->assertEqual(1, count($projects)); + $this->assertEqual( + $child->getPHID(), + head($projects)->getPHID()); + + $parent2 = $this->createProject($user, $ancestor); + $child2 = $this->createProject($user, $parent2); + $grandchild2 = $this->createProject($user, $child2); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withAncestorProjectPHIDs(array($ancestor->getPHID())) + ->execute(); + $this->assertEqual(5, count($projects)); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withParentProjectPHIDs(array($ancestor->getPHID())) + ->execute(); + $this->assertEqual(2, count($projects)); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withAncestorProjectPHIDs(array($ancestor->getPHID())) + ->withDepthBetween(2, null) + ->execute(); + $this->assertEqual(3, count($projects)); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withAncestorProjectPHIDs(array($ancestor->getPHID())) + ->withDepthBetween(3, null) + ->execute(); + $this->assertEqual(1, count($projects)); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withPHIDs( + array( + $child->getPHID(), + $grandchild2->getPHID(), + )) + ->execute(); + $this->assertEqual(2, count($projects)); + } + + public function testMemberMaterialization() { + $material_type = PhabricatorProjectMaterializedMemberEdgeType::EDGECONST; + + $user = $this->createUser(); + $user->save(); + + $parent = $this->createProject($user); + $child = $this->createProject($user, $parent); + + $this->joinProject($child, $user); + + $parent_material = PhabricatorEdgeQuery::loadDestinationPHIDs( + $parent->getPHID(), + $material_type); + + $this->assertEqual( + array($user->getPHID()), + $parent_material); + } + + public function testMilestones() { + $user = $this->createUser(); + $user->save(); + + $parent = $this->createProject($user); + + $m1 = $this->createProject($user, $parent, true); + $m2 = $this->createProject($user, $parent, true); + $m3 = $this->createProject($user, $parent, true); + + $this->assertEqual(1, $m1->getMilestoneNumber()); + $this->assertEqual(2, $m2->getMilestoneNumber()); + $this->assertEqual(3, $m3->getMilestoneNumber()); + } + + public function testMilestoneMembership() { + $user = $this->createUser(); + $user->save(); + + $parent = $this->createProject($user); + $milestone = $this->createProject($user, $parent, true); + + $this->joinProject($parent, $user); + + $milestone = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withPHIDs(array($milestone->getPHID())) + ->executeOne(); + + $this->assertTrue($milestone->isUserMember($user->getPHID())); + + $milestone = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withPHIDs(array($milestone->getPHID())) + ->needMembers(true) + ->executeOne(); + + $this->assertEqual( + array($user->getPHID()), + $milestone->getMemberPHIDs()); + } + + public function testSameSlugAsName() { + // It should be OK to type the primary hashtag into "additional hashtags", + // even if the primary hashtag doesn't exist yet because you're creating + // or renaming the project. + + $user = $this->createUser(); + $user->save(); + + $project = $this->createProject($user); + + // In this first case, set the name and slugs at the same time. + $name = 'slugproject'; + + $xactions = array(); + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setNewValue($name); + $this->applyTransactions($project, $user, $xactions); + + $xactions = array(); + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setNewValue(array($name)); + $this->applyTransactions($project, $user, $xactions); + + $project = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withPHIDs(array($project->getPHID())) + ->needSlugs(true) + ->executeOne(); + + $slugs = $project->getSlugs(); + $slugs = mpull($slugs, 'getSlug'); + + $this->assertTrue(in_array($name, $slugs)); + + // In this second case, set the name first and then the slugs separately. + $name2 = 'slugproject2'; + $xactions = array(); + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setNewValue($name2); + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setNewValue(array($name2)); + + $this->applyTransactions($project, $user, $xactions); + + $project = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withPHIDs(array($project->getPHID())) + ->needSlugs(true) + ->executeOne(); + + $slugs = $project->getSlugs(); + $slugs = mpull($slugs, 'getSlug'); + + $this->assertTrue(in_array($name2, $slugs)); + } + + public function testDuplicateSlugs() { + // Creating a project with multiple duplicate slugs should succeed. + + $user = $this->createUser(); + $user->save(); + + $project = $this->createProject($user); + + $input = 'duplicate'; + + $xactions = array(); + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setNewValue(array($input, $input)); + + $this->applyTransactions($project, $user, $xactions); + + $project = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withPHIDs(array($project->getPHID())) + ->needSlugs(true) + ->executeOne(); + + $slugs = $project->getSlugs(); + $slugs = mpull($slugs, 'getSlug'); + + $this->assertTrue(in_array($input, $slugs)); + } + + public function testNormalizeSlugs() { + // When a user creates a project with slug "XxX360n0sc0perXxX", normalize + // it before writing it. + + $user = $this->createUser(); + $user->save(); + + $project = $this->createProject($user); + + $input = 'NoRmAlIzE'; + $expect = 'normalize'; + + $xactions = array(); + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setNewValue(array($input)); + + $this->applyTransactions($project, $user, $xactions); + + $project = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withPHIDs(array($project->getPHID())) + ->needSlugs(true) + ->executeOne(); + + $slugs = $project->getSlugs(); + $slugs = mpull($slugs, 'getSlug'); + + $this->assertTrue(in_array($expect, $slugs)); + + + // If another user tries to add the same slug in denormalized form, it + // should be caught and fail, even though the database version of the slug + // is normalized. + + $project2 = $this->createProject($user); + + $xactions = array(); + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setNewValue(array($input)); + + $caught = null; + try { + $this->applyTransactions($project2, $user, $xactions); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $caught = $ex; + } + + $this->assertTrue((bool)$caught); + } + + public function testProjectMembersVisibility() { + // This is primarily testing that you can create a project and set the + // visibility or edit policy to "Project Members" immediately. + + $user1 = $this->createUser(); + $user1->save(); + + $user2 = $this->createUser(); + $user2->save(); + + $project = PhabricatorProject::initializeNewProject($user1); + $name = pht('Test Project %d', mt_rand()); + + $xactions = array(); + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setNewValue($name); + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) + ->setNewValue( + id(new PhabricatorProjectMembersPolicyRule()) + ->getObjectPolicyFullKey()); + + $edge_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $edge_type) + ->setNewValue( + array( + '=' => array($user1->getPHID() => $user1->getPHID()), + )); + + $this->applyTransactions($project, $user1, $xactions); + + $this->assertTrue((bool)$this->refreshProject($project, $user1)); + $this->assertFalse((bool)$this->refreshProject($project, $user2)); + + $this->leaveProject($project, $user1); + + $this->assertFalse((bool)$this->refreshProject($project, $user1)); + } + + public function testParentProject() { + $user = $this->createUser(); + $user->save(); + + $parent = $this->createProject($user); + $child = $this->createProject($user, $parent); + + $this->assertTrue(true); + + $child = $this->refreshProject($child, $user); + + $this->assertEqual( + $parent->getPHID(), + $child->getParentProject()->getPHID()); + + $this->assertEqual(1, (int)$child->getProjectDepth()); + + $this->assertFalse( + $child->isUserMember($user->getPHID())); + + $this->assertFalse( + $child->getParentProject()->isUserMember($user->getPHID())); + + $this->joinProject($child, $user); + + $child = $this->refreshProject($child, $user); + + $this->assertTrue( + $child->isUserMember($user->getPHID())); + + $this->assertTrue( + $child->getParentProject()->isUserMember($user->getPHID())); + + + // Test that hiding a parent hides the child. + + $user2 = $this->createUser(); + $user2->save(); + + // Second user can see the project for now. + $this->assertTrue((bool)$this->refreshProject($child, $user2)); + + // Hide the parent. + $this->setViewPolicy($parent, $user, $user->getPHID()); + + // First user (who can see the parent because they are a member of + // the child) can see the project. + $this->assertTrue((bool)$this->refreshProject($child, $user)); + + // Second user can not, because they can't see the parent. + $this->assertFalse((bool)$this->refreshProject($child, $user2)); + } + + private function attemptProjectEdit( + PhabricatorProject $proj, + PhabricatorUser $user, + $skip_refresh = false) { + + $proj = $this->refreshProject($proj, $user, true); + + $new_name = $proj->getName().' '.mt_rand(); + + $xaction = new PhabricatorProjectTransaction(); + $xaction->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME); + $xaction->setNewValue($new_name); + + $this->applyTransactions($proj, $user, array($xaction)); + + return true; + } + + public function testJoinLeaveProject() { + $user = $this->createUser(); + $user->save(); + + $proj = $this->createProjectWithNewAuthor(); + + $proj = $this->refreshProject($proj, $user, true); + $this->assertTrue( + (bool)$proj, + pht( + 'Assumption that projects are default visible '. + 'to any user when created.')); + + $this->assertFalse( + $proj->isUserMember($user->getPHID()), + pht('Arbitrary user not member of project.')); + + // Join the project. + $this->joinProject($proj, $user); + + $proj = $this->refreshProject($proj, $user, true); + $this->assertTrue((bool)$proj); + + $this->assertTrue( + $proj->isUserMember($user->getPHID()), + pht('Join works.')); + + + // Join the project again. + $this->joinProject($proj, $user); + + $proj = $this->refreshProject($proj, $user, true); + $this->assertTrue((bool)$proj); + + $this->assertTrue( + $proj->isUserMember($user->getPHID()), + pht('Joining an already-joined project is a no-op.')); + + + // Leave the project. + $this->leaveProject($proj, $user); + + $proj = $this->refreshProject($proj, $user, true); + $this->assertTrue((bool)$proj); + + $this->assertFalse( + $proj->isUserMember($user->getPHID()), + pht('Leave works.')); + + + // Leave the project again. + $this->leaveProject($proj, $user); + + $proj = $this->refreshProject($proj, $user, true); + $this->assertTrue((bool)$proj); + + $this->assertFalse( + $proj->isUserMember($user->getPHID()), + pht('Leaving an already-left project is a no-op.')); + + + // If a user can't edit or join a project, joining fails. + $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE); + $proj->setJoinPolicy(PhabricatorPolicies::POLICY_NOONE); + $proj->save(); + + $proj = $this->refreshProject($proj, $user, true); + $caught = null; + try { + $this->joinProject($proj, $user); + } catch (Exception $ex) { + $caught = $ex; + } + $this->assertTrue($ex instanceof Exception); + + + // If a user can edit a project, they can join. + $proj->setEditPolicy(PhabricatorPolicies::POLICY_USER); + $proj->setJoinPolicy(PhabricatorPolicies::POLICY_NOONE); + $proj->save(); + + $proj = $this->refreshProject($proj, $user, true); + $this->joinProject($proj, $user); + $proj = $this->refreshProject($proj, $user, true); + $this->assertTrue( + $proj->isUserMember($user->getPHID()), + pht('Join allowed with edit permission.')); + $this->leaveProject($proj, $user); + + + // If a user can join a project, they can join, even if they can't edit. + $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE); + $proj->setJoinPolicy(PhabricatorPolicies::POLICY_USER); + $proj->save(); + + $proj = $this->refreshProject($proj, $user, true); + $this->joinProject($proj, $user); + $proj = $this->refreshProject($proj, $user, true); + $this->assertTrue( + $proj->isUserMember($user->getPHID()), + pht('Join allowed with join permission.')); + + + // A user can leave a project even if they can't edit it or join. + $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE); + $proj->setJoinPolicy(PhabricatorPolicies::POLICY_NOONE); + $proj->save(); + + $proj = $this->refreshProject($proj, $user, true); + $this->leaveProject($proj, $user); + $proj = $this->refreshProject($proj, $user, true); + $this->assertFalse( + $proj->isUserMember($user->getPHID()), + pht('Leave allowed without any permission.')); + } + + private function refreshProject( + PhabricatorProject $project, + PhabricatorUser $viewer, + $need_members = false, + $need_watchers = false) { + + $results = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->needMembers($need_members) + ->needWatchers($need_watchers) + ->withIDs(array($project->getID())) + ->execute(); + + if ($results) { + return head($results); + } else { + return null; + } + } + + private function createProject( + PhabricatorUser $user, + PhabricatorProject $parent = null, + $is_milestone = false) { + + $project = PhabricatorProject::initializeNewProject($user); + + + $name = pht('Test Project %d', mt_rand()); + + $xactions = array(); + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setNewValue($name); + + if ($parent) { + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT) + ->setNewValue($parent->getPHID()); + } + + if ($is_milestone) { + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE) + ->setNewValue(true); + } + + $this->applyTransactions($project, $user, $xactions); + + return $project; + } + + private function setViewPolicy( + PhabricatorProject $project, + PhabricatorUser $user, + $policy) { + + $xactions = array(); + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) + ->setNewValue($policy); + + $this->applyTransactions($project, $user, $xactions); + + return $project; + } + + private function createProjectWithNewAuthor() { + $author = $this->createUser(); + $author->save(); + + $project = $this->createProject($author); + + return $project; + } + + private function createUser() { + $rand = mt_rand(); + + $user = new PhabricatorUser(); + $user->setUsername('unittestuser'.$rand); + $user->setRealName(pht('Unit Test User %d', $rand)); + + return $user; + } + + private function joinProject( + PhabricatorProject $project, + PhabricatorUser $user) { + return $this->joinOrLeaveProject($project, $user, '+'); + } + + private function leaveProject( + PhabricatorProject $project, + PhabricatorUser $user) { + return $this->joinOrLeaveProject($project, $user, '-'); + } + + private function watchProject( + PhabricatorProject $project, + PhabricatorUser $user) { + return $this->watchOrUnwatchProject($project, $user, '+'); + } + + private function unwatchProject( + PhabricatorProject $project, + PhabricatorUser $user) { + return $this->watchOrUnwatchProject($project, $user, '-'); + } + + private function joinOrLeaveProject( + PhabricatorProject $project, + PhabricatorUser $user, + $operation) { + return $this->applyProjectEdgeTransaction( + $project, + $user, + $operation, + PhabricatorProjectProjectHasMemberEdgeType::EDGECONST); + } + + private function watchOrUnwatchProject( + PhabricatorProject $project, + PhabricatorUser $user, + $operation) { + return $this->applyProjectEdgeTransaction( + $project, + $user, + $operation, + PhabricatorObjectHasWatcherEdgeType::EDGECONST); + } + + private function applyProjectEdgeTransaction( + PhabricatorProject $project, + PhabricatorUser $user, + $operation, + $edge_type) { + + $spec = array( + $operation => array($user->getPHID() => $user->getPHID()), + ); + + $xactions = array(); + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $edge_type) + ->setNewValue($spec); + + $this->applyTransactions($project, $user, $xactions); + + return $project; + } + + private function applyTransactions( + PhabricatorProject $project, + PhabricatorUser $user, + array $xactions) { + + $editor = id(new PhabricatorProjectTransactionEditor()) + ->setActor($user) + ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContinueOnNoEffect(true) + ->applyTransactions($project, $xactions); + } + + +} diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 025ad59226..ea85ef7c11 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -90,7 +90,8 @@ final class PhabricatorProjectProfileController ->setName(pht('Edit Details')) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI("details/{$id}/")) - ->setDisabled(!$can_edit)); + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); $view->addAction( id(new PhabricatorActionView()) diff --git a/src/applications/project/edge/PhabricatorProjectMaterializedMemberEdgeType.php b/src/applications/project/edge/PhabricatorProjectMaterializedMemberEdgeType.php new file mode 100644 index 0000000000..7860495917 --- /dev/null +++ b/src/applications/project/edge/PhabricatorProjectMaterializedMemberEdgeType.php @@ -0,0 +1,8 @@ +getColor(); case PhabricatorProjectTransaction::TYPE_LOCKED: return (int)$object->getIsMembershipLocked(); + case PhabricatorProjectTransaction::TYPE_PARENT: + case PhabricatorProjectTransaction::TYPE_MILESTONE: + return null; } return parent::getCustomTransactionOldValue($object, $xaction); @@ -63,13 +68,29 @@ final class PhabricatorProjectTransactionEditor switch ($xaction->getTransactionType()) { case PhabricatorProjectTransaction::TYPE_NAME: - case PhabricatorProjectTransaction::TYPE_SLUGS: case PhabricatorProjectTransaction::TYPE_STATUS: case PhabricatorProjectTransaction::TYPE_IMAGE: case PhabricatorProjectTransaction::TYPE_ICON: case PhabricatorProjectTransaction::TYPE_COLOR: case PhabricatorProjectTransaction::TYPE_LOCKED: + case PhabricatorProjectTransaction::TYPE_PARENT: return $xaction->getNewValue(); + case PhabricatorProjectTransaction::TYPE_SLUGS: + return $this->normalizeSlugs($xaction->getNewValue()); + case PhabricatorProjectTransaction::TYPE_MILESTONE: + $current = queryfx_one( + $object->establishConnection('w'), + 'SELECT MAX(milestoneNumber) n + FROM %T + WHERE parentProjectPHID = %s', + $object->getTableName(), + $object->getParentProject()->getPHID()); + if (!$current) { + $number = 1; + } else { + $number = (int)$current['n'] + 1; + } + return $number; } return parent::getCustomTransactionNewValue($object, $xaction); @@ -102,6 +123,12 @@ final class PhabricatorProjectTransactionEditor case PhabricatorProjectTransaction::TYPE_LOCKED: $object->setIsMembershipLocked($xaction->getNewValue()); return; + case PhabricatorProjectTransaction::TYPE_PARENT: + $object->setParentProjectPHID($xaction->getNewValue()); + return; + case PhabricatorProjectTransaction::TYPE_MILESTONE: + $object->setMilestoneNumber($xaction->getNewValue()); + return; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -119,9 +146,9 @@ final class PhabricatorProjectTransactionEditor // First, add the old name as a secondary slug; this is helpful // for renames and generally a good thing to do. if ($old !== null) { - $this->addSlug($object, $old); + $this->addSlug($object, $old, false); } - $this->addSlug($object, $new); + $this->addSlug($object, $new, false); return; case PhabricatorProjectTransaction::TYPE_SLUGS: @@ -130,29 +157,19 @@ final class PhabricatorProjectTransactionEditor $add = array_diff($new, $old); $rem = array_diff($old, $new); - if ($add) { - $add_slug_template = id(new PhabricatorProjectSlug()) - ->setProjectPHID($object->getPHID()); - foreach ($add as $add_slug_str) { - $add_slug = id(clone $add_slug_template) - ->setSlug($add_slug_str) - ->save(); - } - } - if ($rem) { - $rem_slugs = id(new PhabricatorProjectSlug()) - ->loadAllWhere('slug IN (%Ls)', $rem); - foreach ($rem_slugs as $rem_slug) { - $rem_slug->delete(); - } + foreach ($add as $slug) { + $this->addSlug($object, $slug, true); } + $this->removeSlugs($object, $rem); return; case PhabricatorProjectTransaction::TYPE_STATUS: case PhabricatorProjectTransaction::TYPE_IMAGE: case PhabricatorProjectTransaction::TYPE_ICON: case PhabricatorProjectTransaction::TYPE_COLOR: case PhabricatorProjectTransaction::TYPE_LOCKED: + case PhabricatorProjectTransaction::TYPE_PARENT: + case PhabricatorProjectTransaction::TYPE_MILESTONE: return; } @@ -251,6 +268,17 @@ final class PhabricatorProjectTransactionEditor } $name = last($xactions)->getNewValue(); + + if (!PhabricatorSlug::isValidProjectSlug($name)) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'Project names must contain at least one letter or number.'), + last($xactions)); + break; + } + $name_used_already = id(new PhabricatorProjectQuery()) ->setViewer($this->getActor()) ->withNames(array($name)) @@ -285,8 +313,31 @@ final class PhabricatorProjectTransactionEditor } $slug_xaction = last($xactions); + $new = $slug_xaction->getNewValue(); + $invalid = array(); + foreach ($new as $slug) { + if (!PhabricatorSlug::isValidProjectSlug($slug)) { + $invalid[] = $slug; + } + } + + if ($invalid) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'Hashtags must contain at least one letter or number. %s '. + 'project hashtag(s) are invalid: %s.', + phutil_count($invalid), + implode(', ', $invalid)), + $slug_xaction); + break; + } + + $new = $this->normalizeSlugs($new); + if ($new) { $slugs_used_already = id(new PhabricatorProjectSlug()) ->loadAllWhere('slug IN (%Ls)', $new); @@ -297,34 +348,95 @@ final class PhabricatorProjectTransactionEditor $slugs_used_already = mgroup($slugs_used_already, 'getProjectPHID'); foreach ($slugs_used_already as $project_phid => $used_slugs) { - $used_slug_strs = mpull($used_slugs, 'getSlug'); if ($project_phid == $object->getPHID()) { - if (in_array($object->getPrimarySlug(), $used_slug_strs)) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Project hashtag %s is already the primary hashtag.', - $object->getPrimarySlug()), - $slug_xaction); - $errors[] = $error; - } continue; } + $used_slug_strs = mpull($used_slugs, 'getSlug'); + $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( - '%d project hashtag(s) are already used: %s.', - count($used_slug_strs), + '%s project hashtag(s) are already used by other projects: %s.', + phutil_count($used_slug_strs), implode(', ', $used_slug_strs)), $slug_xaction); $errors[] = $error; } break; + case PhabricatorProjectTransaction::TYPE_PARENT: + if (!$xactions) { + break; + } + $xaction = last($xactions); + + if (!$this->getIsNewObject()) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'You can only set a parent project when creating a project '. + 'for the first time.'), + $xaction); + break; + } + + $parent_phid = $xaction->getNewValue(); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($this->requireActor()) + ->withPHIDs(array($parent_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + if (!$projects) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'Parent project PHID ("%s") must be the PHID of a valid, '. + 'visible project which you have permission to edit.', + $parent_phid), + $xaction); + break; + } + + $project = head($projects); + + if ($project->isMilestone()) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'Parent project PHID ("%s") must not be a milestone. '. + 'Milestones may not have subprojects.', + $parent_phid), + $xaction); + break; + } + + $limit = PhabricatorProject::getProjectDepthLimit(); + if ($project->getProjectDepth() >= ($limit - 1)) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'You can not create a subproject under this parent because '. + 'it would nest projects too deeply. The maximum nesting '. + 'depth of projects is %s.', + new PhutilNumber($limit)), + $xaction); + break; + } + + $object->attachParentProject($project); + break; } return $errors; @@ -493,23 +605,140 @@ final class PhabricatorProjectTransactionEditor return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); } - private function addSlug( + protected function applyFinalEffects( PhabricatorLiskDAO $object, - $name) { + array $xactions) { - $slug = PhabricatorSlug::normalizeProjectSlug($name); + $materialize = false; + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhabricatorTransactions::TYPE_EDGE: + switch ($xaction->getMetadataValue('edge:type')) { + case PhabricatorProjectProjectHasMemberEdgeType::EDGECONST: + $materialize = true; + break; + } + break; + case PhabricatorProjectTransaction::TYPE_PARENT: + $materialize = true; + break; + } + } - $slug_object = id(new PhabricatorProjectSlug())->loadOneWhere( - 'slug = %s', - $slug); + if ($materialize) { + id(new PhabricatorProjectsMembershipIndexEngineExtension()) + ->rematerialize($object); + } - if ($slug_object) { + return parent::applyFinalEffects($object, $xactions); + } + + private function addSlug(PhabricatorProject $project, $slug, $force) { + $slug = PhabricatorSlug::normalizeProjectSlug($slug); + $table = new PhabricatorProjectSlug(); + $project_phid = $project->getPHID(); + + if ($force) { + // If we have the `$force` flag set, we only want to ignore an existing + // slug if it's for the same project. We'll error on collisions with + // other projects. + $current = $table->loadOneWhere( + 'slug = %s AND projectPHID = %s', + $slug, + $project_phid); + } else { + // Without the `$force` flag, we'll just return without doing anything + // if any other project already has the slug. + $current = $table->loadOneWhere( + 'slug = %s', + $slug); + } + + if ($current) { return; } - $new_slug = id(new PhabricatorProjectSlug()) + return id(new PhabricatorProjectSlug()) ->setSlug($slug) - ->setProjectPHID($object->getPHID()) + ->setProjectPHID($project_phid) ->save(); } + + private function removeSlugs(PhabricatorProject $project, array $slugs) { + $slugs = $this->normalizeSlugs($slugs); + + if (!$slugs) { + return; + } + + $objects = id(new PhabricatorProjectSlug())->loadAllWhere( + 'projectPHID = %s AND slug IN (%Ls)', + $project->getPHID(), + $slugs); + + foreach ($objects as $object) { + $object->delete(); + } + } + + private function normalizeSlugs(array $slugs) { + foreach ($slugs as $key => $slug) { + $slugs[$key] = PhabricatorSlug::normalizeProjectSlug($slug); + } + + $slugs = array_unique($slugs); + $slugs = array_values($slugs); + + return $slugs; + } + + protected function adjustObjectForPolicyChecks( + PhabricatorLiskDAO $object, + array $xactions) { + + $copy = parent::adjustObjectForPolicyChecks($object, $xactions); + + $type_edge = PhabricatorTransactions::TYPE_EDGE; + $edgetype_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; + + $member_xaction = null; + foreach ($xactions as $xaction) { + if ($xaction->getTransactionType() !== $type_edge) { + continue; + } + + $edgetype = $xaction->getMetadataValue('edge:type'); + if ($edgetype !== $edgetype_member) { + continue; + } + + $member_xaction = $xaction; + } + + if ($member_xaction) { + $object_phid = $object->getPHID(); + + if ($object_phid) { + $members = PhabricatorEdgeQuery::loadDestinationPHIDs( + $object_phid, + PhabricatorProjectProjectHasMemberEdgeType::EDGECONST); + } else { + $members = array(); + } + + $clone_xaction = clone $member_xaction; + $hint = $this->getPHIDTransactionNewValue($clone_xaction, $members); + $rule = new PhabricatorProjectMembersPolicyRule(); + + $hint = array_fuse($hint); + + PhabricatorPolicyRule::passTransactionHintToRule( + $copy, + $rule, + $hint); + } + + return $copy; + } + } diff --git a/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php b/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php deleted file mode 100644 index 4d19812fe3..0000000000 --- a/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php +++ /dev/null @@ -1,291 +0,0 @@ - true, - ); - } - - public function testViewProject() { - $user = $this->createUser(); - $user->save(); - - $user2 = $this->createUser(); - $user2->save(); - - $proj = $this->createProject($user); - - $proj = $this->refreshProject($proj, $user, true); - - $this->joinProject($proj, $user); - $proj->setViewPolicy(PhabricatorPolicies::POLICY_USER); - $proj->save(); - - $can_view = PhabricatorPolicyCapability::CAN_VIEW; - - // When the view policy is set to "users", any user can see the project. - $this->assertTrue((bool)$this->refreshProject($proj, $user)); - $this->assertTrue((bool)$this->refreshProject($proj, $user2)); - - - // When the view policy is set to "no one", members can still see the - // project. - $proj->setViewPolicy(PhabricatorPolicies::POLICY_NOONE); - $proj->save(); - - $this->assertTrue((bool)$this->refreshProject($proj, $user)); - $this->assertFalse((bool)$this->refreshProject($proj, $user2)); - } - - public function testEditProject() { - $user = $this->createUser(); - $user->save(); - - $user2 = $this->createUser(); - $user2->save(); - - $proj = $this->createProject($user); - - - // When edit and view policies are set to "user", anyone can edit. - $proj->setViewPolicy(PhabricatorPolicies::POLICY_USER); - $proj->setEditPolicy(PhabricatorPolicies::POLICY_USER); - $proj->save(); - - $this->assertTrue($this->attemptProjectEdit($proj, $user)); - - - // When edit policy is set to "no one", no one can edit. - $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE); - $proj->save(); - - $caught = null; - try { - $this->attemptProjectEdit($proj, $user); - } catch (Exception $ex) { - $caught = $ex; - } - $this->assertTrue($caught instanceof Exception); - } - - private function attemptProjectEdit( - PhabricatorProject $proj, - PhabricatorUser $user, - $skip_refresh = false) { - - $proj = $this->refreshProject($proj, $user, true); - - $new_name = $proj->getName().' '.mt_rand(); - - $xaction = new PhabricatorProjectTransaction(); - $xaction->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME); - $xaction->setNewValue($new_name); - - $editor = new PhabricatorProjectTransactionEditor(); - $editor->setActor($user); - $editor->setContentSource(PhabricatorContentSource::newConsoleSource()); - $editor->applyTransactions($proj, array($xaction)); - - return true; - } - - public function testJoinLeaveProject() { - $user = $this->createUser(); - $user->save(); - - $proj = $this->createProjectWithNewAuthor(); - - $proj = $this->refreshProject($proj, $user, true); - $this->assertTrue( - (bool)$proj, - pht( - 'Assumption that projects are default visible '. - 'to any user when created.')); - - $this->assertFalse( - $proj->isUserMember($user->getPHID()), - pht('Arbitrary user not member of project.')); - - // Join the project. - $this->joinProject($proj, $user); - - $proj = $this->refreshProject($proj, $user, true); - $this->assertTrue((bool)$proj); - - $this->assertTrue( - $proj->isUserMember($user->getPHID()), - pht('Join works.')); - - - // Join the project again. - $this->joinProject($proj, $user); - - $proj = $this->refreshProject($proj, $user, true); - $this->assertTrue((bool)$proj); - - $this->assertTrue( - $proj->isUserMember($user->getPHID()), - pht('Joining an already-joined project is a no-op.')); - - - // Leave the project. - $this->leaveProject($proj, $user); - - $proj = $this->refreshProject($proj, $user, true); - $this->assertTrue((bool)$proj); - - $this->assertFalse( - $proj->isUserMember($user->getPHID()), - pht('Leave works.')); - - - // Leave the project again. - $this->leaveProject($proj, $user); - - $proj = $this->refreshProject($proj, $user, true); - $this->assertTrue((bool)$proj); - - $this->assertFalse( - $proj->isUserMember($user->getPHID()), - pht('Leaving an already-left project is a no-op.')); - - - // If a user can't edit or join a project, joining fails. - $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE); - $proj->setJoinPolicy(PhabricatorPolicies::POLICY_NOONE); - $proj->save(); - - $proj = $this->refreshProject($proj, $user, true); - $caught = null; - try { - $this->joinProject($proj, $user); - } catch (Exception $ex) { - $caught = $ex; - } - $this->assertTrue($ex instanceof Exception); - - - // If a user can edit a project, they can join. - $proj->setEditPolicy(PhabricatorPolicies::POLICY_USER); - $proj->setJoinPolicy(PhabricatorPolicies::POLICY_NOONE); - $proj->save(); - - $proj = $this->refreshProject($proj, $user, true); - $this->joinProject($proj, $user); - $proj = $this->refreshProject($proj, $user, true); - $this->assertTrue( - $proj->isUserMember($user->getPHID()), - pht('Join allowed with edit permission.')); - $this->leaveProject($proj, $user); - - - // If a user can join a project, they can join, even if they can't edit. - $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE); - $proj->setJoinPolicy(PhabricatorPolicies::POLICY_USER); - $proj->save(); - - $proj = $this->refreshProject($proj, $user, true); - $this->joinProject($proj, $user); - $proj = $this->refreshProject($proj, $user, true); - $this->assertTrue( - $proj->isUserMember($user->getPHID()), - pht('Join allowed with join permission.')); - - - // A user can leave a project even if they can't edit it or join. - $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE); - $proj->setJoinPolicy(PhabricatorPolicies::POLICY_NOONE); - $proj->save(); - - $proj = $this->refreshProject($proj, $user, true); - $this->leaveProject($proj, $user); - $proj = $this->refreshProject($proj, $user, true); - $this->assertFalse( - $proj->isUserMember($user->getPHID()), - pht('Leave allowed without any permission.')); - } - - private function refreshProject( - PhabricatorProject $project, - PhabricatorUser $viewer, - $need_members = false) { - - $results = id(new PhabricatorProjectQuery()) - ->setViewer($viewer) - ->needMembers($need_members) - ->withIDs(array($project->getID())) - ->execute(); - - if ($results) { - return head($results); - } else { - return null; - } - } - - private function createProject(PhabricatorUser $user) { - $project = PhabricatorProject::initializeNewProject($user); - $project->setName(pht('Test Project %d', mt_rand())); - $project->save(); - - return $project; - } - - private function createProjectWithNewAuthor() { - $author = $this->createUser(); - $author->save(); - - $project = $this->createProject($author); - - return $project; - } - - private function createUser() { - $rand = mt_rand(); - - $user = new PhabricatorUser(); - $user->setUsername('unittestuser'.$rand); - $user->setRealName(pht('Unit Test User %d', $rand)); - - return $user; - } - - private function joinProject( - PhabricatorProject $project, - PhabricatorUser $user) { - $this->joinOrLeaveProject($project, $user, '+'); - } - - private function leaveProject( - PhabricatorProject $project, - PhabricatorUser $user) { - $this->joinOrLeaveProject($project, $user, '-'); - } - - private function joinOrLeaveProject( - PhabricatorProject $project, - PhabricatorUser $user, - $operation) { - - $spec = array( - $operation => array($user->getPHID() => $user->getPHID()), - ); - - $xactions = array(); - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue( - 'edge:type', - PhabricatorProjectProjectHasMemberEdgeType::EDGECONST) - ->setNewValue($spec); - - $editor = id(new PhabricatorProjectTransactionEditor()) - ->setActor($user) - ->setContentSource(PhabricatorContentSource::newConsoleSource()) - ->setContinueOnNoEffect(true) - ->applyTransactions($project, $xactions); - } - -} diff --git a/src/applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php b/src/applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php new file mode 100644 index 0000000000..857783f811 --- /dev/null +++ b/src/applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php @@ -0,0 +1,37 @@ +getPHID(), + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); + + if (!$project_phids) { + return; + } + + foreach ($project_phids as $project_phid) { + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_PROJECT, + $project_phid, + PhabricatorProjectProjectPHIDType::TYPECONST, + $document->getDocumentModified()); // Bogus timestamp. + } + } + +} diff --git a/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php b/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php new file mode 100644 index 0000000000..e1460381ed --- /dev/null +++ b/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php @@ -0,0 +1,98 @@ +rematerialize($object); + } + + public function rematerialize(PhabricatorProject $project) { + $materialize = $project->getAncestorProjects(); + array_unshift($materialize, $project); + + foreach ($materialize as $project) { + $this->materializeProject($project); + } + } + + private function materializeProject(PhabricatorProject $project) { + if ($project->isMilestone()) { + return; + } + + $material_type = PhabricatorProjectMaterializedMemberEdgeType::EDGECONST; + $member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; + + $project_phid = $project->getPHID(); + + $descendants = id(new PhabricatorProjectQuery()) + ->setViewer($this->getViewer()) + ->withAncestorProjectPHIDs(array($project->getPHID())) + ->withIsMilestone(false) + ->withHasSubprojects(false) + ->execute(); + $descendant_phids = mpull($descendants, 'getPHID'); + + if ($descendant_phids) { + $source_phids = $descendant_phids; + $has_subprojects = true; + } else { + $source_phids = array($project->getPHID()); + $has_subprojects = false; + } + + $conn_w = $project->establishConnection('w'); + + $project->openTransaction(); + + // Delete any existing materialized member edges. + queryfx( + $conn_w, + 'DELETE FROM %T WHERE src = %s AND type = %s', + PhabricatorEdgeConfig::TABLE_NAME_EDGE, + $project_phid, + $material_type); + + // Copy current member edges to create new materialized edges. + queryfx( + $conn_w, + 'INSERT IGNORE INTO %T (src, type, dst, dateCreated, seq) + SELECT %s, %d, dst, dateCreated, seq FROM %T + WHERE src IN (%Ls) AND type = %d', + PhabricatorEdgeConfig::TABLE_NAME_EDGE, + $project_phid, + $material_type, + PhabricatorEdgeConfig::TABLE_NAME_EDGE, + $source_phids, + $member_type); + + // Update the hasSubprojects flag. + queryfx( + $conn_w, + 'UPDATE %T SET hasSubprojects = %d WHERE id = %d', + $project->getTableName(), + (int)$has_subprojects, + $project->getID()); + + $project->saveTransaction(); + } + +} diff --git a/src/applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php b/src/applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php new file mode 100644 index 0000000000..5e78a7d2c0 --- /dev/null +++ b/src/applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php @@ -0,0 +1,75 @@ + array( + '[project]', + '[project] [tion]', + '[action] [project]', + '[action] [project] [tion]', + ), + 'project' => array( + 'Backend', + 'Frontend', + 'Web', + 'Mobile', + 'Tablet', + 'Robot', + 'NUX', + 'Cars', + 'Drones', + 'Experience', + 'Swag', + 'Security', + 'Culture', + 'Revenue', + 'Ion Cannon', + 'Graphics Engine', + 'Drivers', + 'Audio Drivers', + 'Graphics Drivers', + 'Hardware', + 'Data Center', + '[project] [project]', + '[adjective] [project]', + '[adjective] [project]', + ), + 'adjective' => array( + 'Self-Driving', + 'Self-Flying', + 'Self-Immolating', + 'Secure', + 'Insecure', + 'Somewhat-Secure', + 'Orbital', + 'Next-Generation', + ), + 'tion' => array( + 'Automation', + 'Optimization', + 'Performance', + 'Improvement', + 'Growth', + 'Monetization', + ), + 'action' => array( + 'Monetize', + 'Monetize', + 'Triage', + 'Triaging', + 'Automate', + 'Automating', + 'Improve', + 'Improving', + 'Optimize', + 'Optimizing', + 'Accelerate', + 'Accelerating', + ), + ); + } + +} diff --git a/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php b/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php index 0462bdd790..1962a43f5d 100644 --- a/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php +++ b/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php @@ -3,75 +3,76 @@ final class PhabricatorProjectTestDataGenerator extends PhabricatorTestDataGenerator { - private $xactions = array(); + public function getGeneratorName() { + return pht('Projects'); + } - public function generate() { - $title = $this->generateTitle(); - $author = $this->loadPhabrictorUser(); - $author_phid = $author->getPHID(); - $project = PhabricatorProject::initializeNewProject($author) - ->setName($title); + public function generateObject() { + $author = $this->loadRandomUser(); + $project = PhabricatorProject::initializeNewProject($author); - $this->addTransaction( + $xactions = array(); + + $xactions[] = $this->newTransaction( PhabricatorProjectTransaction::TYPE_NAME, - $title); - $project->attachMemberPHIDs( - $this->loadMembersWithAuthor($author_phid)); - $this->addTransaction( + $this->newProjectTitle()); + + $xactions[] = $this->newTransaction( PhabricatorProjectTransaction::TYPE_STATUS, - $this->generateProjectStatus()); - $this->addTransaction( - PhabricatorTransactions::TYPE_VIEW_POLICY, - PhabricatorPolicies::POLICY_PUBLIC); - $this->addTransaction( - PhabricatorTransactions::TYPE_EDIT_POLICY, - PhabricatorPolicies::POLICY_PUBLIC); - $this->addTransaction( - PhabricatorTransactions::TYPE_JOIN_POLICY, - PhabricatorPolicies::POLICY_PUBLIC); + $this->newProjectStatus()); + + // Almost always make the author a member. + $members = array(); + if ($this->roll(1, 20) > 2) { + $members[] = $author->getPHID(); + } + + // Add a few other members. + $size = $this->roll(2, 6, -2); + for ($ii = 0; $ii < $size; $ii++) { + $members[] = $this->loadRandomUser()->getPHID(); + } + + $xactions[] = $this->newTransaction( + PhabricatorTransactions::TYPE_EDGE, + array( + '+' => array_fuse($members), + ), + array( + 'edge:type' => PhabricatorProjectProjectHasMemberEdgeType::EDGECONST, + )); $editor = id(new PhabricatorProjectTransactionEditor()) ->setActor($author) - ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContentSource($this->getLipsumContentSource()) ->setContinueOnNoEffect(true) - ->applyTransactions($project, $this->xactions); + ->applyTransactions($project, $xactions); - return $project->save(); + return $project; } - private function addTransaction($type, $value) { - $this->xactions[] = id(new PhabricatorProjectTransaction()) + private function newTransaction($type, $value, $metadata = array()) { + $xaction = id(new PhabricatorProjectTransaction()) ->setTransactionType($type) ->setNewValue($value); - } - - public function loadMembersWithAuthor($author) { - $members = array($author); - for ($i = 0; $i < rand(10, 20);$i++) { - $members[] = $this->loadPhabrictorUserPHID(); + foreach ($metadata as $key => $value) { + $xaction->setMetadataValue($key, $value); } - return $members; + + return $xaction; } - public function generateTitle() { - return id(new PhutilLipsumContextFreeGrammar()) + public function newProjectTitle() { + return id(new PhabricatorProjectNameContextFreeGrammar()) ->generate(); } - public function generateDescription() { - return id(new PhutilLipsumContextFreeGrammar()) - ->generateSeveral(rand(30, 40)); - } - - public function generateProjectStatus() { - $statuses = array_keys(PhabricatorProjectStatus::getStatusMap()); - // Make sure 4/5th of all generated Projects are active - $random = rand(0, 4); - if ($random != 0) { - return $statuses[0]; + public function newProjectStatus() { + if ($this->roll(1, 20) > 5) { + return PhabricatorProjectStatus::STATUS_ACTIVE; } else { - return $statuses[1]; + return PhabricatorProjectStatus::STATUS_ARCHIVED; } } } diff --git a/src/applications/project/policyrule/PhabricatorProjectMembersPolicyRule.php b/src/applications/project/policyrule/PhabricatorProjectMembersPolicyRule.php new file mode 100644 index 0000000000..5fa57a31da --- /dev/null +++ b/src/applications/project/policyrule/PhabricatorProjectMembersPolicyRule.php @@ -0,0 +1,97 @@ +getPHID(); + if (!$viewer_phid) { + return; + } + + if (empty($this->memberships[$viewer_phid])) { + $this->memberships[$viewer_phid] = array(); + } + + foreach ($objects as $key => $object) { + $cache = $this->getTransactionHint($object); + if ($cache === null) { + continue; + } + + unset($objects[$key]); + + if (isset($cache[$viewer_phid])) { + $this->memberships[$viewer_phid][$object->getPHID()] = true; + } + } + + if (!$objects) { + return; + } + + $object_phids = mpull($objects, 'getPHID'); + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(array($viewer_phid)) + ->withDestinationPHIDs($object_phids) + ->withEdgeTypes( + array( + PhabricatorProjectMemberOfProjectEdgeType::EDGECONST, + )); + $edge_query->execute(); + + $memberships = $edge_query->getDestinationPHIDs(); + if (!$memberships) { + return; + } + + $this->memberships[$viewer_phid] += array_fill_keys($memberships, true); + } + + public function applyRule( + PhabricatorUser $viewer, + $value, + PhabricatorPolicyInterface $object) { + $viewer_phid = $viewer->getPHID(); + if (!$viewer_phid) { + return false; + } + + $memberships = idx($this->memberships, $viewer_phid); + return isset($memberships[$object->getPHID()]); + } + + public function getValueControlType() { + return self::CONTROL_TYPE_NONE; + } + + public function canApplyToObject(PhabricatorPolicyInterface $object) { + return ($object instanceof PhabricatorProject); + } + + public function getObjectPolicyKey() { + return 'project.members'; + } + + public function getObjectPolicyName() { + return pht('Project Members'); + } + + public function getObjectPolicyIcon() { + return 'fa-users'; + } + + public function getPolicyExplanation() { + return pht('Project members can take this action.'); + } + +} diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index e534e1c4bf..c975150fbc 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -11,6 +11,12 @@ final class PhabricatorProjectQuery private $nameTokens; private $icons; private $colors; + private $ancestorPHIDs; + private $parentPHIDs; + private $isMilestone; + private $hasSubprojects; + private $minDepth; + private $maxDepth; private $status = 'status-any'; const STATUS_ANY = 'status-any'; @@ -69,6 +75,32 @@ final class PhabricatorProjectQuery return $this; } + public function withParentProjectPHIDs($parent_phids) { + $this->parentPHIDs = $parent_phids; + return $this; + } + + public function withAncestorProjectPHIDs($ancestor_phids) { + $this->ancestorPHIDs = $ancestor_phids; + return $this; + } + + public function withIsMilestone($is_milestone) { + $this->isMilestone = $is_milestone; + return $this; + } + + public function withHasSubprojects($has_subprojects) { + $this->hasSubprojects = $has_subprojects; + return $this; + } + + public function withDepthBetween($min, $max) { + $this->minDepth = $min; + $this->maxDepth = $max; + return $this; + } + public function needMembers($need_members) { $this->needMembers = $need_members; return $this; @@ -126,60 +158,99 @@ final class PhabricatorProjectQuery } protected function loadPage() { - $table = new PhabricatorProject(); - $data = $this->loadStandardPageRows($table); - $projects = $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); + } - if ($projects) { - $viewer_phid = $this->getViewer()->getPHID(); - $project_phids = mpull($projects, 'getPHID'); + protected function willFilterPage(array $projects) { + $ancestor_paths = array(); + foreach ($projects as $project) { + foreach ($project->getAncestorProjectPaths() as $path) { + $ancestor_paths[$path] = $path; + } + } - $member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; - $watcher_type = PhabricatorObjectHasWatcherEdgeType::EDGECONST; + if ($ancestor_paths) { + $ancestors = id(new PhabricatorProject())->loadAllWhere( + 'projectPath IN (%Ls)', + $ancestor_paths); + } else { + $ancestors = array(); + } - $need_edge_types = array(); - if ($this->needMembers) { - $need_edge_types[] = $member_type; + $projects = $this->linkProjectGraph($projects, $ancestors); + + $viewer_phid = $this->getViewer()->getPHID(); + + $member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; + $watcher_type = PhabricatorObjectHasWatcherEdgeType::EDGECONST; + + $types = array(); + $types[] = $member_type; + if ($this->needWatchers) { + $types[] = $watcher_type; + } + + $all_sources = array(); + foreach ($projects as $project) { + if ($project->isMilestone()) { + $phid = $project->getParentProjectPHID(); } else { - foreach ($data as $row) { - $projects[$row['id']]->setIsUserMember( - $viewer_phid, - ($row['viewerIsMember'] !== null)); - } + $phid = $project->getPHID(); + } + $all_sources[$phid] = $phid; + } + + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs($all_sources) + ->withEdgeTypes($types); + + // If we only need to know if the viewer is a member, we can restrict + // the query to just their PHID. + if (!$this->needMembers && !$this->needWatchers) { + $edge_query->withDestinationPHIDs(array($viewer_phid)); + } + + $edge_query->execute(); + + $membership_projects = array(); + foreach ($projects as $project) { + $project_phid = $project->getPHID(); + + if ($project->isMilestone()) { + $source_phids = array($project->getParentProjectPHID()); + } else { + $source_phids = array($project_phid); + } + + $member_phids = $edge_query->getDestinationPHIDs( + $source_phids, + array($member_type)); + + if (in_array($viewer_phid, $member_phids)) { + $membership_projects[$project_phid] = $project; + } + + if ($this->needMembers) { + $project->attachMemberPHIDs($member_phids); } if ($this->needWatchers) { - $need_edge_types[] = $watcher_type; + $watcher_phids = $edge_query->getDestinationPHIDs( + $source_phids, + array($watcher_type)); + $project->attachWatcherPHIDs($watcher_phids); + $project->setIsUserWatcher( + $viewer_phid, + in_array($viewer_phid, $watcher_phids)); } + } - if ($need_edge_types) { - $edges = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs($project_phids) - ->withEdgeTypes($need_edge_types) - ->execute(); + $all_graph = $this->getAllReachableAncestors($projects); + $member_graph = $this->getAllReachableAncestors($membership_projects); - if ($this->needMembers) { - foreach ($projects as $project) { - $phid = $project->getPHID(); - $project->attachMemberPHIDs( - array_keys($edges[$phid][$member_type])); - $project->setIsUserMember( - $viewer_phid, - isset($edges[$phid][$member_type][$viewer_phid])); - } - } - - if ($this->needWatchers) { - foreach ($projects as $project) { - $phid = $project->getPHID(); - $project->attachWatcherPHIDs( - array_keys($edges[$phid][$watcher_type])); - $project->setIsUserWatcher( - $viewer_phid, - isset($edges[$phid][$watcher_type][$viewer_phid])); - } - } - } + foreach ($all_graph as $phid => $project) { + $is_member = isset($member_graph[$phid]); + $project->setIsUserMember($viewer_phid, $is_member); } return $projects; @@ -231,20 +302,6 @@ final class PhabricatorProjectQuery return $projects; } - protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) { - $select = parent::buildSelectClauseParts($conn); - - // NOTE: Because visibility checks for projects depend on whether or not - // the user is a project member, we always load their membership. If we're - // loading all members anyway we can piggyback on that; otherwise we - // do an explicit join. - if (!$this->needMembers) { - $select[] = 'vm.dst viewerIsMember'; - } - - return $select; - } - protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); @@ -323,6 +380,72 @@ final class PhabricatorProjectQuery $this->colors); } + if ($this->parentPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'parentProjectPHID IN (%Ls)', + $this->parentPHIDs); + } + + if ($this->ancestorPHIDs !== null) { + $ancestor_paths = queryfx_all( + $conn, + 'SELECT projectPath, projectDepth FROM %T WHERE phid IN (%Ls)', + id(new PhabricatorProject())->getTableName(), + $this->ancestorPHIDs); + if (!$ancestor_paths) { + throw new PhabricatorEmptyQueryException(); + } + + $sql = array(); + foreach ($ancestor_paths as $ancestor_path) { + $sql[] = qsprintf( + $conn, + '(projectPath LIKE %> AND projectDepth > %d)', + $ancestor_path['projectPath'], + $ancestor_path['projectDepth']); + } + + $where[] = '('.implode(' OR ', $sql).')'; + + $where[] = qsprintf( + $conn, + 'parentProjectPHID IS NOT NULL'); + } + + if ($this->isMilestone !== null) { + if ($this->isMilestone) { + $where[] = qsprintf( + $conn, + 'milestoneNumber IS NOT NULL'); + } else { + $where[] = qsprintf( + $conn, + 'milestoneNumber IS NULL'); + } + } + + if ($this->hasSubprojects !== null) { + $where[] = qsprintf( + $conn, + 'hasSubprojects = %d', + (int)$this->hasSubprojects); + } + + if ($this->minDepth !== null) { + $where[] = qsprintf( + $conn, + 'projectDepth >= %d', + $this->minDepth); + } + + if ($this->maxDepth !== null) { + $where[] = qsprintf( + $conn, + 'projectDepth <= %d', + $this->maxDepth); + } + return $where; } @@ -336,15 +459,6 @@ final class PhabricatorProjectQuery protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { $joins = parent::buildJoinClauseParts($conn); - if (!$this->needMembers !== null) { - $joins[] = qsprintf( - $conn, - 'LEFT JOIN %T vm ON vm.src = p.phid AND vm.type = %d AND vm.dst = %s', - PhabricatorEdgeConfig::TABLE_NAME_EDGE, - PhabricatorProjectProjectHasMemberEdgeType::EDGECONST, - $this->getViewer()->getPHID()); - } - if ($this->memberPHIDs !== null) { $joins[] = qsprintf( $conn, @@ -385,4 +499,88 @@ final class PhabricatorProjectQuery return 'p'; } + private function linkProjectGraph(array $projects, array $ancestors) { + $ancestor_map = mpull($ancestors, null, 'getPHID'); + $projects_map = mpull($projects, null, 'getPHID'); + + $all_map = $projects_map + $ancestor_map; + + $done = array(); + foreach ($projects as $key => $project) { + $seen = array($project->getPHID() => true); + + if (!$this->linkProject($project, $all_map, $done, $seen)) { + $this->didRejectResult($project); + unset($projects[$key]); + continue; + } + + foreach ($project->getAncestorProjects() as $ancestor) { + $seen[$ancestor->getPHID()] = true; + } + } + + return $projects; + } + + private function linkProject($project, array $all, array $done, array $seen) { + $parent_phid = $project->getParentProjectPHID(); + + // This project has no parent, so just attach `null` and return. + if (!$parent_phid) { + $project->attachParentProject(null); + return true; + } + + // This project has a parent, but it failed to load. + if (empty($all[$parent_phid])) { + return false; + } + + // Test for graph cycles. If we encounter one, we're going to hide the + // entire cycle since we can't meaningfully resolve it. + if (isset($seen[$parent_phid])) { + return false; + } + + $seen[$parent_phid] = true; + + $parent = $all[$parent_phid]; + $project->attachParentProject($parent); + + if (!empty($done[$parent_phid])) { + return true; + } + + return $this->linkProject($parent, $all, $done, $seen); + } + + private function getAllReachableAncestors(array $projects) { + $ancestors = array(); + + $seen = mpull($projects, null, 'getPHID'); + + $stack = $projects; + while ($stack) { + $project = array_pop($stack); + + $phid = $project->getPHID(); + $ancestors[$phid] = $project; + + $parent_phid = $project->getParentProjectPHID(); + if (!$parent_phid) { + continue; + } + + if (isset($seen[$parent_phid])) { + continue; + } + + $seen[$parent_phid] = true; + $stack[] = $project->getParentProject(); + } + + return $ancestors; + } + } diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 9b411030c4..6943f02866 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -203,4 +203,24 @@ protected function buildQueryFromParameters(array $map) { } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Project')) + ->setHref('/project/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Projects are flexible storage containers used as '. + 'tags, teams, projects, or anything you need to group.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/project/search/PhabricatorProjectFulltextEngine.php b/src/applications/project/search/PhabricatorProjectFulltextEngine.php new file mode 100644 index 0000000000..f0940286e5 --- /dev/null +++ b/src/applications/project/search/PhabricatorProjectFulltextEngine.php @@ -0,0 +1,24 @@ +updateDatasourceTokens(); + + $document->setDocumentTitle($project->getName()); + + $document->addRelationship( + $project->isArchived() + ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED + : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, + $project->getPHID(), + PhabricatorProjectProjectPHIDType::TYPECONST, + PhabricatorTime::getNow()); + } + +} diff --git a/src/applications/project/search/PhabricatorProjectSearchIndexer.php b/src/applications/project/search/PhabricatorProjectSearchIndexer.php deleted file mode 100644 index a4fb7eaf87..0000000000 --- a/src/applications/project/search/PhabricatorProjectSearchIndexer.php +++ /dev/null @@ -1,35 +0,0 @@ -loadDocumentByPHID($phid); - $project->updateDatasourceTokens(); - - $doc = new PhabricatorSearchAbstractDocument(); - $doc->setPHID($project->getPHID()); - $doc->setDocumentType(PhabricatorProjectProjectPHIDType::TYPECONST); - $doc->setDocumentTitle($project->getName()); - $doc->setDocumentCreated($project->getDateCreated()); - $doc->setDocumentModified($project->getDateModified()); - - $doc->addRelationship( - $project->isArchived() - ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED - : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, - $project->getPHID(), - PhabricatorProjectProjectPHIDType::TYPECONST, - time()); - - // NOTE: This could be more full-featured, but for now we're mostly - // interested in the side effects of indexing. - - return $doc; - } - -} diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index 8bbc6e14ad..223333809f 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -5,15 +5,16 @@ final class PhabricatorProject extends PhabricatorProjectDAO PhabricatorApplicationTransactionInterface, PhabricatorFlaggableInterface, PhabricatorPolicyInterface, + PhabricatorExtendedPolicyInterface, PhabricatorSubscribableInterface, PhabricatorCustomFieldInterface, - PhabricatorDestructibleInterface { + PhabricatorDestructibleInterface, + PhabricatorFulltextInterface { protected $name; protected $status = PhabricatorProjectStatus::STATUS_ACTIVE; protected $authorPHID; - protected $subprojectPHIDs = array(); - protected $phrictionSlug; + protected $primarySlug; protected $profileImagePHID; protected $icon; protected $color; @@ -24,6 +25,16 @@ final class PhabricatorProject extends PhabricatorProjectDAO protected $joinPolicy; protected $isMembershipLocked; + protected $parentProjectPHID; + protected $hasWorkboard; + protected $hasMilestones; + protected $hasSubprojects; + protected $milestoneNumber; + + protected $projectPath; + protected $projectDepth; + protected $projectPathKey; + private $memberPHIDs = self::ATTACHABLE; private $watcherPHIDs = self::ATTACHABLE; private $sparseWatchers = self::ATTACHABLE; @@ -31,6 +42,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO private $customFields = self::ATTACHABLE; private $profileImageFile = self::ATTACHABLE; private $slugs = self::ATTACHABLE; + private $parentProject = self::ATTACHABLE; const DEFAULT_ICON = 'fa-briefcase'; const DEFAULT_COLOR = 'blue'; @@ -59,7 +71,11 @@ final class PhabricatorProject extends PhabricatorProjectDAO ->setJoinPolicy($join_policy) ->setIsMembershipLocked(0) ->attachMemberPHIDs(array()) - ->attachSlugs(array()); + ->attachSlugs(array()) + ->setHasWorkboard(0) + ->setHasMilestones(0) + ->setHasSubprojects(0) + ->attachParentProject(null); } public function getCapabilities() { @@ -82,6 +98,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + $can_edit = PhabricatorPolicyCapability::CAN_EDIT; switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: @@ -91,9 +108,18 @@ final class PhabricatorProject extends PhabricatorProjectDAO } break; case PhabricatorPolicyCapability::CAN_EDIT: + $parent = $this->getParentProject(); + if ($parent) { + $can_edit_parent = PhabricatorPolicyFilter::hasCapability( + $viewer, + $parent, + $can_edit); + if ($can_edit_parent) { + return true; + } + } break; case PhabricatorPolicyCapability::CAN_JOIN: - $can_edit = PhabricatorPolicyCapability::CAN_EDIT; if (PhabricatorPolicyFilter::hasCapability($viewer, $this, $can_edit)) { // Project editors can always join a project. return true; @@ -105,6 +131,9 @@ final class PhabricatorProject extends PhabricatorProjectDAO } public function describeAutomaticCapability($capability) { + + // TODO: Clarify the additional rules that parent and subprojects imply. + switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return pht('Members of a project can always view it.'); @@ -114,6 +143,25 @@ final class PhabricatorProject extends PhabricatorProjectDAO return null; } + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + $extended = array(); + + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + $parent = $this->getParentProject(); + if ($parent) { + $extended[] = array( + $parent, + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + break; + } + + return $extended; + } + + public function isUserMember($user_phid) { if ($this->memberPHIDs !== self::ATTACHABLE) { return in_array($user_phid, $this->memberPHIDs); @@ -132,24 +180,24 @@ final class PhabricatorProject extends PhabricatorProjectDAO protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, - self::CONFIG_SERIALIZATION => array( - 'subprojectPHIDs' => self::SERIALIZATION_JSON, - ), self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'sort128', 'status' => 'text32', - 'phrictionSlug' => 'text128?', + 'primarySlug' => 'text128?', 'isMembershipLocked' => 'bool', 'profileImagePHID' => 'phid?', 'icon' => 'text32', 'color' => 'text32', 'mailKey' => 'bytes20', - - // T6203/NULLABILITY - // These are definitely wrong and should always exist. - 'editPolicy' => 'policy?', - 'viewPolicy' => 'policy?', - 'joinPolicy' => 'policy?', + 'joinPolicy' => 'policy', + 'parentProjectPHID' => 'phid?', + 'hasWorkboard' => 'bool', + 'hasMilestones' => 'bool', + 'hasSubprojects' => 'bool', + 'milestoneNumber' => 'uint32?', + 'projectPath' => 'hashpath64', + 'projectDepth' => 'uint32', + 'projectPathKey' => 'bytes4', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, @@ -163,14 +211,25 @@ final class PhabricatorProject extends PhabricatorProjectDAO 'key_color' => array( 'columns' => array('color'), ), - 'phrictionSlug' => array( - 'columns' => array('phrictionSlug'), - 'unique' => true, - ), 'name' => array( 'columns' => array('name'), 'unique' => true, ), + 'key_milestone' => array( + 'columns' => array('parentProjectPHID', 'milestoneNumber'), + 'unique' => true, + ), + 'key_primaryslug' => array( + 'columns' => array('primarySlug'), + 'unique' => true, + ), + 'key_path' => array( + 'columns' => array('projectPath', 'projectDepth'), + ), + 'key_pathkey' => array( + 'columns' => array('projectPathKey'), + 'unique' => true, + ), ), ) + parent::getConfiguration(); } @@ -189,19 +248,6 @@ final class PhabricatorProject extends PhabricatorProjectDAO return $this->assertAttached($this->memberPHIDs); } - public function setPrimarySlug($slug) { - $this->phrictionSlug = $slug.'/'; - return $this; - } - - // TODO - once we sever project => phriction automagicalness, - // migrate getPhrictionSlug to have no trailing slash and be called - // getPrimarySlug - public function getPrimarySlug() { - $slug = $this->getPhrictionSlug(); - return rtrim($slug, '/'); - } - public function isArchived() { return ($this->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED); } @@ -266,6 +312,34 @@ final class PhabricatorProject extends PhabricatorProjectDAO $this->setMailKey(Filesystem::readRandomCharacters(20)); } + if (!strlen($this->getPHID())) { + $this->setPHID($this->generatePHID()); + } + + if (!strlen($this->getProjectPathKey())) { + $hash = PhabricatorHash::digestForIndex($this->getPHID()); + $hash = substr($hash, 0, 4); + $this->setProjectPathKey($hash); + } + + $path = array(); + $depth = 0; + if ($this->parentProjectPHID) { + $parent = $this->getParentProject(); + $path[] = $parent->getProjectPath(); + $depth = $parent->getProjectDepth() + 1; + } + $path[] = $this->getProjectPathKey(); + $path = implode('', $path); + + $limit = self::getProjectDepthLimit(); + if ($depth >= $limit) { + throw new Exception(pht('Project depth is too great.')); + } + + $this->setProjectPath($path); + $this->setProjectDepth($depth); + $this->openTransaction(); $result = parent::save(); $this->updateDatasourceTokens(); @@ -274,6 +348,12 @@ final class PhabricatorProject extends PhabricatorProjectDAO return $result; } + public static function getProjectDepthLimit() { + // This is limited by how many path hashes we can fit in the path + // column. + return 16; + } + public function updateDatasourceTokens() { $table = self::TABLE_DATASOURCE_TOKEN; $conn_w = $this->establishConnection('w'); @@ -313,6 +393,44 @@ final class PhabricatorProject extends PhabricatorProjectDAO $this->saveTransaction(); } + public function isMilestone() { + return ($this->getMilestoneNumber() !== null); + } + + public function getParentProject() { + return $this->assertAttached($this->parentProject); + } + + public function attachParentProject(PhabricatorProject $project = null) { + $this->parentProject = $project; + return $this; + } + + public function getAncestorProjectPaths() { + $parts = array(); + + $path = $this->getProjectPath(); + $parent_length = (strlen($path) - 4); + + for ($ii = $parent_length; $ii > 0; $ii -= 4) { + $parts[] = substr($path, 0, $ii); + } + + return $parts; + } + + public function getAncestorProjects() { + $ancestors = array(); + + $cursor = $this->getParentProject(); + while ($cursor) { + $ancestors[] = $cursor; + $cursor = $cursor->getParentProject(); + } + + return $ancestors; + } + /* -( PhabricatorSubscribableInterface )----------------------------------- */ @@ -377,6 +495,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO /* -( PhabricatorDestructibleInterface )----------------------------------- */ + public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { @@ -398,4 +517,12 @@ final class PhabricatorProject extends PhabricatorProjectDAO $this->saveTransaction(); } + +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + + public function newFulltextEngine() { + return new PhabricatorProjectFulltextEngine(); + } + } diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index 6529c99d59..b928d43a91 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -10,6 +10,8 @@ final class PhabricatorProjectTransaction const TYPE_ICON = 'project:icon'; const TYPE_COLOR = 'project:color'; const TYPE_LOCKED = 'project:locked'; + const TYPE_PARENT = 'project:parent'; + const TYPE_MILESTONE = 'project:milestone'; // NOTE: This is deprecated, members are just a normal edge now. const TYPE_MEMBERS = 'project:members'; diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index 42ff0f6704..ded1c03735 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -251,4 +251,31 @@ final class PhabricatorRepositorySearchEngine $saved->setParameter('projectPHIDs', $project_phids); } + protected function getNewUserBody() { + + $import_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Import Repository')) + ->setHref('/diffusion/import/') + ->setColor(PHUIButtonView::GREEN); + + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create Repository')) + ->setHref('/diffusion/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Import, create, or just browse repositories in Diffusion.')) + ->addAction($import_button) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/repository/search/DiffusionCommitFulltextEngine.php b/src/applications/repository/search/DiffusionCommitFulltextEngine.php new file mode 100644 index 0000000000..51d2ead5fa --- /dev/null +++ b/src/applications/repository/search/DiffusionCommitFulltextEngine.php @@ -0,0 +1,49 @@ +setViewer($this->getViewer()) + ->withPHIDs(array($object->getPHID())) + ->needCommitData(true) + ->executeOne(); + + $repository = $commit->getRepository(); + $commit_data = $commit->getCommitData(); + + $date_created = $commit->getEpoch(); + $commit_message = $commit_data->getCommitMessage(); + $author_phid = $commit_data->getCommitDetail('authorPHID'); + + $title = 'r'.$repository->getCallsign().$commit->getCommitIdentifier(). + ' '.$commit_data->getSummary(); + + $document + ->setDocumentCreated($date_created) + ->setDocumentModified($date_created) + ->setDocumentTitle($title); + + $document->addField( + PhabricatorSearchDocumentFieldType::FIELD_BODY, + $commit_message); + + if ($author_phid) { + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, + $author_phid, + PhabricatorPeopleUserPHIDType::TYPECONST, + $date_created); + } + + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY, + $repository->getPHID(), + PhabricatorRepositoryRepositoryPHIDType::TYPECONST, + $date_created); + } +} diff --git a/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php b/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php deleted file mode 100644 index f94aa26b4e..0000000000 --- a/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php +++ /dev/null @@ -1,63 +0,0 @@ -loadDocumentByPHID($phid); - - $commit_data = id(new PhabricatorRepositoryCommitData())->loadOneWhere( - 'commitID = %d', - $commit->getID()); - $date_created = $commit->getEpoch(); - $commit_message = $commit_data->getCommitMessage(); - $author_phid = $commit_data->getCommitDetail('authorPHID'); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($this->getViewer()) - ->withIDs(array($commit->getRepositoryID())) - ->executeOne(); - if (!$repository) { - throw new Exception(pht('No such repository!')); - } - - $title = 'r'.$repository->getCallsign().$commit->getCommitIdentifier(). - ' '.$commit_data->getSummary(); - - $doc = new PhabricatorSearchAbstractDocument(); - $doc->setPHID($commit->getPHID()); - $doc->setDocumentType(PhabricatorRepositoryCommitPHIDType::TYPECONST); - $doc->setDocumentCreated($date_created); - $doc->setDocumentModified($date_created); - $doc->setDocumentTitle($title); - - $doc->addField( - PhabricatorSearchDocumentFieldType::FIELD_BODY, - $commit_message); - - if ($author_phid) { - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, - $author_phid, - PhabricatorPeopleUserPHIDType::TYPECONST, - $date_created); - } - - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY, - $repository->getPHID(), - PhabricatorRepositoryRepositoryPHIDType::TYPECONST, - $date_created); - - $this->indexTransactions( - $doc, - new PhabricatorAuditTransactionQuery(), - array($commit->getPHID())); - - return $doc; - } -} diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index 531bac9dad..da6d56b915 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -11,7 +11,8 @@ final class PhabricatorRepositoryCommit PhabricatorMentionableInterface, HarbormasterBuildableInterface, PhabricatorCustomFieldInterface, - PhabricatorApplicationTransactionInterface { + PhabricatorApplicationTransactionInterface, + PhabricatorFulltextInterface { protected $repositoryID; protected $phid; @@ -437,4 +438,11 @@ final class PhabricatorRepositoryCommit return $timeline->setPathMap($path_map); } +/* -( PhabricatorFulltextInterface )--------------------------------------- */ + + + public function newFulltextEngine() { + return new DiffusionCommitFulltextEngine(); + } + } diff --git a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php index 9fcaeb7254..6563c4442c 100644 --- a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php +++ b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php @@ -93,8 +93,7 @@ abstract class PhabricatorRepositoryCommitChangeParserWorker $commit->writeImportStatusFlag( PhabricatorRepositoryCommit::IMPORTED_CHANGE); - id(new PhabricatorSearchIndexer()) - ->queueDocumentForIndexing($commit->getPHID()); + PhabricatorSearchWorker::queueDocumentForIndexing($commit->getPHID()); if ($this->shouldQueueFollowupTasks()) { $this->queueTask( diff --git a/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php b/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php index b54a818398..15ecc3f391 100644 --- a/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php +++ b/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php @@ -16,8 +16,8 @@ final class PhabricatorSearchApplicationStorageEnginePanel $viewer = $this->getViewer(); $application = $this->getApplication(); - $active_engine = PhabricatorSearchEngine::loadEngine(); - $engines = PhabricatorSearchEngine::loadAllEngines(); + $active_engine = PhabricatorFulltextStorageEngine::loadEngine(); + $engines = PhabricatorFulltextStorageEngine::loadAllEngines(); $rows = array(); $rowc = array(); diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index bce361e02a..1619dfcbd8 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -111,11 +111,19 @@ // URIs like "/query/?users=a,b". $pt_data = $request->getPassthroughRequestData(); + $exempt = array( + 'before' => true, + 'after' => true, + 'nux' => true, + ); + foreach ($pt_data as $pt_key => $pt_value) { - if ($pt_key != 'before' && $pt_key != 'after') { - $found_query_data = true; - break; + if (isset($exempt[$pt_key])) { + continue; } + + $found_query_data = true; + break; } } @@ -188,6 +196,7 @@ $title = pht('Advanced Search'); } + $header = id(new PHUIHeaderView()) ->setHeader($title); @@ -207,12 +216,15 @@ $body[] = $box; + if ($run_query) { $box->setAnchor( id(new PhabricatorAnchorView()) ->setAnchorName('R')); try { + $engine->setRequest($request); + $query = $engine->buildQueryFromSavedQuery($saved_query); $pager = $engine->newPagerForSavedQuery($saved_query); @@ -220,54 +232,63 @@ $objects = $engine->executeQuery($query, $pager); - $engine->setRequest($request); - $list = $engine->renderResults($objects, $saved_query); - $description = $this->getDescriptionForQuery($named_query, $query); if ($description) { $header->setHeader($description); } - if (!($list instanceof PhabricatorApplicationSearchResultView)) { - throw new Exception( - pht( - 'SearchEngines must render a "%s" object, but this engine '. - '(of class "%s") rendered something else.', - 'PhabricatorApplicationSearchResultView', - get_class($engine))); + $force_nux = $request->getBool('nux'); + if (!$objects || $force_nux) { + $nux_view = $this->renderNewUserView($engine, $force_nux); + } else { + $nux_view = null; } - if ($list->getActions()) { - foreach ($list->getActions() as $action) { - $header->addActionLink($action); + if ($nux_view) { + $box->appendChild($nux_view); + } else { + $list = $engine->renderResults($objects, $saved_query); + + if (!($list instanceof PhabricatorApplicationSearchResultView)) { + throw new Exception( + pht( + 'SearchEngines must render a "%s" object, but this engine '. + '(of class "%s") rendered something else.', + 'PhabricatorApplicationSearchResultView', + get_class($engine))); + } + + if ($list->getActions()) { + foreach ($list->getActions() as $action) { + $header->addActionLink($action); + } + } + + if ($list->getObjectList()) { + $box->setObjectList($list->getObjectList()); + } + if ($list->getTable()) { + $box->setTable($list->getTable()); + } + if ($list->getInfoView()) { + $box->setInfoView($list->getInfoView()); + } + if ($list->getContent()) { + $box->appendChild($list->getContent()); + } + if ($list->getCollapsed()) { + $box->setCollapsed(true); + } + + if ($pager->willShowPagingControls()) { + $pager_box = id(new PHUIBoxView()) + ->addPadding(PHUI::PADDING_MEDIUM) + ->addMargin(PHUI::MARGIN_LARGE) + ->setBorder(true) + ->appendChild($pager); + $body[] = $pager_box; } } - - if ($list->getObjectList()) { - $box->setObjectList($list->getObjectList()); - } - if ($list->getTable()) { - $box->setTable($list->getTable()); - } - if ($list->getInfoView()) { - $box->setInfoView($list->getInfoView()); - } - if ($list->getContent()) { - $box->appendChild($list->getContent()); - } - if ($list->getCollapsed()) { - $box->setCollapsed(true); - } - - if ($pager->willShowPagingControls()) { - $pager_box = id(new PHUIBoxView()) - ->addPadding(PHUI::PADDING_MEDIUM) - ->addMargin(PHUI::MARGIN_LARGE) - ->setBorder(true) - ->appendChild($pager); - $body[] = $pager_box; - } - } catch (PhabricatorTypeaheadInvalidTokenException $ex) { $errors[] = pht( 'This query specifies an invalid parameter. Review the '. @@ -405,4 +426,47 @@ return $nav; } + private function renderNewUserView( + PhabricatorApplicationSearchEngine $engine, + $force_nux) { + + // Don't render NUX if the user has clicked away from the default page. + if (strlen($this->getQueryKey())) { + return null; + } + + // Don't put NUX in panels because it would be weird. + if ($engine->isPanelContext()) { + return null; + } + + // Try to render the view itself first, since this should be very cheap + // (just returning some text). + $nux_view = $engine->renderNewUserView(); + + if (!$nux_view) { + return null; + } + + $query = $engine->newQuery(); + if (!$query) { + return null; + } + + // Try to load any object at all. If we can, the application has seen some + // use so we just render the normal view. + if (!$force_nux) { + $object = $query + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setLimit(1) + ->execute(); + if ($object) { + return null; + } + } + + return $nux_view; + } + + } diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index 18ce2cfb92..df79a4e0a5 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -1354,4 +1354,22 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { return $attachments; } + final public function renderNewUserView() { + $body = $this->getNewUserBody(); + + if (!$body) { + return null; + } + + return $body; + } + + protected function getNewUserHeader() { + return null; + } + + protected function getNewUserBody() { + return null; + } + } diff --git a/src/applications/search/engine/__tests__/PhabricatorSearchEngineTestCase.php b/src/applications/search/engine/__tests__/PhabricatorSearchEngineTestCase.php index bf997288b6..b535d4f5cf 100644 --- a/src/applications/search/engine/__tests__/PhabricatorSearchEngineTestCase.php +++ b/src/applications/search/engine/__tests__/PhabricatorSearchEngineTestCase.php @@ -3,7 +3,7 @@ final class PhabricatorSearchEngineTestCase extends PhabricatorTestCase { public function testLoadAllEngines() { - PhabricatorSearchEngine::loadAllEngines(); + PhabricatorFulltextStorageEngine::loadAllEngines(); $this->assertTrue(true); } diff --git a/src/applications/search/engineextension/PhabricatorFulltextIndexEngineExtension.php b/src/applications/search/engineextension/PhabricatorFulltextIndexEngineExtension.php new file mode 100644 index 0000000000..0767849abe --- /dev/null +++ b/src/applications/search/engineextension/PhabricatorFulltextIndexEngineExtension.php @@ -0,0 +1,92 @@ +getTransactionVersion($object); + $version[] = $this->getCommentVersion($object); + } + + if (!$version) { + return null; + } + + return implode(':', $version); + } + + public function shouldIndexObject($object) { + return ($object instanceof PhabricatorFulltextInterface); + } + + public function indexObject( + PhabricatorIndexEngine $engine, + $object) { + + $engine = $object->newFulltextEngine(); + if (!$engine) { + return; + } + + $engine->setObject($object); + + $engine->buildFulltextIndexes(); + } + + private function getTransactionVersion($object) { + $xaction = $object->getApplicationTransactionTemplate(); + + $xaction_row = queryfx_one( + $xaction->establishConnection('r'), + 'SELECT id FROM %T WHERE objectPHID = %s + ORDER BY id DESC LIMIT 1', + $xaction->getTableName(), + $object->getPHID()); + if (!$xaction_row) { + return 'none'; + } + + return $xaction_row['id']; + } + + private function getCommentVersion($object) { + $xaction = $object->getApplicationTransactionTemplate(); + + try { + $comment = $xaction->getApplicationTransactionCommentObject(); + if (!$comment) { + return 'none'; + } + } catch (Exception $ex) { + return 'none'; + } + + $comment_row = queryfx_one( + $comment->establishConnection('r'), + 'SELECT c.id FROM %T x JOIN %T c + ON x.phid = c.transactionPHID + WHERE x.objectPHID = %s + ORDER BY c.id DESC LIMIT 1', + $xaction->getTableName(), + $comment->getTableName(), + $object->getPHID()); + if (!$comment_row) { + return 'none'; + } + + return $comment_row['id']; + } + + +} diff --git a/src/applications/search/engineextension/PhabricatorLiskFulltextEngineExtension.php b/src/applications/search/engineextension/PhabricatorLiskFulltextEngineExtension.php new file mode 100644 index 0000000000..9cbe384a10 --- /dev/null +++ b/src/applications/search/engineextension/PhabricatorLiskFulltextEngineExtension.php @@ -0,0 +1,34 @@ +getConfigOption(LiskDAO::CONFIG_TIMESTAMPS)) { + return false; + } + + return true; + } + + public function indexFulltextObject( + $object, + PhabricatorSearchAbstractDocument $document) { + + $document + ->setDocumentCreated($object->getDateCreated()) + ->setDocumentModified($object->getDateModified()); + + } + +} diff --git a/src/applications/search/engineextension/PhabricatorNgramsIndexEngineExtension.php b/src/applications/search/engineextension/PhabricatorNgramsIndexEngineExtension.php new file mode 100644 index 0000000000..a860d2e2db --- /dev/null +++ b/src/applications/search/engineextension/PhabricatorNgramsIndexEngineExtension.php @@ -0,0 +1,34 @@ +newNgrams(); + $map = mpull($ngrams, 'getValue', 'getNgramKey'); + ksort($map); + $serialized = serialize($map); + + return PhabricatorHash::digestForIndex($serialized); + } + + public function shouldIndexObject($object) { + return ($object instanceof PhabricatorNgramsInterface); + } + + public function indexObject( + PhabricatorIndexEngine $engine, + $object) { + + foreach ($object->newNgrams() as $ngram) { + $ngram->writeNgram($object->getID()); + } + } + +} diff --git a/src/applications/search/engineextension/PhabricatorSearchEngineExtensionModule.php b/src/applications/search/engineextension/PhabricatorSearchEngineExtensionModule.php index 12787437db..2dd2697a9d 100644 --- a/src/applications/search/engineextension/PhabricatorSearchEngineExtensionModule.php +++ b/src/applications/search/engineextension/PhabricatorSearchEngineExtensionModule.php @@ -8,7 +8,7 @@ final class PhabricatorSearchEngineExtensionModule } public function getModuleName() { - return pht('SearchEngine Extensions'); + return pht('Engine: Search'); } public function renderModuleStatus(AphrontRequest $request) { diff --git a/src/applications/search/engineextension/PhabricatorSearchIndexVersionDestructionEngineExtension.php b/src/applications/search/engineextension/PhabricatorSearchIndexVersionDestructionEngineExtension.php new file mode 100644 index 0000000000..83112dfd50 --- /dev/null +++ b/src/applications/search/engineextension/PhabricatorSearchIndexVersionDestructionEngineExtension.php @@ -0,0 +1,25 @@ +establishConnection('w'), + 'DELETE FROM %T WHERE objectPHID = %s', + $table->getTableName(), + $object->getPHID()); + } + +} diff --git a/src/applications/search/engineextension/PhabricatorSearchNgramsDestructionEngineExtension.php b/src/applications/search/engineextension/PhabricatorSearchNgramsDestructionEngineExtension.php new file mode 100644 index 0000000000..eb80e014e3 --- /dev/null +++ b/src/applications/search/engineextension/PhabricatorSearchNgramsDestructionEngineExtension.php @@ -0,0 +1,31 @@ +newNgrams() as $ngram) { + queryfx( + $ngram->establishConnection('w'), + 'DELETE FROM %T WHERE objectID = %d', + $ngram->getTableName(), + $object->getID()); + } + } + +} diff --git a/src/applications/search/engine/PhabricatorElasticSearchEngine.php b/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php similarity index 99% rename from src/applications/search/engine/PhabricatorElasticSearchEngine.php rename to src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php index fa6bdf8fee..ee067b942d 100644 --- a/src/applications/search/engine/PhabricatorElasticSearchEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php @@ -1,6 +1,7 @@ object = $object; + return $this; + } + + public function getObject() { + return $this->object; + } + + protected function getViewer() { + return PhabricatorUser::getOmnipotentUser(); + } + + abstract protected function buildAbstractDocument( + PhabricatorSearchAbstractDocument $document, + $object); + + final public function buildFulltextIndexes() { + $object = $this->getObject(); + + $extensions = PhabricatorFulltextEngineExtension::getAllExtensions(); + foreach ($extensions as $key => $extension) { + if (!$extension->shouldIndexFulltextObject($object)) { + unset($extensions[$key]); + } + } + + $document = $this->newAbstractDocument($object); + + $this->buildAbstractDocument($document, $object); + + foreach ($extensions as $extension) { + $extension->indexFulltextObject($object, $document); + } + + $storage_engine = PhabricatorFulltextStorageEngine::loadEngine(); + $storage_engine->reindexAbstractDocument($document); + } + + protected function newAbstractDocument($object) { + $phid = $object->getPHID(); + return id(new PhabricatorSearchAbstractDocument()) + ->setPHID($phid) + ->setDocumentType(phid_get_type($phid)); + } + +} diff --git a/src/applications/search/index/PhabricatorFulltextEngineExtension.php b/src/applications/search/index/PhabricatorFulltextEngineExtension.php new file mode 100644 index 0000000000..c52b35a58a --- /dev/null +++ b/src/applications/search/index/PhabricatorFulltextEngineExtension.php @@ -0,0 +1,28 @@ +getPhobjectClassConstant('EXTENSIONKEY'); + } + + final protected function getViewer() { + return PhabricatorUser::getOmnipotentUser(); + } + + abstract public function getExtensionName(); + + abstract public function shouldIndexFulltextObject($object); + + abstract public function indexFulltextObject( + $object, + PhabricatorSearchAbstractDocument $document); + + final public static function getAllExtensions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getExtensionKey') + ->execute(); + } + +} diff --git a/src/applications/search/index/PhabricatorFulltextEngineExtensionModule.php b/src/applications/search/index/PhabricatorFulltextEngineExtensionModule.php new file mode 100644 index 0000000000..7e42d6e5f5 --- /dev/null +++ b/src/applications/search/index/PhabricatorFulltextEngineExtensionModule.php @@ -0,0 +1,44 @@ +getViewer(); + + $extensions = PhabricatorFulltextEngineExtension::getAllExtensions(); + + $rows = array(); + foreach ($extensions as $extension) { + $rows[] = array( + get_class($extension), + $extension->getExtensionName(), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Class'), + pht('Name'), + )) + ->setColumnClasses( + array( + null, + 'wide pri', + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('FulltextEngine Extensions')) + ->setTable($table); + } + +} diff --git a/src/applications/search/index/PhabricatorIndexEngine.php b/src/applications/search/index/PhabricatorIndexEngine.php new file mode 100644 index 0000000000..1dde3ce9ab --- /dev/null +++ b/src/applications/search/index/PhabricatorIndexEngine.php @@ -0,0 +1,150 @@ +parameters = $parameters; + return $this; + } + + public function getParameters() { + return $this->parameters; + } + + public function setObject($object) { + $this->object = $object; + return $this; + } + + public function getObject() { + return $this->object; + } + + public function shouldIndexObject() { + $extensions = $this->newExtensions(); + + $parameters = $this->getParameters(); + foreach ($extensions as $extension) { + $extension->setParameters($parameters); + } + + $object = $this->getObject(); + $versions = array(); + foreach ($extensions as $key => $extension) { + $version = $extension->getIndexVersion($object); + if ($version !== null) { + $versions[$key] = (string)$version; + } + } + + if (idx($parameters, 'force')) { + $current_versions = array(); + } else { + $keys = array_keys($versions); + $current_versions = $this->loadIndexVersions($keys); + } + + foreach ($versions as $key => $version) { + $current_version = idx($current_versions, $key); + + if ($current_version === null) { + continue; + } + + // If nothing has changed since we built the current index, we do not + // need to rebuild the index. + if ($current_version === $version) { + unset($extensions[$key]); + } + } + + $this->extensions = $extensions; + $this->versions = $versions; + + // We should index the object only if there is any work to be done. + return (bool)$this->extensions; + } + + public function indexObject() { + $extensions = $this->extensions; + $object = $this->getObject(); + + foreach ($extensions as $key => $extension) { + $extension->indexObject($this, $object); + } + + $this->saveIndexVersions($this->versions); + + return $this; + } + + private function newExtensions() { + $object = $this->getObject(); + + $extensions = PhabricatorIndexEngineExtension::getAllExtensions(); + foreach ($extensions as $key => $extension) { + if (!$extension->shouldIndexObject($object)) { + unset($extensions[$key]); + } + } + + return $extensions; + } + + private function loadIndexVersions(array $extension_keys) { + if (!$extension_keys) { + return array(); + } + + $object = $this->getObject(); + $object_phid = $object->getPHID(); + + $table = new PhabricatorSearchIndexVersion(); + $conn_r = $table->establishConnection('w'); + + $rows = queryfx_all( + $conn_r, + 'SELECT * FROM %T WHERE objectPHID = %s AND extensionKey IN (%Ls)', + $table->getTableName(), + $object_phid, + $extension_keys); + + return ipull($rows, 'version', 'extensionKey'); + } + + private function saveIndexVersions(array $versions) { + if (!$versions) { + return; + } + + $object = $this->getObject(); + $object_phid = $object->getPHID(); + + $table = new PhabricatorSearchIndexVersion(); + $conn_w = $table->establishConnection('w'); + + $sql = array(); + foreach ($versions as $key => $version) { + $sql[] = qsprintf( + $conn_w, + '(%s, %s, %s)', + $object_phid, + $key, + $version); + } + + queryfx( + $conn_w, + 'INSERT INTO %T (objectPHID, extensionKey, version) + VALUES %Q + ON DUPLICATE KEY UPDATE version = VALUES(version)', + $table->getTableName(), + implode(', ', $sql)); + } + +} diff --git a/src/applications/search/index/PhabricatorIndexEngineExtension.php b/src/applications/search/index/PhabricatorIndexEngineExtension.php new file mode 100644 index 0000000000..aaf22d055b --- /dev/null +++ b/src/applications/search/index/PhabricatorIndexEngineExtension.php @@ -0,0 +1,48 @@ +parameters = $parameters; + return $this; + } + + public function getParameter($key, $default = null) { + return idx($this->parameters, $key, $default); + } + + final public function getExtensionKey() { + return $this->getPhobjectClassConstant('EXTENSIONKEY'); + } + + final protected function getViewer() { + return PhabricatorUser::getOmnipotentUser(); + } + + abstract public function getExtensionName(); + + abstract public function shouldIndexObject($object); + + abstract public function indexObject( + PhabricatorIndexEngine $engine, + $object); + + public function getIndexVersion($object) { + return null; + } + + final public static function getAllExtensions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getExtensionKey') + ->execute(); + } + + final public function shouldForceFullReindex() { + return $this->getParameter('force'); + } + +} diff --git a/src/applications/search/index/PhabricatorIndexEngineExtensionModule.php b/src/applications/search/index/PhabricatorIndexEngineExtensionModule.php new file mode 100644 index 0000000000..e586dc9e3c --- /dev/null +++ b/src/applications/search/index/PhabricatorIndexEngineExtensionModule.php @@ -0,0 +1,44 @@ +getViewer(); + + $extensions = PhabricatorIndexEngineExtension::getAllExtensions(); + + $rows = array(); + foreach ($extensions as $extension) { + $rows[] = array( + get_class($extension), + $extension->getExtensionName(), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Class'), + pht('Name'), + )) + ->setColumnClasses( + array( + null, + 'wide pri', + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('IndexEngine Extensions')) + ->setTable($table); + } + +} diff --git a/src/applications/search/index/PhabricatorSearchDocumentIndexer.php b/src/applications/search/index/PhabricatorSearchDocumentIndexer.php deleted file mode 100644 index a9ecc3e9c0..0000000000 --- a/src/applications/search/index/PhabricatorSearchDocumentIndexer.php +++ /dev/null @@ -1,181 +0,0 @@ -context = $context; - return $this; - } - - protected function getContext() { - return $this->context; - } - - abstract public function getIndexableObject(); - abstract protected function buildAbstractDocumentByPHID($phid); - - protected function getViewer() { - return PhabricatorUser::getOmnipotentUser(); - } - - public function shouldIndexDocumentByPHID($phid) { - $object = $this->getIndexableObject(); - return (phid_get_type($phid) == phid_get_type($object->generatePHID())); - } - - public function getIndexIterator() { - $object = $this->getIndexableObject(); - return new LiskMigrationIterator($object); - } - - protected function loadDocumentByPHID($phid) { - $object = id(new PhabricatorObjectQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs(array($phid)) - ->executeOne(); - if (!$object) { - throw new Exception(pht("Unable to load object by PHID '%s'!", $phid)); - } - return $object; - } - - public function indexDocumentByPHID($phid, $context) { - $this->setContext($context); - - $document = $this->buildAbstractDocumentByPHID($phid); - if ($document === null) { - // This indexer doesn't build a document index, so we're done. - return $this; - } - - $object = $this->loadDocumentByPHID($phid); - - // Automatically rebuild CustomField indexes if the object uses custom - // fields. - if ($object instanceof PhabricatorCustomFieldInterface) { - $this->indexCustomFields($document, $object); - } - - // Automatically rebuild subscriber indexes if the object is subscribable. - if ($object instanceof PhabricatorSubscribableInterface) { - $this->indexSubscribers($document); - } - - // Automatically build project relationships - if ($object instanceof PhabricatorProjectInterface) { - $this->indexProjects($document, $object); - } - - $engine = PhabricatorSearchEngine::loadEngine(); - $engine->reindexAbstractDocument($document); - - $this->dispatchDidUpdateIndexEvent($phid, $document); - - return $this; - } - - protected function newDocument($phid) { - return id(new PhabricatorSearchAbstractDocument()) - ->setPHID($phid) - ->setDocumentType(phid_get_type($phid)); - } - - protected function indexSubscribers( - PhabricatorSearchAbstractDocument $doc) { - - $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( - $doc->getPHID()); - $handles = id(new PhabricatorHandleQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs($subscribers) - ->execute(); - - foreach ($handles as $phid => $handle) { - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER, - $phid, - $handle->getType(), - $doc->getDocumentModified()); // Bogus timestamp. - } - } - - protected function indexProjects( - PhabricatorSearchAbstractDocument $doc, - PhabricatorProjectInterface $object) { - - $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( - $object->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - if ($project_phids) { - foreach ($project_phids as $project_phid) { - $doc->addRelationship( - PhabricatorSearchRelationship::RELATIONSHIP_PROJECT, - $project_phid, - PhabricatorProjectProjectPHIDType::TYPECONST, - $doc->getDocumentModified()); // Bogus timestamp. - } - } - } - - protected function indexTransactions( - PhabricatorSearchAbstractDocument $doc, - PhabricatorApplicationTransactionQuery $query, - array $phids) { - - $xactions = id(clone $query) - ->setViewer($this->getViewer()) - ->withObjectPHIDs($phids) - ->execute(); - - foreach ($xactions as $xaction) { - if (!$xaction->hasComment()) { - continue; - } - - $comment = $xaction->getComment(); - $doc->addField( - PhabricatorSearchDocumentFieldType::FIELD_COMMENT, - $comment->getContent()); - } - } - - protected function indexCustomFields( - PhabricatorSearchAbstractDocument $document, - PhabricatorCustomFieldInterface $object) { - - // Rebuild the ApplicationSearch indexes. These are internal and not part of - // the fulltext search, but putting them in this workflow allows users to - // use the same tools to rebuild the indexes, which is easy to understand. - - $field_list = PhabricatorCustomField::getObjectFields( - $object, - PhabricatorCustomField::ROLE_DEFAULT); - - $field_list->setViewer($this->getViewer()); - $field_list->readFieldsFromStorage($object); - - // Rebuild ApplicationSearch indexes. - $field_list->rebuildIndexes($object); - - // Rebuild global search indexes. - $field_list->updateAbstractDocument($document); - } - - private function dispatchDidUpdateIndexEvent( - $phid, - PhabricatorSearchAbstractDocument $document) { - - $event = new PhabricatorEvent( - PhabricatorEventType::TYPE_SEARCH_DIDUPDATEINDEX, - array( - 'phid' => $phid, - 'object' => $this->loadDocumentByPHID($phid), - 'document' => $document, - )); - $event->setUser($this->getViewer()); - PhutilEventEngine::dispatchEvent($event); - } - -} diff --git a/src/applications/search/index/PhabricatorSearchIndexer.php b/src/applications/search/index/PhabricatorSearchIndexer.php deleted file mode 100644 index 8a37b0f1e2..0000000000 --- a/src/applications/search/index/PhabricatorSearchIndexer.php +++ /dev/null @@ -1,32 +0,0 @@ - $phid, - 'context' => $context, - ), - array( - 'priority' => PhabricatorWorker::PRIORITY_IMPORT, - )); - } - - public function indexDocumentByPHID($phid, $context) { - $indexers = id(new PhutilClassMapQuery()) - ->setAncestorClass('PhabricatorSearchDocumentIndexer') - ->execute(); - - foreach ($indexers as $indexer) { - if ($indexer->shouldIndexDocumentByPHID($phid)) { - $indexer->indexDocumentByPHID($phid, $context); - break; - } - } - - return $this; - } - -} diff --git a/src/applications/search/interface/PhabricatorFulltextInterface.php b/src/applications/search/interface/PhabricatorFulltextInterface.php new file mode 100644 index 0000000000..5cbde615b2 --- /dev/null +++ b/src/applications/search/interface/PhabricatorFulltextInterface.php @@ -0,0 +1,7 @@ +setSynopsis(pht('Build or rebuild search indexes.')) ->setExamples( "**index** D123\n". - "**index** --type DREV\n". + "**index** --type task\n". "**index** --all") ->setArguments( array( @@ -19,8 +19,9 @@ final class PhabricatorSearchManagementIndexWorkflow ), array( 'name' => 'type', - 'param' => 'TYPE', - 'help' => pht('PHID type to reindex, like "TASK" or "DREV".'), + 'param' => 'type', + 'help' => pht( + 'Object types to reindex, like "task", "commit" or "revision".'), ), array( 'name' => 'background', @@ -29,6 +30,13 @@ final class PhabricatorSearchManagementIndexWorkflow 'the daemons. This can improve performance, but makes '. 'it more difficult to debug search indexing.'), ), + array( + 'name' => 'force', + 'short' => 'f', + 'help' => pht( + 'Force a complete rebuild of the entire index instead of an '. + 'incremental update.'), + ), array( 'name' => 'objects', 'wildcard' => true, @@ -41,6 +49,7 @@ final class PhabricatorSearchManagementIndexWorkflow $is_all = $args->getArg('all'); $is_type = $args->getArg('type'); + $is_force = $args->getArg('force'); $obj_names = $args->getArg('objects'); @@ -93,11 +102,14 @@ final class PhabricatorSearchManagementIndexWorkflow $bar = id(new PhutilConsoleProgressBar()) ->setTotal(count($phids)); + $parameters = array( + 'force' => $is_force, + ); + $any_success = false; - $indexer = new PhabricatorSearchIndexer(); foreach ($phids as $phid) { try { - $indexer->queueDocumentForIndexing($phid); + PhabricatorSearchWorker::queueDocumentForIndexing($phid, $parameters); $any_success = true; } catch (Exception $ex) { phlog($ex); @@ -135,20 +147,49 @@ final class PhabricatorSearchManagementIndexWorkflow } private function loadPHIDsByTypes($type) { - $indexers = id(new PhutilClassMapQuery()) - ->setAncestorClass('PhabricatorSearchDocumentIndexer') + $objects = id(new PhutilClassMapQuery()) + ->setAncestorClass('PhabricatorFulltextInterface') ->execute(); - $phids = array(); - foreach ($indexers as $indexer) { - $indexer_phid = $indexer->getIndexableObject()->generatePHID(); - $indexer_type = phid_get_type($indexer_phid); + $normalized_type = phutil_utf8_strtolower($type); - if ($type && strcasecmp($indexer_type, $type)) { - continue; + $matches = array(); + foreach ($objects as $object) { + $object_class = get_class($object); + $normalized_class = phutil_utf8_strtolower($object_class); + + if (!strlen($type) || + strpos($normalized_class, $normalized_type) !== false) { + $matches[$object_class] = $object; } + } - $iterator = $indexer->getIndexIterator(); + if (!$matches) { + $all_types = array(); + foreach ($objects as $object) { + $all_types[] = get_class($object); + } + sort($all_types); + + throw new PhutilArgumentUsageException( + pht( + 'Type "%s" matches no indexable objects. Supported types are: %s.', + $type, + implode(', ', $all_types))); + } + + if ((count($matches) > 1) && strlen($type)) { + throw new PhutilArgumentUsageException( + pht( + 'Type "%s" matches multiple indexable objects. Use a more '. + 'specific string. Matching object types are: %s.', + $type, + implode(', ', array_keys($matches)))); + } + + $phids = array(); + foreach ($matches as $match) { + $iterator = new LiskMigrationIterator($match); foreach ($iterator as $object) { $phids[] = $object->getPHID(); } @@ -157,4 +198,5 @@ final class PhabricatorSearchManagementIndexWorkflow return $phids; } + } diff --git a/src/applications/search/management/PhabricatorSearchManagementInitWorkflow.php b/src/applications/search/management/PhabricatorSearchManagementInitWorkflow.php index 56a5b33352..4c35b61dd5 100644 --- a/src/applications/search/management/PhabricatorSearchManagementInitWorkflow.php +++ b/src/applications/search/management/PhabricatorSearchManagementInitWorkflow.php @@ -13,7 +13,7 @@ final class PhabricatorSearchManagementInitWorkflow public function execute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); - $engine = PhabricatorSearchEngine::loadEngine(); + $engine = PhabricatorFulltextStorageEngine::loadEngine(); $work_done = false; if (!$engine->indexExists()) { diff --git a/src/applications/search/ngrams/PhabricatorSearchNgrams.php b/src/applications/search/ngrams/PhabricatorSearchNgrams.php new file mode 100644 index 0000000000..9ff8157e9e --- /dev/null +++ b/src/applications/search/ngrams/PhabricatorSearchNgrams.php @@ -0,0 +1,113 @@ +value = $value; + return $this; + } + + final public function getValue() { + return $this->value; + } + + protected function getConfiguration() { + return array( + self::CONFIG_TIMESTAMPS => false, + self::CONFIG_COLUMN_SCHEMA => array( + 'objectID' => 'uint32', + 'ngram' => 'char3', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_ngram' => array( + 'columns' => array('ngram', 'objectID'), + ), + 'key_object' => array( + 'columns' => array('objectID'), + ), + ), + ) + parent::getConfiguration(); + } + + public function getTableName() { + $application = $this->getApplicationName(); + $key = $this->getNgramKey(); + return "{$application}_{$key}_ngrams"; + } + + final public function tokenizeString($value) { + $value = trim($value, ' '); + $value = preg_split('/ +/', $value); + return $value; + } + + final public function getNgramsFromString($value, $mode) { + $tokens = $this->tokenizeString($value); + + $ngrams = array(); + foreach ($tokens as $token) { + $token = phutil_utf8_strtolower($token); + + switch ($mode) { + case 'query': + break; + case 'index': + $token = ' '.$token.' '; + break; + case 'prefix': + $token = ' '.$token; + break; + } + + $len = (strlen($token) - 2); + for ($ii = 0; $ii < $len; $ii++) { + $ngram = substr($token, $ii, 3); + $ngrams[$ngram] = $ngram; + } + } + + ksort($ngrams); + + return array_keys($ngrams); + } + + final public function writeNgram($object_id) { + $ngrams = $this->getNgramsFromString($this->getValue(), 'index'); + $conn_w = $this->establishConnection('w'); + + $sql = array(); + foreach ($ngrams as $ngram) { + $sql[] = qsprintf( + $conn_w, + '(%d, %s)', + $object_id, + $ngram); + } + + queryfx( + $conn_w, + 'DELETE FROM %T WHERE objectID = %d', + $this->getTableName(), + $object_id); + + if ($sql) { + queryfx( + $conn_w, + 'INSERT INTO %T (objectID, ngram) VALUES %Q', + $this->getTableName(), + implode(', ', $sql)); + } + + return $this; + } + +} diff --git a/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php b/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php index 731edbfb04..e1502294d3 100644 --- a/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php +++ b/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php @@ -204,10 +204,16 @@ final class PhabricatorSearchApplicationSearchEngine // TODO: This is inelegant and not very efficient, but gets us reasonable // results. It would be nice to do this more elegantly. - $indexers = id(new PhutilClassMapQuery()) - ->setAncestorClass('PhabricatorSearchDocumentIndexer') + $objects = id(new PhutilClassMapQuery()) + ->setAncestorClass('PhabricatorFulltextInterface') ->execute(); + $type_map = array(); + foreach ($objects as $object) { + $phid_type = phid_get_type($object->generatePHID()); + $type_map[$phid_type] = $object; + } + if ($viewer) { $types = PhabricatorPHIDType::getAllInstalledTypes($viewer); } else { @@ -217,11 +223,9 @@ final class PhabricatorSearchApplicationSearchEngine $results = array(); foreach ($types as $type) { $typeconst = $type->getTypeConstant(); - foreach ($indexers as $indexer) { - $fake_phid = 'PHID-'.$typeconst.'-fake'; - if ($indexer->shouldIndexDocumentByPHID($fake_phid)) { - $results[$typeconst] = $type->getTypeName(); - } + $object = idx($type_map, $typeconst); + if ($object) { + $results[$typeconst] = $type->getTypeName(); } } diff --git a/src/applications/search/query/PhabricatorSearchDocumentQuery.php b/src/applications/search/query/PhabricatorSearchDocumentQuery.php index 4928ee6a63..002c3364af 100644 --- a/src/applications/search/query/PhabricatorSearchDocumentQuery.php +++ b/src/applications/search/query/PhabricatorSearchDocumentQuery.php @@ -74,7 +74,7 @@ final class PhabricatorSearchDocumentQuery ->setParameter('offset', $this->getOffset()) ->setParameter('limit', $this->getRawResultLimit()); - $engine = PhabricatorSearchEngine::loadEngine(); + $engine = PhabricatorFulltextStorageEngine::loadEngine(); return $engine->executeSearch($query); } diff --git a/src/applications/search/storage/PhabricatorSearchIndexVersion.php b/src/applications/search/storage/PhabricatorSearchIndexVersion.php new file mode 100644 index 0000000000..702b1ea4d6 --- /dev/null +++ b/src/applications/search/storage/PhabricatorSearchIndexVersion.php @@ -0,0 +1,26 @@ + false, + self::CONFIG_COLUMN_SCHEMA => array( + 'extensionKey' => 'text64', + 'version' => 'text128', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_object' => array( + 'columns' => array('objectPHID', 'extensionKey'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + +} diff --git a/src/applications/search/worker/PhabricatorSearchWorker.php b/src/applications/search/worker/PhabricatorSearchWorker.php index 689602ab06..607baed69a 100644 --- a/src/applications/search/worker/PhabricatorSearchWorker.php +++ b/src/applications/search/worker/PhabricatorSearchWorker.php @@ -2,22 +2,84 @@ final class PhabricatorSearchWorker extends PhabricatorWorker { + public static function queueDocumentForIndexing($phid, $parameters = null) { + if ($parameters === null) { + $parameters = array(); + } + + parent::scheduleTask( + __CLASS__, + array( + 'documentPHID' => $phid, + 'parameters' => $parameters, + ), + array( + 'priority' => parent::PRIORITY_IMPORT, + )); + } + protected function doWork() { $data = $this->getTaskData(); + $object_phid = idx($data, 'documentPHID'); - $phid = idx($data, 'documentPHID'); - $context = idx($data, 'context'); + $object = $this->loadObjectForIndexing($object_phid); + + $engine = id(new PhabricatorIndexEngine()) + ->setObject($object); + + $parameters = idx($data, 'parameters', array()); + $engine->setParameters($parameters); + + if (!$engine->shouldIndexObject()) { + return; + } + + $key = "index.{$object_phid}"; + $lock = PhabricatorGlobalLock::newLock($key); + + $lock->lock(1); try { - id(new PhabricatorSearchIndexer()) - ->indexDocumentByPHID($phid, $context); + // Reload the object now that we have a lock, to make sure we have the + // most current version. + $object = $this->loadObjectForIndexing($object->getPHID()); + + $engine->setObject($object); + + $engine->indexObject(); } catch (Exception $ex) { + $lock->unlock(); + + if (!($ex instanceof PhabricatorWorkerPermanentFailureException)) { + $ex = new PhabricatorWorkerPermanentFailureException( + pht( + 'Failed to update search index for document "%s": %s', + $object_phid, + $ex->getMessage())); + } + + throw $ex; + } + + $lock->unlock(); + } + + private function loadObjectForIndexing($phid) { + $viewer = PhabricatorUser::getOmnipotentUser(); + + $object = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($phid)) + ->executeOne(); + + if (!$object) { throw new PhabricatorWorkerPermanentFailureException( pht( - 'Failed to update search index for document "%s": %s', - $phid, - $ex->getMessage())); + 'Unable to load object "%s" to rebuild indexes.', + $phid)); } + + return $object; } } diff --git a/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php b/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php index 6605bc9ecc..d38683ee06 100644 --- a/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php +++ b/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php @@ -163,4 +163,23 @@ final class PhabricatorSlowvoteSearchEngine return $result; } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Poll')) + ->setHref('/vote/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Poll other users to help facilitate decision making.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php b/src/applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php index a4f1ed183b..4b469cca1b 100644 --- a/src/applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php +++ b/src/applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php @@ -98,4 +98,24 @@ final class PhabricatorSpacesNamespaceSearchEngine return $result; } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Space')) + ->setHref('/spaces/create/') + ->setColor(PHUIButtonView::GREEN); + + $icon = $this->getApplication()->getFontIcon(); + $app_name = $this->getApplication()->getName(); + $view = id(new PHUIBigInfoView()) + ->setIcon($icon) + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Policy namespaces to segment object visibility throughout your '. + 'instance.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php index 2898c1202a..19b9281d02 100644 --- a/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php +++ b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php @@ -45,6 +45,7 @@ final class PhabricatorSubscriptionsEditEngineExtension ->setIsCopyable(true) ->setUseEdgeTransactions(true) ->setCommentActionLabel(pht('Change Subscribers')) + ->setDescription(pht('Choose subscribers.')) ->setTransactionType($subscribers_type) ->setValue($sub_phids); diff --git a/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsFulltextEngineExtension.php b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsFulltextEngineExtension.php new file mode 100644 index 0000000000..3c25618ebe --- /dev/null +++ b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsFulltextEngineExtension.php @@ -0,0 +1,41 @@ +getPHID()); + + if (!$subscriber_phids) { + return; + } + + $handles = id(new PhabricatorHandleQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($subscriber_phids) + ->execute(); + + foreach ($handles as $phid => $handle) { + $document->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER, + $phid, + $handle->getType(), + $document->getDocumentModified()); // Bogus timestamp. + } + } + +} diff --git a/src/applications/system/engine/PhabricatorDestructionEngine.php b/src/applications/system/engine/PhabricatorDestructionEngine.php index 607c526c14..f8e5be2ccc 100644 --- a/src/applications/system/engine/PhabricatorDestructionEngine.php +++ b/src/applications/system/engine/PhabricatorDestructionEngine.php @@ -17,14 +17,9 @@ final class PhabricatorDestructionEngine extends Phobject { $log->setRootLogID($this->rootLogID); } - $object_phid = null; - if (method_exists($object, 'getPHID')) { - try { - $object_phid = $object->getPHID(); - $log->setObjectPHID($object_phid); - } catch (Exception $ex) { - // Ignore. - } + $object_phid = $this->getObjectPHID($object); + if ($object_phid) { + $log->setObjectPHID($object_phid); } if (method_exists($object, 'getMonogram')) { @@ -44,123 +39,34 @@ final class PhabricatorDestructionEngine extends Phobject { $object->destroyObjectPermanently($this); if ($object_phid) { - $this->destroyEdges($object_phid); - - if ($object instanceof PhabricatorApplicationTransactionInterface) { - $template = $object->getApplicationTransactionTemplate(); - $this->destroyTransactions($template, $object_phid); - } - - $this->destroyWorkerTasks($object_phid); - $this->destroyNotifications($object_phid); - } - - // Nuke any Herald transcripts of the object, because they may contain - // field data. - - // TODO: Define an interface so we don't have to do this for transactions - // and other objects with no Herald adapters? - $transcripts = id(new HeraldTranscript())->loadAllWhere( - 'objectPHID = %s', - $object_phid); - foreach ($transcripts as $transcript) { - $transcript->destroyObjectPermanently($this); - } - - // TODO: Remove stuff from search indexes? - - if ($object instanceof PhabricatorFlaggableInterface) { - $flags = id(new PhabricatorFlag())->loadAllWhere( - 'objectPHID = %s', $object_phid); - - foreach ($flags as $flag) { - $flag->delete(); - } - } - - $flags = id(new PhabricatorFlag())->loadAllWhere( - 'ownerPHID = %s', $object_phid); - foreach ($flags as $flag) { - $flag->delete(); - } - - if ($object instanceof PhabricatorTokenReceiverInterface) { - $tokens = id(new PhabricatorTokenGiven())->loadAllWhere( - 'objectPHID = %s', $object_phid); - - foreach ($tokens as $token) { - $token->delete(); - } - } - - if ($object instanceof AlmanacPropertyInterface) { - $this->destroyAlmanacProperties($object_phid); - } - } - - private function destroyEdges($src_phid) { - try { - $edges = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs(array($src_phid)) - ->execute(); - } catch (Exception $ex) { - // This is (presumably) a "no edges for this PHID type" exception. - return; - } - - $editor = new PhabricatorEdgeEditor(); - foreach ($edges as $type => $type_edges) { - foreach ($type_edges as $src => $src_edges) { - foreach ($src_edges as $dst => $edge) { - $editor->removeEdge($edge['src'], $edge['type'], $edge['dst']); + $extensions = PhabricatorDestructionEngineExtension::getAllExtensions(); + foreach ($extensions as $key => $extension) { + if (!$extension->canDestroyObject($this, $object)) { + unset($extensions[$key]); + continue; } } - } - $editor->save(); - } - private function destroyTransactions( - PhabricatorApplicationTransaction $template, - $object_phid) { - - $xactions = $template->loadAllWhere('objectPHID = %s', $object_phid); - foreach ($xactions as $xaction) { - $this->destroyObject($xaction); + foreach ($extensions as $key => $extension) { + $extension->destroyObject($this, $object); + } } } - private function destroyWorkerTasks($object_phid) { - $tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere( - 'objectPHID = %s', - $object_phid); - - foreach ($tasks as $task) { - $task->archiveTask( - PhabricatorWorkerArchiveTask::RESULT_CANCELLED, - 0); + private function getObjectPHID($object) { + if (!is_object($object)) { + return null; } - } - private function destroyNotifications($object_phid) { - $table = new PhabricatorFeedStoryNotification(); - $conn_w = $table->establishConnection('w'); + if (!method_exists($object, 'getPHID')) { + return null; + } - queryfx( - $conn_w, - 'DELETE FROM %T WHERE primaryObjectPHID = %s', - $table->getTableName(), - $object_phid); - } - - private function destroyAlmanacProperties($object_phid) { - $table = new AlmanacProperty(); - $conn_w = $table->establishConnection('w'); - - queryfx( - $conn_w, - 'DELETE FROM %T WHERE objectPHID = %s', - $table->getTableName(), - $object_phid); + try { + return $object->getPHID(); + } catch (Exception $ex) { + return null; + } } } diff --git a/src/applications/system/engine/PhabricatorDestructionEngineExtension.php b/src/applications/system/engine/PhabricatorDestructionEngineExtension.php new file mode 100644 index 0000000000..3146e5d611 --- /dev/null +++ b/src/applications/system/engine/PhabricatorDestructionEngineExtension.php @@ -0,0 +1,28 @@ +getPhobjectClassConstant('EXTENSIONKEY'); + } + + abstract public function getExtensionName(); + + public function canDestroyObject( + PhabricatorDestructionEngine $engine, + $object) { + return true; + } + + abstract public function destroyObject( + PhabricatorDestructionEngine $engine, + $object); + + final public static function getAllExtensions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getExtensionKey') + ->execute(); + } + +} diff --git a/src/applications/system/engine/PhabricatorDestructionEngineExtensionModule.php b/src/applications/system/engine/PhabricatorDestructionEngineExtensionModule.php new file mode 100644 index 0000000000..e57375e336 --- /dev/null +++ b/src/applications/system/engine/PhabricatorDestructionEngineExtensionModule.php @@ -0,0 +1,44 @@ +getViewer(); + + $extensions = PhabricatorDestructionEngineExtension::getAllExtensions(); + + $rows = array(); + foreach ($extensions as $extension) { + $rows[] = array( + get_class($extension), + $extension->getExtensionName(), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Class'), + pht('Name'), + )) + ->setColumnClasses( + array( + null, + 'wide pri', + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DestructionEngine Extensions')) + ->setTable($table); + } + +} diff --git a/src/applications/tokens/engineextension/PhabricatorTokenDestructionEngineExtension.php b/src/applications/tokens/engineextension/PhabricatorTokenDestructionEngineExtension.php new file mode 100644 index 0000000000..c3766466cd --- /dev/null +++ b/src/applications/tokens/engineextension/PhabricatorTokenDestructionEngineExtension.php @@ -0,0 +1,31 @@ +loadAllWhere( + 'objectPHID = %s', + $object->getPHID()); + + foreach ($tokens as $token) { + $token->delete(); + } + } + +} diff --git a/src/applications/transactions/application/PhabricatorTransactionsApplication.php b/src/applications/transactions/application/PhabricatorTransactionsApplication.php index 33fbfd84b9..51c88e990d 100644 --- a/src/applications/transactions/application/PhabricatorTransactionsApplication.php +++ b/src/applications/transactions/application/PhabricatorTransactionsApplication.php @@ -33,6 +33,8 @@ final class PhabricatorTransactionsApplication extends PhabricatorApplication { => 'PhabricatorApplicationTransactionShowOlderController', '(?Pold|new)/(?[^/]+)/' => 'PhabricatorApplicationTransactionValueController', + 'remarkuppreview/' + => 'PhabricatorApplicationTransactionRemarkupPreviewController', 'editengine/' => array( $this->getQueryRoutePattern() => 'PhabricatorEditEngineListController', diff --git a/src/applications/transactions/constants/PhabricatorTransactions.php b/src/applications/transactions/constants/PhabricatorTransactions.php index 1422f21081..172f80ba31 100644 --- a/src/applications/transactions/constants/PhabricatorTransactions.php +++ b/src/applications/transactions/constants/PhabricatorTransactions.php @@ -13,6 +13,7 @@ final class PhabricatorTransactions extends Phobject { const TYPE_TOKEN = 'token:give'; const TYPE_INLINESTATE = 'core:inlinestate'; const TYPE_SPACE = 'core:space'; + const TYPE_CREATE = 'core:create'; const COLOR_RED = 'red'; const COLOR_ORANGE = 'orange'; diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php new file mode 100644 index 0000000000..4ba8345d5c --- /dev/null +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php @@ -0,0 +1,25 @@ +getViewer(); + + $corpus = $request->getStr('corpus'); + + $remarkup = new PHUIRemarkupView($viewer, $corpus); + + $content = array( + 'content' => hsprintf('%s', $remarkup), + ); + + return id(new AphrontAjaxResponse()) + ->setContent($content); + } + +} diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionShowOlderController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionShowOlderController.php index 67cc3c63aa..cdbdbf1ba9 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionShowOlderController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionShowOlderController.php @@ -17,30 +17,17 @@ final class PhabricatorApplicationTransactionShowOlderController if (!$object) { return new Aphront404Response(); } + if (!$object instanceof PhabricatorApplicationTransactionInterface) { return new Aphront404Response(); } - $template = $object->getApplicationTransactionTemplate(); - $queries = id(new PhutilClassMapQuery()) - ->setAncestorClass('PhabricatorApplicationTransactionQuery') - ->execute(); - - $object_query = null; - foreach ($queries as $query) { - if ($query->getTemplateApplicationTransaction() == $template) { - $object_query = $query; - break; - } - } - - if (!$object_query) { + $query = PhabricatorApplicationTransactionQuery::newQueryForObject($object); + if (!$query) { return new Aphront404Response(); } - $timeline = $this->buildTransactionTimeline( - $object, - $query); + $timeline = $this->buildTransactionTimeline($object, $query); $phui_timeline = $timeline->buildPHUITimelineView($with_hiding = false); $phui_timeline->setShouldAddSpacers(false); diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php index 7280cd050a..dbf73477da 100644 --- a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php @@ -13,7 +13,8 @@ final class PhabricatorEditEngineConfigurationListController $engine_key = $request->getURIData('engineKey'); $this->setEngineKey($engine_key); - $engine = PhabricatorEditEngine::getByKey($viewer, $engine_key); + $engine = PhabricatorEditEngine::getByKey($viewer, $engine_key) + ->setViewer($viewer); $items = array(); $items[] = id(new PHUIListItemView()) @@ -23,9 +24,12 @@ final class PhabricatorEditEngineConfigurationListController $sort_create_uri = "/transactions/editengine/{$engine_key}/sort/create/"; $sort_edit_uri = "/transactions/editengine/{$engine_key}/sort/edit/"; - $can_edit = PhabricatorPolicyFilter::hasCapability( + $builtins = $engine->getBuiltinEngineConfigurations(); + $builtin = head($builtins); + + $can_sort = PhabricatorPolicyFilter::hasCapability( $viewer, - $engine, + $builtin, PhabricatorPolicyCapability::CAN_EDIT); $items[] = id(new PHUIListItemView()) @@ -33,14 +37,14 @@ final class PhabricatorEditEngineConfigurationListController ->setName(pht('Reorder Create Forms')) ->setHref($sort_create_uri) ->setWorkflow(true) - ->setDisabled(!$can_edit); + ->setDisabled(!$can_sort); $items[] = id(new PHUIListItemView()) ->setType(PHUIListItemView::TYPE_LINK) ->setName(pht('Reorder Edit Forms')) ->setHref($sort_edit_uri) ->setWorkflow(true) - ->setDisabled(!$can_edit); + ->setDisabled(!$can_sort); return id(new PhabricatorEditEngineConfigurationSearchEngine()) ->setController($this) diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationSortController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationSortController.php index c0ee0dd6dd..613a847326 100644 --- a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationSortController.php +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationSortController.php @@ -39,6 +39,15 @@ final class PhabricatorEditEngineConfigurationSortController $configs = $query->execute(); + // Do this check here (instead of in the Query above) to get a proper + // policy exception if the user doesn't satisfy + foreach ($configs as $config) { + PhabricatorPolicyFilter::requireCapability( + $viewer, + $config, + PhabricatorPolicyCapability::CAN_EDIT); + } + if ($is_create) { $configs = msort($configs, 'getCreateSortKey'); } else { diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 587c48c9c8..2e96e60dd0 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -836,7 +836,17 @@ abstract class PhabricatorEditEngine unset($submit_fields[$key]); continue; } + } + // Before we read the submitted values, store a copy of what we would + // use if the form was empty so we can figure out which transactions are + // just setting things to their default values for the current form. + $defaults = array(); + foreach ($submit_fields as $key => $field) { + $defaults[$key] = $field->getValueForTransaction(); + } + + foreach ($submit_fields as $key => $field) { $field->setIsSubmittedForm(true); if (!$field->shouldReadValueFromSubmit()) { @@ -847,14 +857,28 @@ abstract class PhabricatorEditEngine } $xactions = array(); - foreach ($submit_fields as $field) { + + if ($this->getIsCreate()) { + $xactions[] = id(clone $template) + ->setTransactionType(PhabricatorTransactions::TYPE_CREATE); + } + + foreach ($submit_fields as $key => $field) { + $field_value = $field->getValueForTransaction(); + $type_xactions = $field->generateTransactions( clone $template, array( - 'value' => $field->getValueForTransaction(), + 'value' => $field_value, )); foreach ($type_xactions as $type_xaction) { + $default = $defaults[$key]; + + if ($default === $field->getValueForTransaction()) { + $type_xaction->setIsDefaultTransaction(true); + } + $xactions[] = $type_xaction; } } @@ -1629,6 +1653,12 @@ abstract class PhabricatorEditEngine } $results = array(); + + if ($this->getIsCreate()) { + $results[] = id(clone $template) + ->setTransactionType(PhabricatorTransactions::TYPE_CREATE); + } + foreach ($xactions as $xaction) { $type = $types[$xaction['type']]; @@ -1723,7 +1753,8 @@ abstract class PhabricatorEditEngine ->setName($group_name); foreach ($configs as $config) { - $items[] = $this->newQuickCreateItem($config); + $items[] = $this->newQuickCreateItem($config) + ->setIndented(true); } } diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php index 7ca483c2b9..9d1b976299 100644 --- a/src/applications/transactions/editfield/PhabricatorEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEditField.php @@ -755,8 +755,4 @@ abstract class PhabricatorEditField extends Phobject { return $edit_type->generateTransactions($template, $spec); } - - - - } diff --git a/src/applications/transactions/editfield/PhabricatorSpaceEditField.php b/src/applications/transactions/editfield/PhabricatorSpaceEditField.php index 6ad1e27831..ee15f0b19e 100644 --- a/src/applications/transactions/editfield/PhabricatorSpaceEditField.php +++ b/src/applications/transactions/editfield/PhabricatorSpaceEditField.php @@ -3,6 +3,17 @@ final class PhabricatorSpaceEditField extends PhabricatorEditField { + private $policyField; + + public function setPolicyField(PhabricatorPolicyEditField $policy_field) { + $this->policyField = $policy_field; + return $this; + } + + public function getPolicyField() { + return $this->policyField; + } + protected function newControl() { // NOTE: This field doesn't do anything on its own, it just serves as a // companion to the associated View Policy field. @@ -17,4 +28,13 @@ final class PhabricatorSpaceEditField return new ConduitPHIDParameterType(); } + + public function shouldReadValueFromRequest() { + return $this->getPolicyField()->shouldReadValueFromRequest(); + } + + public function shouldReadValueFromSubmit() { + return $this->getPolicyField()->shouldReadValueFromSubmit(); + } + } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index fa11d0625b..63bf47a56b 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -258,6 +258,8 @@ abstract class PhabricatorApplicationTransactionEditor public function getTransactionTypes() { $types = array(); + $types[] = PhabricatorTransactions::TYPE_CREATE; + if ($this->object instanceof PhabricatorSubscribableInterface) { $types[] = PhabricatorTransactions::TYPE_SUBSCRIBERS; } @@ -303,23 +305,32 @@ abstract class PhabricatorApplicationTransactionEditor PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { + case PhabricatorTransactions::TYPE_CREATE: + return null; case PhabricatorTransactions::TYPE_SUBSCRIBERS: return array_values($this->subscribers); case PhabricatorTransactions::TYPE_VIEW_POLICY: + if ($this->getIsNewObject()) { + return null; + } return $object->getViewPolicy(); case PhabricatorTransactions::TYPE_EDIT_POLICY: + if ($this->getIsNewObject()) { + return null; + } return $object->getEditPolicy(); case PhabricatorTransactions::TYPE_JOIN_POLICY: + if ($this->getIsNewObject()) { + return null; + } return $object->getJoinPolicy(); case PhabricatorTransactions::TYPE_SPACE: + if ($this->getIsNewObject()) { + return null; + } + $space_phid = $object->getSpacePHID(); if ($space_phid === null) { - if ($this->getIsNewObject()) { - // In this case, just return `null` so we know this is the initial - // transaction and it should be hidden. - return null; - } - $default_space = PhabricatorSpacesNamespaceQuery::getDefaultSpace(); if ($default_space) { $space_phid = $default_space->getPHID(); @@ -364,6 +375,8 @@ abstract class PhabricatorApplicationTransactionEditor PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { + case PhabricatorTransactions::TYPE_CREATE: + return null; case PhabricatorTransactions::TYPE_SUBSCRIBERS: return $this->getPHIDTransactionNewValue($xaction); case PhabricatorTransactions::TYPE_VIEW_POLICY: @@ -415,6 +428,8 @@ abstract class PhabricatorApplicationTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { + case PhabricatorTransactions::TYPE_CREATE: + return true; case PhabricatorTransactions::TYPE_COMMENT: return $xaction->hasComment(); case PhabricatorTransactions::TYPE_CUSTOMFIELD: @@ -477,6 +492,7 @@ abstract class PhabricatorApplicationTransactionEditor case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getCustomFieldForTransaction($object, $xaction); return $field->applyApplicationTransactionInternalEffects($xaction); + case PhabricatorTransactions::TYPE_CREATE: case PhabricatorTransactions::TYPE_BUILDABLE: case PhabricatorTransactions::TYPE_TOKEN: case PhabricatorTransactions::TYPE_VIEW_POLICY: @@ -527,6 +543,7 @@ abstract class PhabricatorApplicationTransactionEditor case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getCustomFieldForTransaction($object, $xaction); return $field->applyApplicationTransactionExternalEffects($xaction); + case PhabricatorTransactions::TYPE_CREATE: case PhabricatorTransactions::TYPE_EDGE: case PhabricatorTransactions::TYPE_BUILDABLE: case PhabricatorTransactions::TYPE_TOKEN: @@ -823,6 +840,22 @@ abstract class PhabricatorApplicationTransactionEditor throw $ex; } + // TODO: Once everything is on EditEngine, just use getIsNewObject() to + // figure this out instead. + $mark_as_create = false; + $create_type = PhabricatorTransactions::TYPE_CREATE; + foreach ($xactions as $xaction) { + if ($xaction->getTransactionType() == $create_type) { + $mark_as_create = true; + } + } + + if ($mark_as_create) { + foreach ($xactions as $xaction) { + $xaction->setIsCreateTransaction(true); + } + } + // Now that we've merged, filtered, and combined transactions, check for // required capabilities. foreach ($xactions as $xaction) { @@ -1060,10 +1093,11 @@ abstract class PhabricatorApplicationTransactionEditor } if ($this->supportsSearch()) { - id(new PhabricatorSearchIndexer()) - ->queueDocumentForIndexing( - $object->getPHID(), - $this->getSearchContextParameter($object, $xactions)); + PhabricatorSearchWorker::queueDocumentForIndexing( + $object->getPHID(), + array( + 'transactionPHIDs' => mpull($xactions, 'getPHID'), + )); } if ($this->shouldPublishFeedStory($object, $xactions)) { @@ -2833,15 +2867,6 @@ abstract class PhabricatorApplicationTransactionEditor return false; } - /** - * @task search - */ - protected function getSearchContextParameter( - PhabricatorLiskDAO $object, - array $xactions) { - return null; - } - /* -( Herald Integration )-------------------------------------------------- */ diff --git a/src/applications/transactions/editengineextension/PhabricatorCommentEditEngineExtension.php b/src/applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php similarity index 100% rename from src/applications/transactions/editengineextension/PhabricatorCommentEditEngineExtension.php rename to src/applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php diff --git a/src/applications/transactions/editengineextension/PhabricatorEditEngineExtension.php b/src/applications/transactions/engineextension/PhabricatorEditEngineExtension.php similarity index 100% rename from src/applications/transactions/editengineextension/PhabricatorEditEngineExtension.php rename to src/applications/transactions/engineextension/PhabricatorEditEngineExtension.php diff --git a/src/applications/transactions/editengineextension/PhabricatorEditEngineExtensionModule.php b/src/applications/transactions/engineextension/PhabricatorEditEngineExtensionModule.php similarity index 96% rename from src/applications/transactions/editengineextension/PhabricatorEditEngineExtensionModule.php rename to src/applications/transactions/engineextension/PhabricatorEditEngineExtensionModule.php index 98a4e0cb3b..3cc572908a 100644 --- a/src/applications/transactions/editengineextension/PhabricatorEditEngineExtensionModule.php +++ b/src/applications/transactions/engineextension/PhabricatorEditEngineExtensionModule.php @@ -8,7 +8,7 @@ final class PhabricatorEditEngineExtensionModule } public function getModuleName() { - return pht('EditEngine Extensions'); + return pht('Engine: Edit'); } public function renderModuleStatus(AphrontRequest $request) { diff --git a/src/applications/transactions/engineextension/PhabricatorTransactionsDestructionEngineExtension.php b/src/applications/transactions/engineextension/PhabricatorTransactionsDestructionEngineExtension.php new file mode 100644 index 0000000000..142b0153ae --- /dev/null +++ b/src/applications/transactions/engineextension/PhabricatorTransactionsDestructionEngineExtension.php @@ -0,0 +1,31 @@ +getApplicationTransactionTemplate(); + $xactions = $template->loadAllWhere( + 'objectPHID = %s', + $object->getPHID()); + foreach ($xactions as $xaction) { + $engine->destroyObject($xaction); + } + } + +} diff --git a/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php b/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php new file mode 100644 index 0000000000..b95fc0167a --- /dev/null +++ b/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php @@ -0,0 +1,44 @@ +setViewer($this->getViewer()) + ->withObjectPHIDs(array($object->getPHID())) + ->needComments(true) + ->execute(); + + foreach ($xactions as $xaction) { + if (!$xaction->hasComment()) { + continue; + } + + $comment = $xaction->getComment(); + + $document->addField( + PhabricatorSearchDocumentFieldType::FIELD_COMMENT, + $comment->getContent()); + } + } + +} diff --git a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php index 4c1e3ccd17..da8454c7fc 100644 --- a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php +++ b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php @@ -11,6 +11,27 @@ abstract class PhabricatorApplicationTransactionQuery private $needComments = true; private $needHandles = true; + final public static function newQueryForObject( + PhabricatorApplicationTransactionInterface $object) { + + $xaction = $object->getApplicationTransactionTemplate(); + $target_class = get_class($xaction); + + $queries = id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->execute(); + foreach ($queries as $query) { + $query_xaction = $query->getTemplateApplicationTransaction(); + $query_class = get_class($query_xaction); + + if ($query_class === $target_class) { + return id(clone $query); + } + } + + return null; + } + abstract public function getTemplateApplicationTransaction(); protected function buildMoreWhereClauses(AphrontDatabaseConnection $conn_r) { diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index df46649c08..6fa6a6458e 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -142,6 +142,22 @@ abstract class PhabricatorApplicationTransaction return $this->comment; } + public function setIsCreateTransaction($create) { + return $this->setMetadataValue('core.create', $create); + } + + public function getIsCreateTransaction() { + return (bool)$this->getMetadataValue('core.create', false); + } + + public function setIsDefaultTransaction($default) { + return $this->setMetadataValue('core.default', $default); + } + + public function getIsDefaultTransaction() { + return (bool)$this->getMetadataValue('core.default', false); + } + public function attachComment( PhabricatorApplicationTransactionComment $comment) { $this->comment = $comment; @@ -456,11 +472,61 @@ abstract class PhabricatorApplicationTransaction } public function shouldHide() { + // Never hide comments. + if ($this->hasComment()) { + return false; + } + + // Hide creation transactions if the old value is empty. These are + // transactions like "alice set the task tile to: ...", which are + // essentially never interesting. + if ($this->getIsCreateTransaction()) { + switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_CREATE: + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + case PhabricatorTransactions::TYPE_JOIN_POLICY: + case PhabricatorTransactions::TYPE_SPACE: + break; + default: + $old = $this->getOldValue(); + + if (is_array($old) && !$old) { + return true; + } + + if (!strlen($old)) { + return true; + } + break; + } + } + + // Hide creation transactions setting values to defaults, even if + // the old value is not empty. For example, tasks may have a global + // default view policy of "All Users", but a particular form sets the + // policy to "Administrators". The transaction corresponding to this + // change is not interesting, since it is the default behavior of the + // form. + + if ($this->getIsCreateTransaction()) { + if ($this->getIsDefaultTransaction()) { + return true; + } + } + switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: case PhabricatorTransactions::TYPE_SPACE: + if ($this->getIsCreateTransaction()) { + break; + } + + // TODO: Remove this eventually, this is handling old changes during + // object creation prior to the introduction of "create" and "default" + // transaction display flags. if ($this->getOldValue() === null) { return true; } else { @@ -499,6 +565,10 @@ abstract class PhabricatorApplicationTransaction } public function shouldHideForMail(array $xactions) { + if ($this->isSelfSubscription()) { + return true; + } + switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_TOKEN: return true; @@ -548,6 +618,10 @@ abstract class PhabricatorApplicationTransaction } public function shouldHideForFeed() { + if ($this->isSelfSubscription()) { + return true; + } + switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_TOKEN: return true; @@ -638,34 +712,66 @@ abstract class PhabricatorApplicationTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s created this object.', + $this->renderHandleLink($author_phid)); case PhabricatorTransactions::TYPE_COMMENT: return pht( '%s added a comment.', $this->renderHandleLink($author_phid)); case PhabricatorTransactions::TYPE_VIEW_POLICY: - return pht( - '%s changed the visibility from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderPolicyName($old, 'old'), - $this->renderPolicyName($new, 'new')); + if ($this->getIsCreateTransaction()) { + return pht( + '%s created this object with visibility "%s".', + $this->renderHandleLink($author_phid), + $this->renderPolicyName($new, 'new')); + } else { + return pht( + '%s changed the visibility from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $this->renderPolicyName($old, 'old'), + $this->renderPolicyName($new, 'new')); + } case PhabricatorTransactions::TYPE_EDIT_POLICY: - return pht( - '%s changed the edit policy from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderPolicyName($old, 'old'), - $this->renderPolicyName($new, 'new')); + if ($this->getIsCreateTransaction()) { + return pht( + '%s created this object with edit policy "%s".', + $this->renderHandleLink($author_phid), + $this->renderPolicyName($new, 'new')); + } else { + return pht( + '%s changed the edit policy from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $this->renderPolicyName($old, 'old'), + $this->renderPolicyName($new, 'new')); + } case PhabricatorTransactions::TYPE_JOIN_POLICY: - return pht( - '%s changed the join policy from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderPolicyName($old, 'old'), - $this->renderPolicyName($new, 'new')); + if ($this->getIsCreateTransaction()) { + return pht( + '%s created this object with join policy "%s".', + $this->renderHandleLink($author_phid), + $this->renderPolicyName($new, 'new')); + } else { + return pht( + '%s changed the join policy from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $this->renderPolicyName($old, 'old'), + $this->renderPolicyName($new, 'new')); + } case PhabricatorTransactions::TYPE_SPACE: - return pht( - '%s shifted this object from the %s space to the %s space.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); + if ($this->getIsCreateTransaction()) { + return pht( + '%s created this object in space %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($new)); + } else { + return pht( + '%s shifted this object from the %s space to the %s space.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); + } case PhabricatorTransactions::TYPE_SUBSCRIBERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -826,6 +932,11 @@ abstract class PhabricatorApplicationTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s created %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_COMMENT: return pht( '%s added a comment to %s.', @@ -1000,33 +1111,13 @@ abstract class PhabricatorApplicationTransaction case PhabricatorTransactions::TYPE_COMMENT: return 0.5; case PhabricatorTransactions::TYPE_SUBSCRIBERS: - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $add = array_diff($old, $new); - $rem = array_diff($new, $old); - - // If this action is the actor subscribing or unsubscribing themselves, - // it is less interesting. In particular, if someone makes a comment and - // also implicitly subscribes themselves, we should treat the - // transaction group as "comment", not "subscribe". In this specific - // case (one affected user, and that affected user it the actor), - // decrease the action strength. - - if ((count($add) + count($rem)) != 1) { - // Not exactly one CC change. - break; + if ($this->isSelfSubscription()) { + // Make this weaker than TYPE_COMMENT. + return 0.25; } - - $affected_phid = head(array_merge($add, $rem)); - if ($affected_phid != $this->getAuthorPHID()) { - // Affected user is someone else. - break; - } - - // Make this weaker than TYPE_COMMENT. - return 0.25; + break; } + return 1.0; } @@ -1229,6 +1320,35 @@ abstract class PhabricatorApplicationTransaction return rtrim($text."\n\n".$body); } + /** + * Test if this transaction is just a user subscribing or unsubscribing + * themselves. + */ + private function isSelfSubscription() { + $type = $this->getTransactionType(); + if ($type != PhabricatorTransactions::TYPE_SUBSCRIBERS) { + return false; + } + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($old, $new); + $rem = array_diff($new, $old); + + if ((count($add) + count($rem)) != 1) { + // More than one user affected. + return false; + } + + $affected_phid = head(array_merge($add, $rem)); + if ($affected_phid != $this->getAuthorPHID()) { + // Affected user is someone else. + return false; + } + + return true; + } /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ diff --git a/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php b/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php index 582dfb8179..da5f0520f6 100644 --- a/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php @@ -34,6 +34,10 @@ final class PhabricatorEditEngineConfigurationTransaction $type = $this->getTransactionType(); switch ($type) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s created this form configuration.', + $this->renderHandleLink($author_phid)); case self::TYPE_NAME: if (strlen($old)) { return pht( diff --git a/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php b/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php index bd6ffaa807..767e392248 100644 --- a/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php +++ b/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php @@ -39,11 +39,17 @@ final class PhabricatorApplicationEditHTTPParameterHelpView // Remove fields which do not expose an HTTP parameter type. $types = array(); foreach ($fields as $key => $field) { + if (!$field->shouldGenerateTransactionsFromSubmit()) { + unset($fields[$key]); + continue; + } + $type = $field->getHTTPParameterType(); if ($type === null) { unset($fields[$key]); continue; } + $types[$type->getTypeName()] = $type; } diff --git a/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php b/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php index c1ba981066..1b017c224e 100644 --- a/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php +++ b/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php @@ -92,8 +92,15 @@ final class PhabricatorApplicationTransactionPublishWorker $viewer = PhabricatorUser::getOmnipotentUser(); - $type = phid_get_subtype(head($xaction_phids)); - $xactions = $this->buildTransactionQuery($type) + $query = PhabricatorApplicationTransactionQuery::newQueryForObject($object); + if (!$query) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to load query for transaction object "%s"!', + $object->getPHID())); + } + + $xactions = $query ->setViewer($viewer) ->withPHIDs($xaction_phids) ->needComments(true) @@ -111,29 +118,4 @@ final class PhabricatorApplicationTransactionPublishWorker return array_select_keys($xactions, $xaction_phids); } - - /** - * Build a new transaction query of the appropriate class so we can load - * the transactions. - */ - private function buildTransactionQuery($type) { - $queries = id(new PhutilClassMapQuery()) - ->setAncestorClass('PhabricatorApplicationTransactionQuery') - ->execute(); - - foreach ($queries as $query) { - $query_type = $query - ->getTemplateApplicationTransaction() - ->getApplicationTransactionType(); - if ($query_type == $type) { - return $query; - } - } - - throw new PhabricatorWorkerPermanentFailureException( - pht( - 'Unable to load query for transaction type "%s"!', - $type)); - } - } diff --git a/src/docs/user/userguide/events.diviner b/src/docs/user/userguide/events.diviner index b0b22a02c1..dc9722a596 100644 --- a/src/docs/user/userguide/events.diviner +++ b/src/docs/user/userguide/events.diviner @@ -188,41 +188,6 @@ Data available on this event: Using @{class@libphutil:PhutilEmailAddress} may be helpful in parsing the query. -== Search: Did Update Index == - -The constant for this event is -`PhabricatorEventType::TYPE_SEARCH_DIDUPDATEINDEX`. - -This event is dispatched from the Search application's indexing engine, after -it indexes a document. It allows you to publish search-like indexes into other -systems. - -Note that this event happens after the update is fully complete: you can not -prevent or modify the update. Further, the event may fire significantly later -in real time than the update, as indexing may occur in the background. You -should use other events if you need guarantees about when the event executes. - -Finally, this event may fire more than once for a single update. For example, -if the search indexes are rebuilt, this event will fire on objects which have -not actually changed. - -So, good use cases for event listeners are: - - - Updating secondary search indexes. - -Bad use cases are: - - - Editing the object or document. - - Anything with side effects, like sending email. - -Data available on this event: - - - `phid` The PHID of the updated object. - - `object` The object which was updated (like a @{class:ManiphesTask}). - - `document` The @{class:PhabricatorSearchAbstractDocument} which was indexed. - This contains an abstract representation of the object, and may be useful - in populating secondary indexes because it provides a uniform API. - == Test: Did Run Test == The constant for this event is diff --git a/src/infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php b/src/infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php new file mode 100644 index 0000000000..8186ff5311 --- /dev/null +++ b/src/infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php @@ -0,0 +1,39 @@ +setViewer($this->getViewer()); + $field_list->readFieldsFromStorage($object); + + // Rebuild ApplicationSearch indexes. + $field_list->rebuildIndexes($object); + + // Rebuild global search indexes. + $field_list->updateAbstractDocument($document); + } + +} diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php index 8b4c167d6d..6299cc047e 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomField.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php @@ -594,6 +594,13 @@ abstract class PhabricatorCustomField extends Phobject { throw new PhabricatorCustomFieldImplementationIncompleteException($this); } + public function didSetValueFromStorage() { + if ($this->proxy) { + return $this->proxy->didSetValueFromStorage(); + } + return $this; + } + /* -( ApplicationSearch )-------------------------------------------------- */ diff --git a/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php b/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php index 160a618397..9fefa52ad4 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php @@ -73,10 +73,12 @@ final class PhabricatorCustomFieldList extends Phobject { $storage = idx($objects, $key); if ($storage) { $field->setValueFromStorage($storage->getFieldValue()); + $field->didSetValueFromStorage(); } else if ($object->getPHID()) { // NOTE: We set this only if the object exists. Otherwise, we allow the // field to retain any default value it may have. $field->setValueFromStorage(null); + $field->didSetValueFromStorage(); } } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php index 4cdafa7149..0c3897731b 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php @@ -16,6 +16,7 @@ abstract class PhabricatorStandardCustomField private $required; private $default; private $isCopyable; + private $hasStorageValue; private $hidden; abstract public function getFieldType(); @@ -223,6 +224,19 @@ abstract class PhabricatorStandardCustomField return $this->setFieldValue($value); } + public function didSetValueFromStorage() { + $this->hasStorageValue = true; + return $this; + } + + public function getOldValueForApplicationTransactions() { + if ($this->hasStorageValue) { + return $this->getValueForStorage(); + } else { + return null; + } + } + public function shouldAppearInApplicationTransactions() { return true; } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php index eadb382af7..14e4eede5a 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php @@ -103,4 +103,8 @@ final class PhabricatorStandardCustomFieldRemarkup return false; } + public function getConduitEditParameterType() { + return new ConduitStringParameterType(); + } + } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php index 3c02c7dd20..716e1dfc99 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php @@ -71,4 +71,8 @@ final class PhabricatorStandardCustomFieldText return false; } + public function getConduitEditParameterType() { + return new ConduitStringParameterType(); + } + } diff --git a/src/infrastructure/daemon/workers/PhabricatorWorker.php b/src/infrastructure/daemon/workers/PhabricatorWorker.php index 473649ba19..ea59462740 100644 --- a/src/infrastructure/daemon/workers/PhabricatorWorker.php +++ b/src/infrastructure/daemon/workers/PhabricatorWorker.php @@ -158,11 +158,8 @@ abstract class PhabricatorWorker extends Phobject { while (true) { try { - $worker->doWork(); - foreach ($worker->getQueuedTasks() as $queued_task) { - list($queued_class, $queued_data, $queued_options) = $queued_task; - self::scheduleTask($queued_class, $queued_data, $queued_options); - } + $worker->executeTask(); + $worker->flushTaskQueue(); break; } catch (PhabricatorWorkerYieldException $ex) { phlog( @@ -236,11 +233,34 @@ abstract class PhabricatorWorker extends Phobject { * * @return list> Queued task specifications. */ - final public function getQueuedTasks() { + final protected function getQueuedTasks() { return $this->queuedTasks; } + /** + * Schedule any queued tasks, then empty the task queue. + * + * By default, the queue is flushed only if a task succeeds. You can call + * this method to force the queue to flush before failing (for example, if + * you are using queues to improve locking behavior). + * + * @param map Optional default options. + * @return this + */ + final public function flushTaskQueue($defaults = array()) { + foreach ($this->getQueuedTasks() as $task) { + list($class, $data, $options) = $task; + + $options = $options + $defaults; + + self::scheduleTask($class, $data, $options); + } + + $this->queuedTasks = array(); + } + + /** * Awaken tasks that have yielded. * diff --git a/src/infrastructure/daemon/workers/engineextension/PhabricatorWorkerDestructionEngineExtension.php b/src/infrastructure/daemon/workers/engineextension/PhabricatorWorkerDestructionEngineExtension.php new file mode 100644 index 0000000000..93f6e34e5c --- /dev/null +++ b/src/infrastructure/daemon/workers/engineextension/PhabricatorWorkerDestructionEngineExtension.php @@ -0,0 +1,27 @@ +loadAllWhere( + 'objectPHID = %s', + $object->getPHID()); + + foreach ($tasks as $task) { + $task->archiveTask( + PhabricatorWorkerArchiveTask::RESULT_CANCELLED, + 0); + } + } + +} diff --git a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php index 57a1794ec3..0fcc78db6e 100644 --- a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php +++ b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php @@ -216,16 +216,11 @@ final class PhabricatorWorkerActiveTask extends PhabricatorWorkerTask { // NOTE: If this throws, we don't want it to cause the task to fail again, // so execute it out here and just let the exception escape. if ($did_succeed) { - foreach ($worker->getQueuedTasks() as $task) { - list($class, $data, $options) = $task; - - // Default the new task priority to our own priority. - $options = $options + array( - 'priority' => (int)$this->getPriority(), - ); - - PhabricatorWorker::scheduleTask($class, $data, $options); - } + // Default the new task priority to our own priority. + $defaults = array( + 'priority' => (int)$this->getPriority(), + ); + $worker->flushTaskQueue($defaults); } return $result; diff --git a/src/infrastructure/edges/engineextension/PhabricatorEdgesDestructionEngineExtension.php b/src/infrastructure/edges/engineextension/PhabricatorEdgesDestructionEngineExtension.php new file mode 100644 index 0000000000..b2e41b1575 --- /dev/null +++ b/src/infrastructure/edges/engineextension/PhabricatorEdgesDestructionEngineExtension.php @@ -0,0 +1,38 @@ +getPHID(); + + try { + $edges = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(array($src_phid)) + ->execute(); + } catch (Exception $ex) { + // This is (presumably) a "no edges for this PHID type" exception. + return; + } + + $editor = new PhabricatorEdgeEditor(); + foreach ($edges as $type => $type_edges) { + foreach ($type_edges as $src => $src_edges) { + foreach ($src_edges as $dst => $edge) { + $editor->removeEdge($edge['src'], $edge['type'], $edge['dst']); + } + } + } + $editor->save(); + } + +} diff --git a/src/infrastructure/events/constant/PhabricatorEventType.php b/src/infrastructure/events/constant/PhabricatorEventType.php index 2a570cc823..64c58c11e5 100644 --- a/src/infrastructure/events/constant/PhabricatorEventType.php +++ b/src/infrastructure/events/constant/PhabricatorEventType.php @@ -27,6 +27,4 @@ final class PhabricatorEventType extends PhutilEventType { const TYPE_AUTH_WILLLOGINUSER = 'auth.willLoginUser'; const TYPE_AUTH_DIDVERIFYEMAIL = 'auth.didVerifyEmail'; - const TYPE_SEARCH_DIDUPDATEINDEX = 'search.didUpdateIndex'; - } diff --git a/src/infrastructure/internationalization/translation/PhabricatorPirateEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorPirateEnglishTranslation.php new file mode 100644 index 0000000000..35b80c9483 --- /dev/null +++ b/src/infrastructure/internationalization/translation/PhabricatorPirateEnglishTranslation.php @@ -0,0 +1,65 @@ + 'Scour', + 'Review Code' => 'Inspect Riggins', + 'Tasks and Bugs' => 'Bilge rats', + 'Cancel' => 'Belay', + 'Advanced Search' => 'Scour Hard', + 'No search results.' => 'We be finding nothin.', + 'Send' => 'Aye!', + 'Partial' => 'Parrtial', + 'Upload' => 'Hoist', + 'Partial Upload' => 'Parrtial Hoist', + 'Submit' => 'Aye!', + 'Create' => 'Make Sail', + 'Okay' => 'Ahoy!', + 'Edit Query' => 'Overhaul Query', + 'Hide Query' => 'Furl Query', + 'Execute Query' => 'Quarter Query', + 'Wiki' => 'Sea Log', + 'Blog' => 'Capn\'s Tales', + 'Add Action...' => 'Be Addin\' an Action...', + 'Change Subscribers' => 'Change Spies', + 'Change Projects' => 'Change Prrojects', + 'Change Priority' => 'Change Priarrrity', + 'Change Status' => 'Change Ye Status', + 'Assign / Claim' => 'Arrsign / Stake Claim', + 'Prototype' => 'Ramshackle', + 'Continue' => 'Set Sail', + 'Recent Activity' => 'Recent Plunderin\'s', + 'Browse and Audit Commits' => 'Inspect ye Work of Yore', + 'Upcoming Events' => 'Upcoming Pillages', + 'Get Organized' => 'Straighten yer gig', + 'Host and Browse Repositories' => 'Hide ye Treasures', + 'Chat with Others' => 'Parley with yer Mates', + 'Review Recent Activity' => 'Spy ye Freshest Bottles', + 'Comment' => 'Scrawl', + 'Actions' => 'Actions! Arrr!', + 'Title' => 'Ye Olde Title', + 'Assigned To' => 'Matey Assigned', + 'Status' => 'Ye Status', + 'Priority' => 'Priarrrity', + 'Description' => 'Splainin\'', + 'Visible To' => 'Bein\' Spied By', + 'Editable By' => 'Plunderable By', + 'Subscribers' => 'Spies', + 'Projects' => 'Prrojects', + '%s added a comment.' => '%s scrawled.', + '%s edited the task description.' => + 'Cap\'n %s be updatin\' ye task\'s splainin\'.', + '%s claimed this task.' => + 'Cap\'n %s be stakin\' a claim on this here task.', + '%s created this task.' => + 'Cap\'n %s be the one creatin\' this here task.', + ); + } +} diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 79c9ef567a..ff4174522c 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -766,14 +766,22 @@ final class PhabricatorUSEnglishTranslation ), ), - '%d project hashtag(s) are already used: %s.' => array( - 'Project hashtag %2$s is already used.', - '%d project hashtags are already used: %2$s.', + '%s project hashtag(s) are already used by other projects: %s.' => array( + 'Project hashtag "%2$s" is already used by another project.', + 'Some project hashtags are already used by other projects: %2$s.', ), '%s changed project hashtag(s), added %d: %s; removed %d: %s.' => '%s changed project hashtags, added %3$s; removed %5$s.', + 'Hashtags must contain at least one letter or number. %s '. + 'project hashtag(s) are invalid: %s.' => array( + 'Hashtags must contain at least one letter or number. The '. + 'hashtag "%2$s" is not valid.', + 'Hashtags must contain at least one letter or number. These '. + 'hashtags are invalid: %2$s.', + ), + '%s added %d project hashtag(s): %s.' => array( array( '%s added a hashtag: %3$s.', diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 606fa6e9ff..58df7aea33 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -26,6 +26,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery private $edgeLogicConstraintsAreValid = false; private $spacePHIDs; private $spaceIsArchived; + private $ngrams = array(); protected function getPageCursors(array $page) { return array( @@ -253,6 +254,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $joins = array(); $joins[] = $this->buildEdgeLogicJoinClause($conn); $joins[] = $this->buildApplicationSearchJoinClause($conn); + $joins[] = $this->buildNgramsJoinClause($conn); return $joins; } @@ -274,6 +276,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $where[] = $this->buildPagingClause($conn); $where[] = $this->buildEdgeLogicWhereClause($conn); $where[] = $this->buildSpacesWhereClause($conn); + $where[] = $this->buildNgramsWhereClause($conn); return $where; } @@ -324,6 +327,10 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery return true; } + if ($this->shouldGroupNgramResultRows()) { + return true; + } + return false; } @@ -1345,6 +1352,138 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } +/* -( Ngrams )------------------------------------------------------------- */ + + + protected function withNgramsConstraint( + PhabricatorSearchNgrams $index, + $value) { + + if (strlen($value)) { + $this->ngrams[] = array( + 'index' => $index, + 'value' => $value, + 'length' => count(phutil_utf8v($value)), + ); + } + + return $this; + } + + + protected function buildNgramsJoinClause(AphrontDatabaseConnection $conn) { + $flat = array(); + foreach ($this->ngrams as $spec) { + $index = $spec['index']; + $value = $spec['value']; + $length = $spec['length']; + + if ($length >= 3) { + $ngrams = $index->getNgramsFromString($value, 'query'); + $prefix = false; + } else if ($length == 2) { + $ngrams = $index->getNgramsFromString($value, 'prefix'); + $prefix = false; + } else { + $ngrams = array(' '.$value); + $prefix = true; + } + + foreach ($ngrams as $ngram) { + $flat[] = array( + 'table' => $index->getTableName(), + 'ngram' => $ngram, + 'prefix' => $prefix, + ); + } + } + + // MySQL only allows us to join a maximum of 61 tables per query. Each + // ngram is going to cost us a join toward that limit, so if the user + // specified a very long query string, just pick 16 of the ngrams + // at random. + if (count($flat) > 16) { + shuffle($flat); + $flat = array_slice($flat, 0, 16); + } + + $alias = $this->getPrimaryTableAlias(); + if ($alias) { + $id_column = qsprintf($conn, '%T.%T', $alias, 'id'); + } else { + $id_column = qsprintf($conn, '%T', 'id'); + } + + $idx = 1; + $joins = array(); + foreach ($flat as $spec) { + $table = $spec['table']; + $ngram = $spec['ngram']; + $prefix = $spec['prefix']; + + $alias = 'ngm'.$idx++; + + if ($prefix) { + $joins[] = qsprintf( + $conn, + 'JOIN %T %T ON %T.objectID = %Q AND %T.ngram LIKE %>', + $table, + $alias, + $alias, + $id_column, + $alias, + $ngram); + } else { + $joins[] = qsprintf( + $conn, + 'JOIN %T %T ON %T.objectID = %Q AND %T.ngram = %s', + $table, + $alias, + $alias, + $id_column, + $alias, + $ngram); + } + } + + return $joins; + } + + + protected function buildNgramsWhereClause(AphrontDatabaseConnection $conn) { + $where = array(); + + foreach ($this->ngrams as $ngram) { + $index = $ngram['index']; + $value = $ngram['value']; + + $column = $index->getColumnName(); + $alias = $this->getPrimaryTableAlias(); + if ($alias) { + $column = qsprintf($conn, '%T.%T', $alias, $column); + } else { + $column = qsprintf($conn, '%T', $column); + } + + $tokens = $index->tokenizeString($value); + foreach ($tokens as $token) { + $where[] = qsprintf( + $conn, + '%Q LIKE %~', + $column, + $token); + } + } + + return $where; + } + + + protected function shouldGroupNgramResultRows() { + return (bool)$this->ngrams; + } + + /* -( Edge Logic )--------------------------------------------------------- */ diff --git a/src/infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php b/src/infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php index 50a7f9d105..2624427dc8 100644 --- a/src/infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php +++ b/src/infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php @@ -24,6 +24,11 @@ final class PhabricatorStorageFixtureScopeGuard extends Phobject { public function destroy() { PhabricatorLiskDAO::popStorageNamespace(); + // NOTE: We need to close all connections before destroying the databases. + // If we do not, the "DROP DATABASE ..." statements may hang, waiting for + // our connections to close. + PhabricatorLiskDAO::closeAllConnections(); + execx( 'php %s destroy --force --namespace %s', $this->getStorageBinPath(), diff --git a/src/infrastructure/util/PhabricatorSlug.php b/src/infrastructure/util/PhabricatorSlug.php index fd169914fe..c977c21d70 100644 --- a/src/infrastructure/util/PhabricatorSlug.php +++ b/src/infrastructure/util/PhabricatorSlug.php @@ -8,6 +8,11 @@ final class PhabricatorSlug extends Phobject { return rtrim($slug, '/'); } + public static function isValidProjectSlug($slug) { + $slug = self::normalizeProjectSlug($slug); + return ($slug != '_'); + } + public static function normalize($slug, $hashtag = false) { $slug = preg_replace('@/+@', '/', $slug); $slug = trim($slug, '/'); diff --git a/src/view/form/control/AphrontFormPolicyControl.php b/src/view/form/control/AphrontFormPolicyControl.php index 4201091d2b..348dc8171f 100644 --- a/src/view/form/control/AphrontFormPolicyControl.php +++ b/src/view/form/control/AphrontFormPolicyControl.php @@ -272,6 +272,7 @@ final class AphrontFormPolicyControl extends AphrontFormControl { 'capability' => $this->capability, 'editURI' => '/policy/edit/'.$context_path, 'customPlaceholder' => $this->getCustomPolicyPlaceholder(), + 'disabled' => $this->getDisabled(), )); $selected = idx($flat_options, $this->getValue(), array()); @@ -350,6 +351,7 @@ final class AphrontFormPolicyControl extends AphrontFormControl { $viewer, $space_phid), array( + 'disabled' => ($this->getDisabled() ? 'disabled' : null), 'name' => 'spacePHID', 'class' => 'aphront-space-select-control-knob', )); diff --git a/src/view/form/control/AphrontFormTokenizerControl.php b/src/view/form/control/AphrontFormTokenizerControl.php index 4994d89834..3f24dd1348 100644 --- a/src/view/form/control/AphrontFormTokenizerControl.php +++ b/src/view/form/control/AphrontFormTokenizerControl.php @@ -112,6 +112,7 @@ final class AphrontFormTokenizerControl extends AphrontFormControl { 'username' => $username, 'placeholder' => $placeholder, 'browseURI' => $browse_uri, + 'disabled' => $this->getDisabled(), )); } diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php index ad847a6c6a..45e9286d04 100644 --- a/src/view/form/control/PhabricatorRemarkupControl.php +++ b/src/view/form/control/PhabricatorRemarkupControl.php @@ -31,14 +31,18 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { // currently ship JS or CSS. require_celerity_resource('lightbox-attachment-css'); - Javelin::initBehavior( - 'aphront-drag-and-drop-textarea', - array( - 'target' => $id, - 'activatedClass' => 'aphront-textarea-drag-and-drop', - 'uri' => '/file/dropupload/', - 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(), - )); + if (!$this->getDisabled()) { + Javelin::initBehavior( + 'aphront-drag-and-drop-textarea', + array( + 'target' => $id, + 'activatedClass' => 'aphront-textarea-drag-and-drop', + 'uri' => '/file/dropupload/', + 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(), + )); + } + + $root_id = celerity_generate_unique_node_id(); Javelin::initBehavior( 'phabricator-remarkup-assist', @@ -53,39 +57,51 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { 'name' => pht('name'), 'URL' => pht('URL'), ), + 'disabled' => $this->getDisabled(), + 'rootID' => $root_id, )); Javelin::initBehavior('phabricator-tooltips', array()); $actions = array( 'fa-bold' => array( 'tip' => pht('Bold'), + 'nodevice' => true, ), 'fa-italic' => array( 'tip' => pht('Italics'), + 'nodevice' => true, ), 'fa-text-width' => array( 'tip' => pht('Monospaced'), + 'nodevice' => true, ), 'fa-link' => array( 'tip' => pht('Link'), + 'nodevice' => true, ), array( 'spacer' => true, + 'nodevice' => true, ), 'fa-list-ul' => array( 'tip' => pht('Bulleted List'), + 'nodevice' => true, ), 'fa-list-ol' => array( 'tip' => pht('Numbered List'), + 'nodevice' => true, ), 'fa-code' => array( 'tip' => pht('Code Block'), + 'nodevice' => true, ), 'fa-quote-right' => array( 'tip' => pht('Quote'), + 'nodevice' => true, ), 'fa-table' => array( 'tip' => pht('Table'), + 'nodevice' => true, ), 'fa-cloud-upload' => array( 'tip' => pht('Upload File'), @@ -111,11 +127,22 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { ); } + $actions['fa-eye'] = array( + 'tip' => pht('Preview'), + 'align' => 'right', + ); + + $actions[] = array( + 'spacer' => true, + 'align' => 'right', + ); + $actions['fa-life-bouy'] = array( - 'tip' => pht('Help'), - 'align' => 'right', - 'href' => PhabricatorEnv::getDoclink('Remarkup Reference'), - ); + 'tip' => pht('Help'), + 'align' => 'right', + 'href' => PhabricatorEnv::getDoclink('Remarkup Reference'), + ); + if (!$this->disableFullScreen) { $actions[] = array( @@ -138,6 +165,10 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { $classes[] = 'remarkup-assist-right'; } + if (idx($spec, 'nodevice')) { + $classes[] = 'remarkup-assist-nodevice'; + } + if (idx($spec, 'spacer')) { $classes[] = 'remarkup-assist-separator'; $buttons[] = phutil_tag( @@ -175,12 +206,18 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { $tip); } + $sigils = array(); + $sigils[] = 'remarkup-assist'; + if (!$this->getDisabled()) { + $sigils[] = 'has-tooltip'; + } + $buttons[] = javelin_tag( 'a', array( 'class' => implode(' ', $classes), 'href' => $href, - 'sigil' => 'remarkup-assist has-tooltip', + 'sigil' => implode(' ', $sigils), 'meta' => $meta, 'mustcapture' => $mustcapture, 'target' => $target, @@ -220,6 +257,8 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { 'div', array( 'sigil' => 'remarkup-assist-control', + 'class' => $this->getDisabled() ? 'disabled-control' : null, + 'id' => $root_id, ), array( $buttons, diff --git a/src/view/phui/PHUIBigInfoView.php b/src/view/phui/PHUIBigInfoView.php new file mode 100644 index 0000000000..03e1aea196 --- /dev/null +++ b/src/view/phui/PHUIBigInfoView.php @@ -0,0 +1,95 @@ +icon = $icon; + return $this; + } + + public function setTitle($title) { + $this->title = $title; + return $this; + } + + public function setDescription($description) { + $this->description = $description; + return $this; + } + + public function addAction(PHUIButtonView $button) { + $this->actions[] = $button; + return $this; + } + + protected function getTagName() { + return 'div'; + } + + protected function getTagAttributes() { + $classes = array(); + $classes[] = 'phui-big-info-view'; + + return array( + 'class' => implode(' ', $classes), + ); + } + + protected function getTagContent() { + require_celerity_resource('phui-big-info-view-css'); + + $icon = id(new PHUIIconView()) + ->setIconFont($this->icon) + ->addClass('phui-big-info-icon'); + + $icon = phutil_tag( + 'div', + array( + 'class' => 'phui-big-info-icon-container', + ), + $icon); + + $title = phutil_tag( + 'div', + array( + 'class' => 'phui-big-info-title', + ), + $this->title); + + $description = phutil_tag( + 'div', + array( + 'class' => 'phui-big-info-description', + ), + $this->description); + + $buttons = array(); + foreach ($this->actions as $button) { + $buttons[] = phutil_tag( + 'div', + array( + 'class' => 'phui-big-info-button', + ), + $button); + } + + $actions = null; + if ($buttons) { + $actions = phutil_tag( + 'div', + array( + 'class' => 'phui-big-info-actions', + ), + $buttons); + } + + return array($icon, $title, $description, $actions); + + } + +} diff --git a/src/view/phui/PHUIDocumentSummaryView.php b/src/view/phui/PHUIDocumentSummaryView.php index 8b4ad26dd5..0e85e9292f 100644 --- a/src/view/phui/PHUIDocumentSummaryView.php +++ b/src/view/phui/PHUIDocumentSummaryView.php @@ -90,7 +90,15 @@ final class PHUIDocumentSummaryView extends AphrontTagView { ), $this->summary); - return array($header, $subtitle, $body); + $read_more = phutil_tag( + 'a', + array( + 'class' => 'phui-document-read-more', + 'href' => $this->href, + ), + pht('Read more...')); + + return array($header, $subtitle, $body, $read_more); } } diff --git a/src/view/phui/PHUIListItemView.php b/src/view/phui/PHUIListItemView.php index e2e347306f..e877049840 100644 --- a/src/view/phui/PHUIListItemView.php +++ b/src/view/phui/PHUIListItemView.php @@ -28,6 +28,7 @@ final class PHUIListItemView extends AphrontTagView { private $order; private $aural; private $profileImage; + private $indented; public function setDropdownMenu(PhabricatorActionListView $actions) { Javelin::initBehavior('phui-dropdown-menu'); @@ -91,6 +92,15 @@ final class PHUIListItemView extends AphrontTagView { return $this->icon; } + public function setIndented($indented) { + $this->indented = $indented; + return $this; + } + + public function getIndented() { + return $this->indented; + } + public function setKey($key) { $this->key = (string)$key; return $this; @@ -256,11 +266,20 @@ final class PHUIListItemView extends AphrontTagView { ->setIconFont($this->appIcon); } + $classes = array(); + if ($this->href) { + $classes[] = 'phui-list-item-href'; + } + + if ($this->indented) { + $classes[] = 'phui-list-item-indented'; + } + return javelin_tag( $this->href ? 'a' : 'div', array( 'href' => $this->href, - 'class' => $this->href ? 'phui-list-item-href' : null, + 'class' => implode(' ', $classes), 'meta' => $meta, 'sigil' => $sigil, ), diff --git a/src/view/phui/PHUIWorkboardView.php b/src/view/phui/PHUIWorkboardView.php index 04a0941d70..45061f25fe 100644 --- a/src/view/phui/PHUIWorkboardView.php +++ b/src/view/phui/PHUIWorkboardView.php @@ -3,8 +3,6 @@ final class PHUIWorkboardView extends AphrontTagView { private $panels = array(); - private $fluidLayout = false; - private $fluidishLayout = false; private $actions = array(); public function addPanel(PHUIWorkpanelView $panel) { @@ -12,21 +10,6 @@ final class PHUIWorkboardView extends AphrontTagView { return $this; } - public function setFluidLayout($layout) { - $this->fluidLayout = $layout; - return $this; - } - - public function setFluidishLayout($layout) { - $this->fluidishLayout = $layout; - return $this; - } - - public function addAction(PHUIIconView $action) { - $this->actions[] = $action; - return $this; - } - protected function getTagAttributes() { return array( 'class' => 'phui-workboard-view', @@ -36,33 +19,8 @@ final class PHUIWorkboardView extends AphrontTagView { protected function getTagContent() { require_celerity_resource('phui-workboard-view-css'); - $action_list = null; - if (!empty($this->actions)) { - $items = array(); - foreach ($this->actions as $action) { - $items[] = phutil_tag( - 'li', - array( - 'class' => 'phui-workboard-action-item', - ), - $action); - } - $action_list = phutil_tag( - 'ul', - array( - 'class' => 'phui-workboard-action-list', - ), - $items); - } - $view = new AphrontMultiColumnView(); $view->setGutter(AphrontMultiColumnView::GUTTER_MEDIUM); - if ($this->fluidLayout) { - $view->setFluidLayout($this->fluidLayout); - } - if ($this->fluidishLayout) { - $view->setFluidishLayout($this->fluidishLayout); - } foreach ($this->panels as $panel) { $view->addColumn($panel); } @@ -74,9 +32,6 @@ final class PHUIWorkboardView extends AphrontTagView { ), $view); - return array( - $action_list, - $board, - ); + return $board; } } diff --git a/webroot/rsrc/css/application/phame/phame.css b/webroot/rsrc/css/application/phame/phame.css index 0b1f42ac24..02d8761a6b 100644 --- a/webroot/rsrc/css/application/phame/phame.css +++ b/webroot/rsrc/css/application/phame/phame.css @@ -85,7 +85,7 @@ } .phame-blog-list-title:hover { - color: {$indigo}; + color: {$violet}; text-decoration: none; } @@ -120,12 +120,12 @@ } .phame-blog-list-new-post:hover { - color: {$indigo}; + color: {$violet}; text-decoration: none; } .phame-blog-list-new-post:hover .phame-blog-list-icon { - color: {$indigo}; + color: {$violet}; } .phame-blog-list-icon { diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 92c44d944d..98d6876e1d 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -498,6 +498,7 @@ var.remarkup-assist-textarea { height: auto; border-width: 1px 0 0 0; outline: none; + resize: none; } .phabricator-image-macro-hero { @@ -523,3 +524,39 @@ var.remarkup-assist-textarea { background-color: {$lightviolet}; padding: 0 4px; } + +.remarkup-inline-preview { + display: block; + position: relative; + background: #fff; + overflow-y: auto; + box-sizing: border-box; + width: 100%; + border: 1px solid {$sky}; + resize: vertical; + padding: 4px 6px; +} + +.remarkup-control-fullscreen-mode .remarkup-inline-preview { + resize: none; +} + +.remarkup-inline-preview * { + resize: none; +} + +.remarkup-assist-button.preview-active { + background: {$sky}; +} + +.remarkup-assist-button.preview-active .phui-icon-view { + color: #ffffff; +} + +.remarkup-assist-button.preview-active:hover .phui-icon-view { + color: {$lightsky}; +} + +.device .remarkup-assist-nodevice { + display: none; +} diff --git a/webroot/rsrc/css/phui/phui-big-info-view.css b/webroot/rsrc/css/phui/phui-big-info-view.css new file mode 100644 index 0000000000..3c92025d79 --- /dev/null +++ b/webroot/rsrc/css/phui/phui-big-info-view.css @@ -0,0 +1,37 @@ +/** + * @provides phui-big-info-view-css + */ + +.phui-big-info-view { + padding: 64px 32px; + margin: 16px 4px; + background-color: {$sh-greybackground}; + text-align: center; +} + +.phui-big-info-icon { + font-size: 48px; +} + +.phui-big-info-title { + font-size: 18px; + padding: 4px; + font-weight: bold; +} + +.phui-big-info-description { + font-size: {$biggerfontsize}; + padding: 4px; +} + +.phui-big-info-actions { + padding: 24px 0 0; +} + +.phui-big-info-button { + display: inline-block; +} + +.phui-big-info-button + .phui-big-info-button { + margin-left: 12px; +} diff --git a/webroot/rsrc/css/phui/phui-document-summary.css b/webroot/rsrc/css/phui/phui-document-summary.css index df484e0453..5b09bb9c7f 100644 --- a/webroot/rsrc/css/phui/phui-document-summary.css +++ b/webroot/rsrc/css/phui/phui-document-summary.css @@ -25,6 +25,21 @@ body .phui-document-view .phui-document-summary-view h2.remarkup-header { text-decoration: none; } +.phui-document-view .phui-document-summary-body div.phabricator-remarkup { + padding-bottom: 4px; +} + +a.phui-document-read-more { + color: {$lightgreytext}; + font-size: {$smallerfontsize}; + display: block; + padding-bottom: 12px; +} + +a.phui-document-read-more:hover { + color: {$violet}; +} + .phui-document-summary-subtitle { color: {$lightgreytext}; font-size: {$normalfontsize}; @@ -33,3 +48,7 @@ body .phui-document-view .phui-document-summary-view h2.remarkup-header { .phui-document-summary-subtitle a { color: {$darkbluetext}; } + +.phui-document-summary-subtitle a:hover { + color: {$violet}; +} diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css index 84ed19aad6..2ff68ba4f3 100644 --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -531,3 +531,12 @@ properly, and submit values. */ .aphront-form-preview-hidden { opacity: 0.5; } + +.aphront-form-error .phui-icon-view { + color: {$lightgreyborder}; + font-size: 16px; +} + +.device-desktop .aphront-form-error .phui-icon-view:hover { + color: {$darkgreyborder}; +} diff --git a/webroot/rsrc/css/phui/phui-form.css b/webroot/rsrc/css/phui/phui-form.css index cb8c0a7752..52f41e4ab6 100644 --- a/webroot/rsrc/css/phui/phui-form.css +++ b/webroot/rsrc/css/phui/phui-form.css @@ -142,7 +142,9 @@ textarea::-webkit-input-placeholder { } select[disabled], -input[disabled] { +input[disabled], +textarea[disabled], +.disabled-control { opacity: 0.5; } diff --git a/webroot/rsrc/css/phui/phui-list.css b/webroot/rsrc/css/phui/phui-list.css index 01d04f8b37..25504a611d 100644 --- a/webroot/rsrc/css/phui/phui-list.css +++ b/webroot/rsrc/css/phui/phui-list.css @@ -62,6 +62,7 @@ line-height: 18px; } +.phabricator-side-menu .phui-list-item-disabled .phui-list-item-href, .phui-list-sidenav .phui-list-item-disabled .phui-list-item-href { color: {$lightgreytext}; } @@ -70,6 +71,11 @@ padding: 4px 10px; } +.phui-list-sidenav .phui-list-item-has-icon .phui-list-item-indented { + padding-left: 18px; +} + + .device-desktop .phui-list-sidenav .phui-list-item-href:hover { background: {$sky}; color: white; diff --git a/webroot/rsrc/css/phui/phui-workboard-view.css b/webroot/rsrc/css/phui/phui-workboard-view.css index 2befbd7823..e6817c6254 100644 --- a/webroot/rsrc/css/phui/phui-workboard-view.css +++ b/webroot/rsrc/css/phui/phui-workboard-view.css @@ -37,54 +37,6 @@ background: {$lightbluetext}; } -.phui-workboard-action-list { - width: 60px; - float: left; - min-height: 60px; - border-radius: 5px; - margin-right: 8px; - background: {$lightbluebackground}; - border: 1px solid {$lightblueborder}; - border-bottom: 1px solid {$blueborder}; -} - -.device-phone .phui-workboard-action-list { - width: 100%; - float: none; - display: block; - overflow: hidden; - border: none; - background: none; - border-radius: none; - min-height: 0; -} - -.phui-workboard-action-list li:first-child { - padding-top: 5px; -} - -.device-phone .phui-workboard-action-list li { - display: inline; - float: left; - margin: 0; - padding: 0 0 8px 0; -} - -.phui-workboard-action-list .phui-icon-view { - display: inline-block; - vertical-align: top; - width: 50px; - height: 50px; - margin: 0 4px 5px 5px; -} - -.device-phone .phui-workboard-action-list .phui-icon-view { - background-size: 35px; - height: 35px; - width: 35px; - margin: 0 3px; -} - .device-desktop .project-board-wrapper .phui-workboard-view-shadow { left: 53px; } diff --git a/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js b/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js index 34af63992c..a60c91e528 100644 --- a/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js +++ b/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js @@ -46,7 +46,8 @@ JX.install('Tokenizer', { properties : { limit : null, renderTokenCallback : null, - browseURI: null + browseURI: null, + disabled: false }, members : { @@ -65,6 +66,11 @@ JX.install('Tokenizer', { _placeholder : null, start : function() { + if (this.getDisabled()) { + JX.DOM.alterClass(this._containerNode, 'disabled-control', true); + return; + } + if (__DEV__) { if (!this._typeahead) { throw new Error( diff --git a/webroot/rsrc/js/application/policy/behavior-policy-control.js b/webroot/rsrc/js/application/policy/behavior-policy-control.js index ea66761191..9696a09dc8 100644 --- a/webroot/rsrc/js/application/policy/behavior-policy-control.js +++ b/webroot/rsrc/js/application/policy/behavior-policy-control.js @@ -14,6 +14,14 @@ JX.behavior('policy-control', function(config) { var input = JX.$(config.inputID); var value = config.value; + if (config.disabled) { + JX.DOM.alterClass(control, 'disabled-control', true); + JX.DOM.listen(control, 'click', null, function(e) { + e.kill(); + }); + return; + } + var menu = new JX.PHUIXDropdownMenu(control) .setWidth(260) .setAlign('left'); diff --git a/webroot/rsrc/js/application/transactions/behavior-comment-actions.js b/webroot/rsrc/js/application/transactions/behavior-comment-actions.js index c121111077..e9204772c4 100644 --- a/webroot/rsrc/js/application/transactions/behavior-comment-actions.js +++ b/webroot/rsrc/js/application/transactions/behavior-comment-actions.js @@ -6,6 +6,7 @@ * javelin-dom * phuix-form-control-view * phuix-icon-view + * javelin-behavior-phabricator-gesture */ JX.behavior('comment-actions', function(config) { @@ -60,13 +61,35 @@ JX.behavior('comment-actions', function(config) { .setControl(action.type, action.spec); var node = control.getNode(); + JX.Stratcom.addSigil(node, 'touchable'); + + var remove_action = function() { + JX.DOM.remove(node); + delete rows[action.key]; + option.disabled = false; + }; + + JX.DOM.listen(node, 'gesture.swipe.end', null, function(e) { + var data = e.getData(); + + if (data.direction != 'left') { + // Didn't swipe left. + return; + } + + if (data.length <= (JX.Vector.getDim(node).x / 2)) { + // Didn't swipe far enough. + return; + } + + remove_action(); + }); + rows[action.key] = control; JX.DOM.listen(remove, 'click', null, function(e) { e.kill(); - JX.DOM.remove(node); - delete rows[action.key]; - option.disabled = false; + remove_action(); }); place_node.parentNode.insertBefore(node, place_node); @@ -115,6 +138,10 @@ JX.behavior('comment-actions', function(config) { } function onresponse(response) { + if (JX.Device.getDevice() != 'desktop') { + return; + } + var panel = JX.$(config.panelID); if (!response.xactions.length) { JX.DOM.hide(panel); @@ -152,7 +179,25 @@ JX.behavior('comment-actions', function(config) { JX.DOM.listen(form_node, 'shouldRefresh', null, always_trigger); request.start(); + + var ondevicechange = function() { + var panel = JX.$(config.panelID); + if (JX.Device.getDevice() == 'desktop') { + request.setRateLimit(500); + always_trigger(); + } else { + // On mobile, don't show live previews and only save drafts every + // 10 seconds. + request.setRateLimit(10000); + JX.DOM.hide(panel); + } + }; + + ondevicechange(); + + JX.Stratcom.listen('phabricator-device-change', null, ondevicechange); } restore_draft_actions(config.drafts || []); + }); diff --git a/webroot/rsrc/js/core/Prefab.js b/webroot/rsrc/js/core/Prefab.js index 347c2b7e48..d48b295f0d 100644 --- a/webroot/rsrc/js/core/Prefab.js +++ b/webroot/rsrc/js/core/Prefab.js @@ -240,6 +240,10 @@ JX.install('Prefab', { tokenizer.setBrowseURI(config.browseURI); } + if (config.disabled) { + tokenizer.setDisabled(true); + } + JX.Stratcom.addData(root, {'tokenizer' : tokenizer}); return { diff --git a/webroot/rsrc/js/core/TextAreaUtils.js b/webroot/rsrc/js/core/TextAreaUtils.js index b96099e0e5..f8c84c97a7 100644 --- a/webroot/rsrc/js/core/TextAreaUtils.js +++ b/webroot/rsrc/js/core/TextAreaUtils.js @@ -38,14 +38,21 @@ JX.install('TextAreaUtils', { } }, - setSelectionText : function(area, text) { + setSelectionText : function(area, text, select) { var v = area.value; var r = JX.TextAreaUtils.getSelectionRange(area); v = v.substring(0, r.start) + text + v.substring(r.end, v.length); area.value = v; - JX.TextAreaUtils.setSelectionRange(area, r.start, r.start + text.length); + var start = r.start; + var end = r.start + text.length; + + if (!select) { + start = end; + } + + JX.TextAreaUtils.setSelectionRange(area, start, end); }, /** 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 03d3d5f54f..7ee536edb4 100644 --- a/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js +++ b/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js @@ -11,18 +11,18 @@ JX.behavior('aphront-drag-and-drop-textarea', function(config) { var target = JX.$(config.target); function onupload(f) { - var text = JX.TextAreaUtils.getSelectionText(target); var ref = '{F' + f.getID() + '}'; - // If the user has dragged and dropped multiple files, we'll get an event - // each time an upload completes. Rather than overwriting the first - // reference, append the new reference if the selected text looks like an - // existing file reference. - if (text.match(/^\{F/)) { - ref = text + '\n\n' + ref; + // 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); + JX.TextAreaUtils.setSelectionText(target, ref, false); } if (JX.PhabricatorDragAndDropFileUpload.isSupported()) { diff --git a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js index 266d2ecd37..293c44ed9b 100644 --- a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js +++ b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js @@ -11,9 +11,12 @@ JX.behavior('phabricator-remarkup-assist', function(config) { var pht = JX.phtize(config.pht); + var root = JX.$(config.rootID); + var area = JX.DOM.find(root, 'textarea'); var edit_mode = 'normal'; var edit_root = null; + var preview = null; function set_edit_mode(root, mode) { if (mode == edit_mode) { @@ -26,7 +29,16 @@ JX.behavior('phabricator-remarkup-assist', function(config) { JX.DOM.alterClass(edit_root, 'remarkup-control-fullscreen-mode', false); JX.DOM.alterClass(document.body, 'remarkup-fullscreen-mode', false); } - JX.DOM.find(edit_root, 'textarea').style.height = ''; + + area.style.height = ''; + + // If we're in preview mode, kick the preview back down to default + // size. + if (preview) { + JX.DOM.show(area); + resize_preview(); + JX.DOM.hide(area); + } } edit_root = root; @@ -36,10 +48,21 @@ JX.behavior('phabricator-remarkup-assist', function(config) { if (mode == 'fa-arrows-alt') { JX.DOM.alterClass(edit_root, 'remarkup-control-fullscreen-mode', true); JX.DOM.alterClass(document.body, 'remarkup-fullscreen-mode', true); + + // If we're in preview mode, expand the preview to full-size. + if (preview) { + JX.DOM.show(area); + } + resizearea(); + + if (preview) { + resize_preview(); + JX.DOM.hide(area); + } } - JX.DOM.focus(JX.DOM.find(edit_root, 'textarea')); + JX.DOM.focus(area); } function resizearea() { @@ -54,8 +77,6 @@ JX.behavior('phabricator-remarkup-assist', function(config) { // "top" and "bottom", and height "auto" renders as two lines high. Force // it to the correct height with Javascript. - var area = JX.DOM.find(edit_root, 'textarea'); - var v = JX.Vector.getViewport(); v.x = null; v.y -= 26; @@ -65,7 +86,6 @@ JX.behavior('phabricator-remarkup-assist', function(config) { JX.Stratcom.listen('resize', null, resizearea); - JX.Stratcom.listen('keydown', null, function(e) { if (e.getSpecialKey() != 'esc') { return; @@ -81,7 +101,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { function update(area, l, m, r) { // Replace the selection with the entire assisted text. - JX.TextAreaUtils.setSelectionText(area, l + m + r); + JX.TextAreaUtils.setSelectionText(area, l + m + r, true); // Now, select just the middle part. For instance, if the user clicked // "B" to create bold text, we insert '**bold**' but just select the word @@ -115,7 +135,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { return sel.join('\n' + ch); } - function assist(area, action, root) { + function assist(area, action, root, button) { // If the user has some text selected, we'll try to use that (for example, // if they have a word selected and want to bold it). Otherwise we'll insert // generic text. @@ -182,11 +202,81 @@ JX.behavior('phabricator-remarkup-assist', function(config) { set_edit_mode(root, 'fa-arrows-alt'); } break; + case 'fa-eye': + if (!preview) { + preview = JX.$N( + 'div', + { + className: 'remarkup-inline-preview' + }, + null); + + area.parentNode.insertBefore(preview, area); + JX.DOM.alterClass(button, 'preview-active', true); + resize_preview(); + JX.DOM.hide(area); + + update_preview(); + } else { + JX.DOM.show(area); + resize_preview(true); + JX.DOM.remove(preview); + preview = null; + + JX.DOM.alterClass(button, 'preview-active', false); + } + break; } } - JX.Stratcom.listen( - ['click'], + function resize_preview(restore) { + if (!preview) { + return; + } + + var src; + var dst; + + if (restore) { + src = preview; + dst = area; + } else { + src = area; + dst = preview; + } + + var d = JX.Vector.getDim(src); + d.x = null; + d.setDim(dst); + } + + function update_preview() { + var value = area.value; + + var data = { + corpus: value + }; + + var onupdate = function(r) { + if (area.value !== value) { + return; + } + + if (!preview) { + return; + } + + JX.DOM.setContent(preview, JX.$H(r.content).getFragment()); + }; + + new JX.Workflow('/transactions/remarkuppreview/', data) + .setHandler(onupdate) + .start(); + } + + JX.DOM.listen( + root, + 'click', 'remarkup-assist', function(e) { var data = e.getNodeData('remarkup-assist'); @@ -196,10 +286,11 @@ JX.behavior('phabricator-remarkup-assist', function(config) { e.kill(); - var root = e.getNode('remarkup-assist-control'); - var area = JX.DOM.find(root, 'textarea'); + if (config.disabled) { + return; + } - assist(area, data.action, root); + assist(area, data.action, root, e.getNode('remarkup-assist')); }); });