diff --git a/externals/octicons/LICENSE b/externals/octicons/LICENSE new file mode 100644 index 0000000000..4cf2020ce7 --- /dev/null +++ b/externals/octicons/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2012-2016 GitHub, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/externals/octicons/README.md b/externals/octicons/README.md new file mode 100644 index 0000000000..84edd84dce --- /dev/null +++ b/externals/octicons/README.md @@ -0,0 +1,194 @@ +# GitHub Octicons + +[![NPM version](https://img.shields.io/npm/v/octicons.svg)](https://www.npmjs.org/package/octicons) +[![Build Status](https://travis-ci.org/primer/octicons.svg?branch=master)](https://travis-ci.org/primer/octicons) + +> Octicons are a scalable set of icons handcrafted with <3 by GitHub. + +## Install + +**NOTE:** The compiled files are located in `/build/`. This directory is located in the published npm package. Which means you can access it when you `npm install octicons`. You can also build this directory by following the [building octicons directions](#building-octicons). The files in the `/lib/` directory are the raw source files and are not compiled or optimized. + +#### NPM + +This repository is distributed with [npm][npm]. After [installing npm][install-npm], you can install `octicons` with this command. + +``` +$ npm install --save octicons +``` + +## Usage + +For all the usages, we recommend using the CSS located in `./build/octicons.css`. This is some simple CSS to normalize the icons and inherit colors. + +### Spritesheet + +With a [SVG sprite icon system](https://css-tricks.com/svg-sprites-use-better-icon-fonts/) you can include the sprite sheet located `./build/sprite.octicons.svg` after you [build the icons](#building-octicons) or from the npm package. There is a demo of how to use the spritesheet in the build directory also. + +### Node + +After installing `npm install octicons` you can access the icons like this. + +```js +var octicons = require("octicons") +octicons.alert +// { keywords: [ 'warning', 'triangle', 'exclamation', 'point' ], +// path: '', +// height: '16', +// width: '16', +// symbol: 'alert', +// options: +// { version: '1.1', +// width: '16', +// height: '16', +// viewBox: '0 0 16 16', +// class: 'octicon octicon-alert', +// 'aria-hidden': 'true' }, +// toSVG: [Function] } +``` + +There will be a key for every icon, with `keywords` and `svg`. + +#### `octicons.alert.symbol` + +Returns the string of the symbol name + +```js +octicons.x.symbol +// "x" +``` + +#### `octicons.person.path` + +Path returns the string representation of the path of the icon. + +```js +octicons.x.path +// +``` + +#### `octicons.issue.options` + +This is a json object of all the `options` that will be added to the output tag. + +```js +octicons.x.options +// { version: '1.1', width: '12', height: '16', viewBox: '0 0 12 16', class: 'octicon octicon-x', 'aria-hidden': 'true' } +``` + +#### `octicons.alert.width` + +Width is the icon's true width. Based on the svg view box width. _Note, this doesn't change if you scale it up with size options, it only is the natural width of the icon_ + +#### `octicons.alert.height` + +Height is the icon's true height. Based on the svg view box height. _Note, this doesn't change if you scale it up with size options, it only is the natural height of the icon_ + +#### `keywords` + +Returns an array of keywords for the icon. The data [comes from the octicons repository](https://github.com/primer/octicons/blob/master/lib/data.json). Consider contributing more aliases for the icons. + +```js +octicons.x.keywords +// ["remove", "close", "delete"] +``` + +#### `octicons.alert.toSVG()` + +Returns a string of the svg tag + +```js +octicons.x.toSVG() +// +``` + +The `.toSVG()` method accepts an optional `options` object. This is used to add CSS classnames, a11y options, and sizing. + +##### class + +Add more CSS classes to the `` tag. + +```js +octicons.x.toSVG({ "class": "close" }) +// +``` + +##### aria-label + +Add accessibility `aria-label` to the icon. + +```js +octicons.x.toSVG({ "aria-label": "Close the window" }) +// +``` + +##### width & height + +Size the SVG icon larger using `width` & `height` independently or together. + +```js +octicons.x.toSVG({ "width": 45 }) +// +``` + +#### `octicons.alert.toSVGUse()` + +Returns a string of the svg tag with the `` tag, for use with the spritesheet located in the /build/ directory. + +```js +octicons.x.toSVGUse() +// +``` + +### Ruby + +If your environment is Ruby on Rails, we have a [octicons_helper](https://github.com/primer/octicons_helper) gem available that renders SVG in your page. The octicons_helper uses the [octicons_gem](https://github.com/primer/octicons_gem) to do the computing and reading of the SVG files. + +### Jekyll + +For jekyll, there's a [jekyll-octicons](https://github.com/primer/jekyll-octicons) plugin available. This works exactly like the octicons_helper. + +## Changing, adding, or deleting icons + +1. Open the [Sketch document][sketch-document] in `/lib/`. Each icon exists as an artboard within our master Sketch document. If you’re adding an icon, duplicate one of the artboards and add your shapes to it. Be sure to give your artboard a name that makes sense. +2. Once you’re happy with your icon set, choose File > Export… +3. Choose all the artboards you’d like to export and then press “Export” +4. Export to `/lib/svg/` + +You’ll next need to build your Octicons. + +## Building Octicons + +All the files you need will be in the `/build/` directory already, but if you’ve made changes to the `/lib/` directory and need to regenerate, follow these steps: + +1. Open the Octicons directory in Terminal +2. `npm install` to install all dependencies for the project. +3. Run the command `npm run build`. This will run the grunt task to build the SVGs, placing them in the `/build/` directory. + +## Publishing + +If you have access to publish this repository, these are the steps to publishing. If you need access, contact [#design-systems](https://github.slack.com/archives/design-systems). + +1. Update the [CHANGELOG.md](./CHANGELOG.md) with relevant version number and any updates made to the repository. +2. `npm version ` Run [npm version](https://docs.npmjs.com/cli/version) inputing the relevant version type. The versioning is [semver](http://semver.org/), so version appropriately based on what has changed. +3. `npm publish` This will publish the new version to npmjs.org +4. `git push && git push --tags` Push all these changes to origin. + +## License + +(c) 2012-2016 GitHub, Inc. + +When using the GitHub logos, be sure to follow the [GitHub logo guidelines](https://github.com/logos). + +_SVG License:_ [SIL OFL 1.1](http://scripts.sil.org/OFL) +Applies to all SVG files + +_Code License:_ [MIT](./LICENSE) +Applies to all other files + +[primer]: https://github.com/primer/primer +[docs]: http://primercss.io/ +[npm]: https://www.npmjs.com/ +[install-npm]: https://docs.npmjs.com/getting-started/installing-node +[sass]: http://sass-lang.com/ +[sketch-document]: https://github.com/primer/octicons/blob/master/lib/octicons-master.sketch diff --git a/resources/builtin/project.png b/resources/builtin/project.png index 1e16790ea8..c6abf5ed42 100644 Binary files a/resources/builtin/project.png and b/resources/builtin/project.png differ diff --git a/resources/builtin/projects/v3/archive.png b/resources/builtin/projects/v3/archive.png new file mode 100644 index 0000000000..77ff79e555 Binary files /dev/null and b/resources/builtin/projects/v3/archive.png differ diff --git a/resources/builtin/projects/v3/basic-book.png b/resources/builtin/projects/v3/basic-book.png new file mode 100644 index 0000000000..b2a6d99415 Binary files /dev/null and b/resources/builtin/projects/v3/basic-book.png differ diff --git a/resources/builtin/projects/v3/book.png b/resources/builtin/projects/v3/book.png new file mode 100644 index 0000000000..eceb0bfb4d Binary files /dev/null and b/resources/builtin/projects/v3/book.png differ diff --git a/resources/builtin/projects/v3/briefcase.png b/resources/builtin/projects/v3/briefcase.png new file mode 100644 index 0000000000..c6abf5ed42 Binary files /dev/null and b/resources/builtin/projects/v3/briefcase.png differ diff --git a/resources/builtin/projects/v3/bug.png b/resources/builtin/projects/v3/bug.png new file mode 100644 index 0000000000..bb2948a93a Binary files /dev/null and b/resources/builtin/projects/v3/bug.png differ diff --git a/resources/builtin/projects/v3/calendar.png b/resources/builtin/projects/v3/calendar.png new file mode 100644 index 0000000000..6ebdc2e08c Binary files /dev/null and b/resources/builtin/projects/v3/calendar.png differ diff --git a/resources/builtin/projects/v3/clipboard.png b/resources/builtin/projects/v3/clipboard.png new file mode 100644 index 0000000000..60e2acd4f4 Binary files /dev/null and b/resources/builtin/projects/v3/clipboard.png differ diff --git a/resources/builtin/projects/v3/cloud.png b/resources/builtin/projects/v3/cloud.png new file mode 100644 index 0000000000..efb644001f Binary files /dev/null and b/resources/builtin/projects/v3/cloud.png differ diff --git a/resources/builtin/projects/v3/contact.png b/resources/builtin/projects/v3/contact.png new file mode 100644 index 0000000000..6b3095dc3d Binary files /dev/null and b/resources/builtin/projects/v3/contact.png differ diff --git a/resources/builtin/projects/v3/creditcard.png b/resources/builtin/projects/v3/creditcard.png new file mode 100644 index 0000000000..d231c9437d Binary files /dev/null and b/resources/builtin/projects/v3/creditcard.png differ diff --git a/resources/builtin/projects/v3/database.png b/resources/builtin/projects/v3/database.png new file mode 100644 index 0000000000..9e44c0ec58 Binary files /dev/null and b/resources/builtin/projects/v3/database.png differ diff --git a/resources/builtin/projects/v3/desktop.png b/resources/builtin/projects/v3/desktop.png new file mode 100644 index 0000000000..cf6f80eeaf Binary files /dev/null and b/resources/builtin/projects/v3/desktop.png differ diff --git a/resources/builtin/projects/v3/discussion.png b/resources/builtin/projects/v3/discussion.png new file mode 100644 index 0000000000..e4519d664f Binary files /dev/null and b/resources/builtin/projects/v3/discussion.png differ diff --git a/resources/builtin/projects/v3/download.png b/resources/builtin/projects/v3/download.png new file mode 100644 index 0000000000..f086222212 Binary files /dev/null and b/resources/builtin/projects/v3/download.png differ diff --git a/resources/builtin/projects/v3/experimental.png b/resources/builtin/projects/v3/experimental.png new file mode 100644 index 0000000000..5bb05ac100 Binary files /dev/null and b/resources/builtin/projects/v3/experimental.png differ diff --git a/resources/builtin/projects/v3/flag.png b/resources/builtin/projects/v3/flag.png new file mode 100644 index 0000000000..c7d2563115 Binary files /dev/null and b/resources/builtin/projects/v3/flag.png differ diff --git a/resources/builtin/projects/v3/folder.png b/resources/builtin/projects/v3/folder.png new file mode 100644 index 0000000000..cac0f9fbfa Binary files /dev/null and b/resources/builtin/projects/v3/folder.png differ diff --git a/resources/builtin/projects/v3/gears.png b/resources/builtin/projects/v3/gears.png new file mode 100644 index 0000000000..ecbef1a9cf Binary files /dev/null and b/resources/builtin/projects/v3/gears.png differ diff --git a/resources/builtin/projects/v3/gold.png b/resources/builtin/projects/v3/gold.png new file mode 100644 index 0000000000..630f45bc61 Binary files /dev/null and b/resources/builtin/projects/v3/gold.png differ diff --git a/resources/builtin/projects/v3/home.png b/resources/builtin/projects/v3/home.png new file mode 100644 index 0000000000..808d639f0d Binary files /dev/null and b/resources/builtin/projects/v3/home.png differ diff --git a/resources/builtin/projects/v3/library.png b/resources/builtin/projects/v3/library.png new file mode 100644 index 0000000000..1f9ea1a64a Binary files /dev/null and b/resources/builtin/projects/v3/library.png differ diff --git a/resources/builtin/projects/v3/lightbulb.png b/resources/builtin/projects/v3/lightbulb.png new file mode 100644 index 0000000000..1aba0d32f3 Binary files /dev/null and b/resources/builtin/projects/v3/lightbulb.png differ diff --git a/resources/builtin/projects/v3/lock.png b/resources/builtin/projects/v3/lock.png new file mode 100644 index 0000000000..839cb1e5bf Binary files /dev/null and b/resources/builtin/projects/v3/lock.png differ diff --git a/resources/builtin/projects/v3/mail.png b/resources/builtin/projects/v3/mail.png new file mode 100644 index 0000000000..17f91ea881 Binary files /dev/null and b/resources/builtin/projects/v3/mail.png differ diff --git a/resources/builtin/projects/v3/manage.png b/resources/builtin/projects/v3/manage.png new file mode 100644 index 0000000000..7804360a29 Binary files /dev/null and b/resources/builtin/projects/v3/manage.png differ diff --git a/resources/builtin/projects/v3/marker.png b/resources/builtin/projects/v3/marker.png new file mode 100644 index 0000000000..c2c753a006 Binary files /dev/null and b/resources/builtin/projects/v3/marker.png differ diff --git a/resources/builtin/projects/v3/mobile.png b/resources/builtin/projects/v3/mobile.png new file mode 100644 index 0000000000..fbb1985015 Binary files /dev/null and b/resources/builtin/projects/v3/mobile.png differ diff --git a/resources/builtin/projects/v3/one-server.png b/resources/builtin/projects/v3/one-server.png new file mode 100644 index 0000000000..d8fbff8a17 Binary files /dev/null and b/resources/builtin/projects/v3/one-server.png differ diff --git a/resources/builtin/projects/v3/organization.png b/resources/builtin/projects/v3/organization.png new file mode 100644 index 0000000000..1957dd8e68 Binary files /dev/null and b/resources/builtin/projects/v3/organization.png differ diff --git a/resources/builtin/projects/v3/people.png b/resources/builtin/projects/v3/people.png new file mode 100644 index 0000000000..5bb42656df Binary files /dev/null and b/resources/builtin/projects/v3/people.png differ diff --git a/resources/builtin/projects/v3/piechart.png b/resources/builtin/projects/v3/piechart.png new file mode 100644 index 0000000000..4c707ed6ff Binary files /dev/null and b/resources/builtin/projects/v3/piechart.png differ diff --git a/resources/builtin/projects/v3/police-badge.png b/resources/builtin/projects/v3/police-badge.png new file mode 100644 index 0000000000..8b729bc35a Binary files /dev/null and b/resources/builtin/projects/v3/police-badge.png differ diff --git a/resources/builtin/projects/v3/purchase-order.png b/resources/builtin/projects/v3/purchase-order.png new file mode 100644 index 0000000000..906d72a529 Binary files /dev/null and b/resources/builtin/projects/v3/purchase-order.png differ diff --git a/resources/builtin/projects/v3/robot.png b/resources/builtin/projects/v3/robot.png new file mode 100644 index 0000000000..317544f2dd Binary files /dev/null and b/resources/builtin/projects/v3/robot.png differ diff --git a/resources/builtin/projects/v3/rocket.png b/resources/builtin/projects/v3/rocket.png new file mode 100644 index 0000000000..f5a758ff81 Binary files /dev/null and b/resources/builtin/projects/v3/rocket.png differ diff --git a/resources/builtin/projects/v3/server-documentation.png b/resources/builtin/projects/v3/server-documentation.png new file mode 100644 index 0000000000..d71dd53b72 Binary files /dev/null and b/resources/builtin/projects/v3/server-documentation.png differ diff --git a/resources/builtin/projects/v3/servers.png b/resources/builtin/projects/v3/servers.png new file mode 100644 index 0000000000..df20dd6492 Binary files /dev/null and b/resources/builtin/projects/v3/servers.png differ diff --git a/resources/builtin/projects/v3/shield.png b/resources/builtin/projects/v3/shield.png new file mode 100644 index 0000000000..fd938307fd Binary files /dev/null and b/resources/builtin/projects/v3/shield.png differ diff --git a/resources/builtin/projects/v3/silver.png b/resources/builtin/projects/v3/silver.png new file mode 100644 index 0000000000..afe0e84b35 Binary files /dev/null and b/resources/builtin/projects/v3/silver.png differ diff --git a/resources/builtin/projects/v3/sitemap.png b/resources/builtin/projects/v3/sitemap.png new file mode 100644 index 0000000000..8ee6e232b4 Binary files /dev/null and b/resources/builtin/projects/v3/sitemap.png differ diff --git a/resources/builtin/projects/v3/support.png b/resources/builtin/projects/v3/support.png new file mode 100644 index 0000000000..88a9746708 Binary files /dev/null and b/resources/builtin/projects/v3/support.png differ diff --git a/resources/builtin/projects/v3/sword.png b/resources/builtin/projects/v3/sword.png new file mode 100644 index 0000000000..30040633fd Binary files /dev/null and b/resources/builtin/projects/v3/sword.png differ diff --git a/resources/builtin/projects/v3/tag.png b/resources/builtin/projects/v3/tag.png new file mode 100644 index 0000000000..c91bac473a Binary files /dev/null and b/resources/builtin/projects/v3/tag.png differ diff --git a/resources/builtin/projects/v3/three-servers.png b/resources/builtin/projects/v3/three-servers.png new file mode 100644 index 0000000000..3361d8967b Binary files /dev/null and b/resources/builtin/projects/v3/three-servers.png differ diff --git a/resources/builtin/projects/v3/trash.png b/resources/builtin/projects/v3/trash.png new file mode 100644 index 0000000000..ce3f557d44 Binary files /dev/null and b/resources/builtin/projects/v3/trash.png differ diff --git a/resources/builtin/projects/v3/truck.png b/resources/builtin/projects/v3/truck.png new file mode 100644 index 0000000000..3c903ea58b Binary files /dev/null and b/resources/builtin/projects/v3/truck.png differ diff --git a/resources/builtin/projects/v3/two-servers.png b/resources/builtin/projects/v3/two-servers.png new file mode 100644 index 0000000000..d5d408d9ad Binary files /dev/null and b/resources/builtin/projects/v3/two-servers.png differ diff --git a/resources/builtin/projects/v3/umbrella.png b/resources/builtin/projects/v3/umbrella.png new file mode 100644 index 0000000000..98c7c12365 Binary files /dev/null and b/resources/builtin/projects/v3/umbrella.png differ diff --git a/resources/builtin/projects/v3/upload.png b/resources/builtin/projects/v3/upload.png new file mode 100644 index 0000000000..13e33507bb Binary files /dev/null and b/resources/builtin/projects/v3/upload.png differ diff --git a/resources/builtin/projects/v3/wand.png b/resources/builtin/projects/v3/wand.png new file mode 100644 index 0000000000..6de1cb55ab Binary files /dev/null and b/resources/builtin/projects/v3/wand.png differ diff --git a/resources/builtin/repo/building.png b/resources/builtin/repo/building.png new file mode 100644 index 0000000000..d3ab581fe8 Binary files /dev/null and b/resources/builtin/repo/building.png differ diff --git a/resources/builtin/repo/cloud.png b/resources/builtin/repo/cloud.png new file mode 100644 index 0000000000..51a40d6cbe Binary files /dev/null and b/resources/builtin/repo/cloud.png differ diff --git a/resources/builtin/repo/code.png b/resources/builtin/repo/code.png new file mode 100644 index 0000000000..aeedf4a6ce Binary files /dev/null and b/resources/builtin/repo/code.png differ diff --git a/resources/builtin/repo/commit.png b/resources/builtin/repo/commit.png new file mode 100644 index 0000000000..e6d251f095 Binary files /dev/null and b/resources/builtin/repo/commit.png differ diff --git a/resources/builtin/repo/database.png b/resources/builtin/repo/database.png new file mode 100644 index 0000000000..4c9ec543fa Binary files /dev/null and b/resources/builtin/repo/database.png differ diff --git a/resources/builtin/repo/desktop.png b/resources/builtin/repo/desktop.png new file mode 100644 index 0000000000..f75cc54e43 Binary files /dev/null and b/resources/builtin/repo/desktop.png differ diff --git a/resources/builtin/repo/gears.png b/resources/builtin/repo/gears.png new file mode 100644 index 0000000000..ecbef1a9cf Binary files /dev/null and b/resources/builtin/repo/gears.png differ diff --git a/resources/builtin/repo/globe.png b/resources/builtin/repo/globe.png new file mode 100644 index 0000000000..b9ca72ffa2 Binary files /dev/null and b/resources/builtin/repo/globe.png differ diff --git a/resources/builtin/repo/locked.png b/resources/builtin/repo/locked.png new file mode 100644 index 0000000000..34074731a0 Binary files /dev/null and b/resources/builtin/repo/locked.png differ diff --git a/resources/builtin/repo/microchip.png b/resources/builtin/repo/microchip.png new file mode 100644 index 0000000000..25da5810de Binary files /dev/null and b/resources/builtin/repo/microchip.png differ diff --git a/resources/builtin/repo/mobile.png b/resources/builtin/repo/mobile.png new file mode 100644 index 0000000000..5c59ad9b07 Binary files /dev/null and b/resources/builtin/repo/mobile.png differ diff --git a/resources/builtin/repo/repo-git.png b/resources/builtin/repo/repo-git.png new file mode 100644 index 0000000000..b8dfed8ad2 Binary files /dev/null and b/resources/builtin/repo/repo-git.png differ diff --git a/resources/builtin/repo/repo-hg.png b/resources/builtin/repo/repo-hg.png new file mode 100644 index 0000000000..d12c2e5339 Binary files /dev/null and b/resources/builtin/repo/repo-hg.png differ diff --git a/resources/builtin/repo/repo-svn.png b/resources/builtin/repo/repo-svn.png new file mode 100644 index 0000000000..702f36e794 Binary files /dev/null and b/resources/builtin/repo/repo-svn.png differ diff --git a/resources/builtin/repo/repo.png b/resources/builtin/repo/repo.png new file mode 100644 index 0000000000..b3706f91bb Binary files /dev/null and b/resources/builtin/repo/repo.png differ diff --git a/resources/builtin/repo/servers.png b/resources/builtin/repo/servers.png new file mode 100644 index 0000000000..3adb8a2fcd Binary files /dev/null and b/resources/builtin/repo/servers.png differ diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a0aa198418..6bf5a5003b 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,15 +7,15 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => 'ff161f2d', + 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '2611a3ba', - 'core.pkg.js' => 'fffe0122', + 'core.pkg.css' => '10b2368c', + 'core.pkg.js' => '6c085267', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '90b30783', - 'differential.pkg.js' => 'ddfeb49b', - 'diffusion.pkg.css' => 'b93d9b8c', - 'diffusion.pkg.js' => '84c8f8fd', + 'differential.pkg.css' => '45951e9e', + 'differential.pkg.js' => 'b71b8c5d', + 'diffusion.pkg.css' => 'a2d17c7d', + 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '4d48ee79', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '5ab2753f', @@ -25,35 +25,34 @@ return array( 'rsrc/audio/basic/tap.mp3' => 'fc2fd796', 'rsrc/audio/basic/ting.mp3' => '17660001', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', - 'rsrc/css/aphront/dark-console.css' => '53798a6d', - 'rsrc/css/aphront/dialog-view.css' => '685c7e2d', + 'rsrc/css/aphront/dark-console.css' => 'f7b071f1', + 'rsrc/css/aphront/dialog-view.css' => '6bfc244b', 'rsrc/css/aphront/list-filter-view.css' => '5d6f0526', 'rsrc/css/aphront/multi-column.css' => '84cc6640', - 'rsrc/css/aphront/notification.css' => '3f6c89c9', + 'rsrc/css/aphront/notification.css' => '457861ec', 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => 'faf6a6fc', - 'rsrc/css/aphront/table-view.css' => '34cf86b4', - 'rsrc/css/aphront/tokenizer.css' => '9a8cb501', + 'rsrc/css/aphront/table-view.css' => '8c9bbafe', + 'rsrc/css/aphront/tokenizer.css' => '15d5ff71', 'rsrc/css/aphront/tooltip.css' => '173b9431', - 'rsrc/css/aphront/typeahead-browse.css' => '8904346a', - 'rsrc/css/aphront/typeahead.css' => '8a84cc7d', + 'rsrc/css/aphront/typeahead-browse.css' => 'f2818435', + 'rsrc/css/aphront/typeahead.css' => 'a4a21016', 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', 'rsrc/css/application/auth/auth.css' => '0877ed6e', - 'rsrc/css/application/base/main-menu-view.css' => '865b827f', - 'rsrc/css/application/base/notification-menu.css' => '6a697e43', + 'rsrc/css/application/base/main-menu-view.css' => '7821ca89', + 'rsrc/css/application/base/notification-menu.css' => '10685bd4', 'rsrc/css/application/base/phui-theme.css' => '9f261c6b', - 'rsrc/css/application/base/standard-page-view.css' => 'eb5b80c5', + 'rsrc/css/application/base/standard-page-view.css' => '34ee718b', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', - 'rsrc/css/application/config/config-options.css' => '0ede4c9b', - 'rsrc/css/application/config/config-page.css' => 'c1d5121b', + 'rsrc/css/application/config/config-options.css' => '4615667b', 'rsrc/css/application/config/config-template.css' => '8f18fa41', - 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', + 'rsrc/css/application/config/setup-issue.css' => '7dae7f18', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 'rsrc/css/application/conpherence/color.css' => 'abb4c358', 'rsrc/css/application/conpherence/durable-column.css' => '89ea6bef', 'rsrc/css/application/conpherence/header-pane.css' => 'cb6f4e19', - 'rsrc/css/application/conpherence/menu.css' => '6953e7ec', + 'rsrc/css/application/conpherence/menu.css' => '69368e97', 'rsrc/css/application/conpherence/message-pane.css' => 'b0f55ecc', 'rsrc/css/application/conpherence/notification.css' => 'cef0a3fc', 'rsrc/css/application/conpherence/participant-pane.css' => '26a3ce56', @@ -62,20 +61,22 @@ return array( 'rsrc/css/application/countdown/timer.css' => '16c52f5c', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', - 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', + 'rsrc/css/application/diff/inline-comment-summary.css' => 'f23d4e8f', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => '41af6d25', + 'rsrc/css/application/differential/changeset-view.css' => 'bf84345b', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', - 'rsrc/css/application/differential/phui-inline-comment.css' => 'be663c95', + 'rsrc/css/application/differential/phui-inline-comment.css' => '65ae3bc2', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', - 'rsrc/css/application/diffusion/diffusion-readme.css' => '18bd3910', - 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', + 'rsrc/css/application/diffusion/diffusion-icons.css' => '0c15255e', + 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', + 'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec', + 'rsrc/css/application/diffusion/diffusion-source.css' => '69ac9399', + 'rsrc/css/application/diffusion/diffusion.css' => '45727264', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', - 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', + 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', 'rsrc/css/application/flag/flag.css' => 'bba8f811', 'rsrc/css/application/harbormaster/harbormaster.css' => 'f491c9f4', 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', @@ -86,10 +87,10 @@ return array( 'rsrc/css/application/maniphest/task-summary.css' => '11cc5344', 'rsrc/css/application/objectselector/object-selector.css' => '85ee8ce6', 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', - 'rsrc/css/application/paste/paste.css' => '1898e534', + 'rsrc/css/application/paste/paste.css' => '9fcc9773', 'rsrc/css/application/people/people-picture-menu-item.css' => 'a06f7f34', 'rsrc/css/application/people/people-profile.css' => '4df76faf', - 'rsrc/css/application/phame/phame.css' => 'b3a0b3a3', + 'rsrc/css/application/phame/phame.css' => '8cb3afcd', 'rsrc/css/application/pholio/pholio-edit.css' => '07676f51', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', 'rsrc/css/application/pholio/pholio.css' => 'ca89d380', @@ -102,84 +103,86 @@ return array( 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', 'rsrc/css/application/ponder/ponder-view.css' => 'fbd45f96', - 'rsrc/css/application/project/project-card-view.css' => '3d3c1f91', + 'rsrc/css/application/project/project-card-view.css' => '0010bb52', 'rsrc/css/application/project/project-view.css' => '792c9057', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', - 'rsrc/css/application/search/application-search-view.css' => '66ee5d46', - 'rsrc/css/application/search/search-results.css' => 'f87d23ad', + 'rsrc/css/application/search/application-search-view.css' => '787f5b76', + 'rsrc/css/application/search/search-results.css' => '505dd8cf', 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => '9f4cb463', - 'rsrc/css/core/remarkup.css' => '17c0fb37', + 'rsrc/css/core/core.css' => '1760853c', + 'rsrc/css/core/remarkup.css' => 'cad18339', 'rsrc/css/core/syntax.css' => 'cae95e89', - 'rsrc/css/core/z-index.css' => '0233d039', + 'rsrc/css/core/z-index.css' => '9d8f7c4b', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', 'rsrc/css/font/font-awesome.css' => 'e838e088', 'rsrc/css/font/font-lato.css' => 'c7ccd872', 'rsrc/css/font/phui-font-icon-base.css' => '870a7360', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', - 'rsrc/css/layout/phabricator-source-code-view.css' => '4383192f', + 'rsrc/css/layout/phabricator-source-code-view.css' => 'aea41829', + 'rsrc/css/phui/button/phui-button-bar.css' => 'f1ff5494', + 'rsrc/css/phui/button/phui-button-simple.css' => '8e1baf68', + 'rsrc/css/phui/button/phui-button.css' => '1863cc6e', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600', - 'rsrc/css/phui/calendar/phui-calendar-month.css' => '8e10e92c', - 'rsrc/css/phui/calendar/phui-calendar.css' => '477acfaa', - 'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '19f9369b', + 'rsrc/css/phui/calendar/phui-calendar-month.css' => '21154caf', + 'rsrc/css/phui/calendar/phui-calendar.css' => 'f1ddf11c', + 'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '628f59de', 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', - 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'f12cbc9f', + 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '7c8ec27a', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'bf094950', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', - 'rsrc/css/phui/phui-action-list.css' => 'c01858f4', - 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', + 'rsrc/css/phui/phui-action-list.css' => 'e7eba156', + 'rsrc/css/phui/phui-action-panel.css' => 'b4798122', 'rsrc/css/phui/phui-badge.css' => '22c0cf4f', - 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', - 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', - 'rsrc/css/phui/phui-box.css' => '269cbc99', - 'rsrc/css/phui/phui-button.css' => '8d23596a', + 'rsrc/css/phui/phui-basic-nav-view.css' => '98c11ab3', + 'rsrc/css/phui/phui-big-info-view.css' => 'acc3492c', + 'rsrc/css/phui/phui-box.css' => '9f3745fb', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', - 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', + 'rsrc/css/phui/phui-comment-form.css' => 'ac68149f', 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb', - 'rsrc/css/phui/phui-curtain-view.css' => '679743bb', - 'rsrc/css/phui/phui-document-pro.css' => '62c4dcbf', + 'rsrc/css/phui/phui-curtain-view.css' => 'ca363f15', + 'rsrc/css/phui/phui-document-pro.css' => '8af7ea27', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'c32e8dec', 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', 'rsrc/css/phui/phui-fontkit.css' => '1320ed01', - 'rsrc/css/phui/phui-form-view.css' => 'f46e13df', - 'rsrc/css/phui/phui-form.css' => 'a5570f70', + 'rsrc/css/phui/phui-form-view.css' => 'b446e8ff', + 'rsrc/css/phui/phui-form.css' => '7aaa04e3', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', - 'rsrc/css/phui/phui-header-view.css' => 'e082678d', + 'rsrc/css/phui/phui-header-view.css' => '67fab16d', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', - 'rsrc/css/phui/phui-icon.css' => '12b387a1', + 'rsrc/css/phui/phui-icon.css' => '5c4a5de6', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', - 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', - 'rsrc/css/phui/phui-info-view.css' => 'ec92802a', + 'rsrc/css/phui/phui-info-view.css' => 'e929f98c', 'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0', + 'rsrc/css/phui/phui-left-right.css' => '75227a4d', 'rsrc/css/phui/phui-lightbox.css' => '0a035e40', - 'rsrc/css/phui/phui-list.css' => '12eb8ce6', + 'rsrc/css/phui/phui-list.css' => '38f8c9bd', 'rsrc/css/phui/phui-object-box.css' => '9cff003c', - 'rsrc/css/phui/phui-pager.css' => '77d8a794', + 'rsrc/css/phui/phui-pager.css' => 'edcbc226', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-property-list-view.css' => '2dc7993f', 'rsrc/css/phui/phui-remarkup-preview.css' => '54a34863', 'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', - 'rsrc/css/phui/phui-tag-view.css' => 'cc4fd402', - 'rsrc/css/phui/phui-timeline-view.css' => '1d7ef61d', - 'rsrc/css/phui/phui-two-column-view.css' => 'ce9fa0b7', + 'rsrc/css/phui/phui-tag-view.css' => 'b4719c50', + 'rsrc/css/phui/phui-timeline-view.css' => 'f21db7ca', + 'rsrc/css/phui/phui-two-column-view.css' => '1ade9c5f', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', 'rsrc/css/phui/workboards/phui-workcard.css' => 'cca5fa92', 'rsrc/css/phui/workboards/phui-workpanel.css' => 'a3a63478', - 'rsrc/css/sprite-login.css' => '587d92d7', + 'rsrc/css/sprite-login.css' => '396f3c3a', 'rsrc/css/sprite-tokens.css' => '9cdfd599', 'rsrc/css/syntax/syntax-default.css' => '9923583c', 'rsrc/custom/css/phabricator-welcome-page.css' => 'c370f13b', @@ -239,7 +242,7 @@ return array( 'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => '7a94d6a5', 'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '6ea96ac9', 'rsrc/externals/javelin/lib/Cookie.js' => '62dfea03', - 'rsrc/externals/javelin/lib/DOM.js' => '805b806a', + 'rsrc/externals/javelin/lib/DOM.js' => '4976858c', 'rsrc/externals/javelin/lib/History.js' => 'd4505101', 'rsrc/externals/javelin/lib/JSON.js' => '69adf288', 'rsrc/externals/javelin/lib/Leader.js' => '7f243deb', @@ -249,7 +252,7 @@ return array( 'rsrc/externals/javelin/lib/Resource.js' => '44959b73', 'rsrc/externals/javelin/lib/Routable.js' => 'b3e7d692', 'rsrc/externals/javelin/lib/Router.js' => '29274e2b', - 'rsrc/externals/javelin/lib/Scrollbar.js' => '087e919c', + 'rsrc/externals/javelin/lib/Scrollbar.js' => '9065f639', 'rsrc/externals/javelin/lib/Sound.js' => '949c0fe5', 'rsrc/externals/javelin/lib/URI.js' => 'c989ade3', 'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8', @@ -297,6 +300,8 @@ return array( 'rsrc/image/checker_dark.png' => 'd8e65881', 'rsrc/image/checker_light.png' => 'a0155918', 'rsrc/image/checker_lighter.png' => 'd5da91b6', + 'rsrc/image/controls/checkbox-checked.png' => 'ad6441ea', + 'rsrc/image/controls/checkbox-unchecked.png' => '8eb1f0ae', 'rsrc/image/d5d8e1.png' => '0c2a1497', 'rsrc/image/darkload.gif' => '1ffd3ec6', 'rsrc/image/divot.png' => '94dded62', @@ -355,8 +360,8 @@ return array( 'rsrc/image/phrequent_active.png' => 'a466a8ed', 'rsrc/image/phrequent_inactive.png' => 'bfc15a69', 'rsrc/image/resize.png' => 'fd476de4', - 'rsrc/image/sprite-login-X2.png' => '4abee916', - 'rsrc/image/sprite-login.png' => '2b9663fd', + 'rsrc/image/sprite-login-X2.png' => '308c92c4', + 'rsrc/image/sprite-login.png' => '9ec54245', 'rsrc/image/sprite-tokens-X2.png' => '804a5232', 'rsrc/image/sprite-tokens.png' => 'b41d03da', 'rsrc/image/texture/card-gradient.png' => '815f26e8', @@ -371,16 +376,16 @@ return array( 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 'rsrc/js/application/aphlict/Aphlict.js' => 'e1d4b11a', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2', - 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '3c547a81', + 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'a14cbdfc', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', - 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'd5a2d665', + 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => '27ca6289', 'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443', 'rsrc/js/application/calendar/behavior-event-all-day.js' => 'b41537c9', 'rsrc/js/application/calendar/behavior-month-view.js' => 'fe33e256', 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '4d863052', 'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '9bbf3762', - 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'aa3bd034', + 'rsrc/js/application/conpherence/behavior-durable-column.js' => '2ae077e1', 'rsrc/js/application/conpherence/behavior-menu.js' => 'c9b99b77', 'rsrc/js/application/conpherence/behavior-participant-pane.js' => '8604caa8', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '55616e04', @@ -392,22 +397,18 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', + 'rsrc/js/application/diff/DiffChangeset.js' => '99abf4cd', + 'rsrc/js/application/diff/DiffChangesetList.js' => '8f1cd52c', + 'rsrc/js/application/diff/DiffInline.js' => 'e83d28f3', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', - 'rsrc/js/application/differential/ChangesetViewManager.js' => 'a2828756', - 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '2e3f9738', - 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', - 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', + 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '9a6b9324', - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '4fbbc3e9', - 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '92904457', - 'rsrc/js/application/differential/behavior-populate.js' => '8694b1df', - 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', + 'rsrc/js/application/differential/behavior-populate.js' => '419998ab', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', 'rsrc/js/application/diffusion/behavior-commit-branches.js' => 'bdaf4d04', - 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '49ae8328', + 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '75b83cbb', 'rsrc/js/application/diffusion/behavior-diffusion-browse-file.js' => '054a0f0b', 'rsrc/js/application/diffusion/behavior-jump-to.js' => '73d09eef', 'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667', @@ -437,7 +438,7 @@ return array( 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c', 'rsrc/js/application/projects/WorkboardBoard.js' => '8935deef', 'rsrc/js/application/projects/WorkboardCard.js' => 'c587b80f', - 'rsrc/js/application/projects/WorkboardColumn.js' => '21df4ff5', + 'rsrc/js/application/projects/WorkboardColumn.js' => '758b4758', 'rsrc/js/application/projects/WorkboardController.js' => '26167537', 'rsrc/js/application/projects/behavior-project-boards.js' => '4250a34e', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', @@ -452,22 +453,11 @@ return array( 'rsrc/js/application/transactions/behavior-comment-actions.js' => '9a6dd75c', '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' => '94c65b72', + 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '8f29b364', 'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => 'b23b49e6', 'rsrc/js/application/transactions/behavior-transaction-list.js' => '1f6794f6', 'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '635de1ec', 'rsrc/js/application/typeahead/behavior-typeahead-search.js' => '93d0c9e3', - 'rsrc/js/application/uiexample/JavelinViewExample.js' => 'd4a14807', - 'rsrc/js/application/uiexample/ReactorButtonExample.js' => 'd19198c8', - 'rsrc/js/application/uiexample/ReactorCheckboxExample.js' => '519705ea', - 'rsrc/js/application/uiexample/ReactorFocusExample.js' => '40a6a403', - 'rsrc/js/application/uiexample/ReactorInputExample.js' => '886fd850', - 'rsrc/js/application/uiexample/ReactorMouseoverExample.js' => '47c794d8', - 'rsrc/js/application/uiexample/ReactorRadioExample.js' => '988040b4', - 'rsrc/js/application/uiexample/ReactorSelectExample.js' => 'a155550f', - 'rsrc/js/application/uiexample/ReactorSendClassExample.js' => '1def2711', - 'rsrc/js/application/uiexample/ReactorSendPropertiesExample.js' => 'b1f0ccee', - 'rsrc/js/application/uiexample/busy-example.js' => '60479091', 'rsrc/js/application/uiexample/gesture-example.js' => '558829c2', 'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5', 'rsrc/js/core/Busy.js' => '59a7976a', @@ -477,24 +467,25 @@ return array( 'rsrc/js/core/FileUpload.js' => '680ea2c8', 'rsrc/js/core/Hovercard.js' => '1bd28176', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', - 'rsrc/js/core/KeyboardShortcutManager.js' => '4a021c10', + 'rsrc/js/core/KeyboardShortcutManager.js' => 'c19dd9b9', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', - 'rsrc/js/core/Notification.js' => 'ccf1cbf8', + 'rsrc/js/core/Notification.js' => '5c3349b2', 'rsrc/js/core/Prefab.js' => 'c5af80a2', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '320810c8', 'rsrc/js/core/Title.js' => '485aaa6c', - 'rsrc/js/core/ToolTip.js' => '8fadb715', + 'rsrc/js/core/ToolTip.js' => '358b8c04', 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', 'rsrc/js/core/behavior-badge-view.js' => '8ff5e24c', 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', + 'rsrc/js/core/behavior-copy.js' => 'b0b8f86d', 'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96', 'rsrc/js/core/behavior-device.js' => 'bb1dd507', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', - 'rsrc/js/core/behavior-fancy-datepicker.js' => 'a9210d03', + 'rsrc/js/core/behavior-fancy-datepicker.js' => 'ecf4e799', 'rsrc/js/core/behavior-file-tree.js' => '88236f00', 'rsrc/js/core/behavior-form.js' => '5c54cbf3', 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', @@ -504,12 +495,12 @@ return array( 'rsrc/js/core/behavior-hovercard.js' => 'bcaccd64', 'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0', 'rsrc/js/core/behavior-keyboard-shortcuts.js' => '01fca1f0', - 'rsrc/js/core/behavior-lightbox-attachments.js' => 'a5c57c24', + 'rsrc/js/core/behavior-lightbox-attachments.js' => '560f41da', 'rsrc/js/core/behavior-line-linker.js' => '1499a8cb', 'rsrc/js/core/behavior-more.js' => 'a80d0378', - 'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f', + 'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', - 'rsrc/js/core/behavior-phabricator-nav.js' => '08675c6d', + 'rsrc/js/core/behavior-phabricator-nav.js' => '947753e0', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'acd29eee', 'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207', 'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b', @@ -517,7 +508,7 @@ return array( 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', - 'rsrc/js/core/behavior-search-typeahead.js' => 'eded9ee8', + 'rsrc/js/core/behavior-search-typeahead.js' => 'd0a99ab4', 'rsrc/js/core/behavior-select-content.js' => 'bf5374ef', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 'rsrc/js/core/behavior-setup-check-https.js' => '491416b3', @@ -537,65 +528,66 @@ return array( 'rsrc/js/phui/behavior-phui-submenu.js' => 'a6f7a73b', 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', - 'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b', - 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'f6699267', + 'rsrc/js/phuix/PHUIXActionView.js' => '442efd08', + 'rsrc/js/phuix/PHUIXAutocomplete.js' => '4b7430ab', + 'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', + 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', ), 'symbols' => array( 'almanac-css' => 'dbb9b3af', 'aphront-bars' => '231ac33c', - 'aphront-dark-console-css' => '53798a6d', - 'aphront-dialog-view-css' => '685c7e2d', + 'aphront-dark-console-css' => 'f7b071f1', + 'aphront-dialog-view-css' => '6bfc244b', 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => '84cc6640', 'aphront-panel-view-css' => '8427b78d', - 'aphront-table-view-css' => '34cf86b4', - 'aphront-tokenizer-control-css' => '9a8cb501', + 'aphront-table-view-css' => '8c9bbafe', + 'aphront-tokenizer-control-css' => '15d5ff71', 'aphront-tooltip-css' => '173b9431', - 'aphront-typeahead-control-css' => '8a84cc7d', - 'application-search-view-css' => '66ee5d46', + 'aphront-typeahead-control-css' => 'a4a21016', + 'application-search-view-css' => '787f5b76', 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', - 'changeset-view-manager' => 'a2828756', 'conduit-api-css' => '7bc725c4', - 'config-options-css' => '0ede4c9b', - 'config-page-css' => 'c1d5121b', + 'config-options-css' => '4615667b', 'conpherence-color-css' => 'abb4c358', 'conpherence-durable-column-view' => '89ea6bef', 'conpherence-header-pane-css' => 'cb6f4e19', - 'conpherence-menu-css' => '6953e7ec', + 'conpherence-menu-css' => '69368e97', 'conpherence-message-pane-css' => 'b0f55ecc', 'conpherence-notification-css' => 'cef0a3fc', 'conpherence-participant-pane-css' => '26a3ce56', 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => '41af6d25', + 'differential-changeset-view-css' => 'bf84345b', 'differential-core-view-css' => '5b7b8ff4', - 'differential-inline-comment-editor' => '2e3f9738', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-icons-css' => 'a6a1e2ba', - 'diffusion-readme-css' => '18bd3910', - 'diffusion-source-css' => '750add59', + 'diffusion-css' => '45727264', + 'diffusion-icons-css' => '0c15255e', + 'diffusion-readme-css' => '419dd5b6', + 'diffusion-repository-css' => 'ee6f20ec', + 'diffusion-source-css' => '69ac9399', 'diviner-shared-css' => '896f1d43', 'font-fontawesome' => 'e838e088', 'font-lato' => 'c7ccd872', - 'global-drag-and-drop-css' => '5c1b47c2', + 'global-drag-and-drop-css' => 'b556a948', 'harbormaster-css' => 'f491c9f4', 'herald-css' => 'dc31f6e9', 'herald-rule-editor' => 'd6a7e717', 'herald-test-css' => 'a52e323e', - 'inline-comment-summary-css' => '51efda3a', + 'inline-comment-summary-css' => 'f23d4e8f', 'javelin-aphlict' => 'e1d4b11a', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => 'caade6f2', - 'javelin-behavior-aphlict-listen' => '3c547a81', + 'javelin-behavior-aphlict-listen' => 'a14cbdfc', 'javelin-behavior-aphlict-status' => '5e2634b9', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-drag-and-drop-textarea' => '484a6e22', @@ -620,40 +612,35 @@ return array( 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', 'javelin-behavior-day-view' => '4b3c4443', - 'javelin-behavior-desktop-notifications-control' => 'd5a2d665', + 'javelin-behavior-desktop-notifications-control' => '27ca6289', 'javelin-behavior-detect-timezone' => '4c193c96', 'javelin-behavior-device' => 'bb1dd507', 'javelin-behavior-diff-preview-link' => '051c7832', - 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', - 'javelin-behavior-differential-dropdown-menus' => '9a6b9324', - 'javelin-behavior-differential-edit-inline-comments' => '4fbbc3e9', - 'javelin-behavior-differential-feedback-preview' => 'b064af76', - 'javelin-behavior-differential-keyboard-navigation' => '92904457', - 'javelin-behavior-differential-populate' => '8694b1df', - 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', + 'javelin-behavior-differential-feedback-preview' => '51c5ad07', + 'javelin-behavior-differential-populate' => '419998ab', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', - 'javelin-behavior-diffusion-commit-graph' => '49ae8328', + 'javelin-behavior-diffusion-commit-graph' => '75b83cbb', 'javelin-behavior-diffusion-jump-to' => '73d09eef', 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-drydock-live-operation-status' => '901935ef', - 'javelin-behavior-durable-column' => 'aa3bd034', + 'javelin-behavior-durable-column' => '2ae077e1', 'javelin-behavior-editengine-reorder-configs' => 'd7a74243', 'javelin-behavior-editengine-reorder-fields' => 'b59e1e96', 'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-event-all-day' => 'b41537c9', - 'javelin-behavior-fancy-datepicker' => 'a9210d03', + 'javelin-behavior-fancy-datepicker' => 'ecf4e799', 'javelin-behavior-global-drag-and-drop' => '960f6a39', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 'javelin-behavior-high-security-warning' => 'a464fe03', 'javelin-behavior-history-install' => '7ee2b591', 'javelin-behavior-icon-composer' => '8499b6ab', 'javelin-behavior-launch-icon-composer' => '48086888', - 'javelin-behavior-lightbox-attachments' => 'a5c57c24', + 'javelin-behavior-lightbox-attachments' => '560f41da', 'javelin-behavior-line-chart' => 'e4232876', 'javelin-behavior-load-blame' => '42126667', 'javelin-behavior-maniphest-batch-editor' => '782ab6e7', @@ -664,21 +651,21 @@ return array( 'javelin-behavior-passphrase-credential-control' => '3cb0b2fc', 'javelin-behavior-phabricator-active-nav' => 'e379b58e', 'javelin-behavior-phabricator-autofocus' => '7319e029', - 'javelin-behavior-phabricator-busy-example' => '60479091', + 'javelin-behavior-phabricator-clipboard-copy' => 'b0b8f86d', 'javelin-behavior-phabricator-file-tree' => '88236f00', 'javelin-behavior-phabricator-gesture' => '3ab51e2c', 'javelin-behavior-phabricator-gesture-example' => '558829c2', 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0', 'javelin-behavior-phabricator-line-linker' => '1499a8cb', - 'javelin-behavior-phabricator-nav' => '08675c6d', + 'javelin-behavior-phabricator-nav' => '947753e0', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', - 'javelin-behavior-phabricator-object-selector' => 'e0ec7f2f', + 'javelin-behavior-phabricator-object-selector' => '77c1f0b0', 'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-remarkup-assist' => 'acd29eee', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', - 'javelin-behavior-phabricator-search-typeahead' => 'eded9ee8', - 'javelin-behavior-phabricator-show-older-transactions' => '94c65b72', + 'javelin-behavior-phabricator-search-typeahead' => 'd0a99ab4', + 'javelin-behavior-phabricator-show-older-transactions' => '8f29b364', 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', 'javelin-behavior-phabricator-transaction-list' => '1f6794f6', @@ -690,6 +677,7 @@ return array( 'javelin-behavior-phui-hovercards' => 'bcaccd64', 'javelin-behavior-phui-submenu' => 'a6f7a73b', 'javelin-behavior-phui-tab-group' => '0a0b10e9', + 'javelin-behavior-phuix-example' => '68af71ca', 'javelin-behavior-policy-control' => 'd0c516d5', 'javelin-behavior-policy-rule-editor' => '5e9f347c', 'javelin-behavior-project-boards' => '4250a34e', @@ -724,7 +712,7 @@ return array( 'javelin-color' => '7e41274a', 'javelin-cookie' => '62dfea03', 'javelin-diffusion-locate-file-source' => 'c93358e3', - 'javelin-dom' => '805b806a', + 'javelin-dom' => '4976858c', 'javelin-dynval' => 'f6555212', 'javelin-event' => '2ee659ce', 'javelin-fx' => '54b612ba', @@ -743,7 +731,7 @@ return array( 'javelin-resource' => '44959b73', 'javelin-routable' => 'b3e7d692', 'javelin-router' => '29274e2b', - 'javelin-scrollbar' => '087e919c', + 'javelin-scrollbar' => '9065f639', 'javelin-sound' => '949c0fe5', 'javelin-stratcom' => '6ad39b6f', 'javelin-tokenizer' => '8d3bc1b2', @@ -765,7 +753,7 @@ return array( 'javelin-websocket' => '3ffe32d6', 'javelin-workboard-board' => '8935deef', 'javelin-workboard-card' => 'c587b80f', - 'javelin-workboard-column' => '21df4ff5', + 'javelin-workboard-column' => '758b4758', 'javelin-workboard-controller' => '26167537', 'javelin-workflow' => '1e911d0f', 'maniphest-batch-editor' => 'b0f0b6d5', @@ -775,19 +763,22 @@ return array( 'multirow-row-manager' => 'b5d57730', 'owners-path-editor' => 'aa1733d0', 'owners-path-editor-css' => '2f00933b', - 'paste-css' => '1898e534', + 'paste-css' => '9fcc9773', 'path-typeahead' => 'f7fc67ec', 'people-picture-menu-item-css' => 'a06f7f34', 'people-profile-css' => '4df76faf', - 'phabricator-action-list-view-css' => 'c01858f4', + 'phabricator-action-list-view-css' => 'e7eba156', 'phabricator-busy' => '59a7976a', 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => '9f4cb463', + 'phabricator-core-css' => '1760853c', 'phabricator-countdown-css' => '16c52f5c', 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', + 'phabricator-diff-changeset' => '99abf4cd', + 'phabricator-diff-changeset-list' => '8f1cd52c', + 'phabricator-diff-inline' => 'e83d28f3', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -797,38 +788,28 @@ return array( 'phabricator-filetree-view-css' => 'fccf9f82', 'phabricator-flag-css' => 'bba8f811', 'phabricator-keyboard-shortcut' => '1ae869f2', - 'phabricator-keyboard-shortcut-manager' => '4a021c10', - 'phabricator-main-menu-view' => '865b827f', + 'phabricator-keyboard-shortcut-manager' => 'c19dd9b9', + 'phabricator-main-menu-view' => '7821ca89', 'phabricator-nav-view-css' => 'faf6a6fc', - 'phabricator-notification' => 'ccf1cbf8', - 'phabricator-notification-css' => '3f6c89c9', - 'phabricator-notification-menu-css' => '6a697e43', + 'phabricator-notification' => '5c3349b2', + 'phabricator-notification-css' => '457861ec', + 'phabricator-notification-menu-css' => '10685bd4', 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', - 'phabricator-remarkup-css' => '17c0fb37', - 'phabricator-search-results-css' => 'f87d23ad', + 'phabricator-remarkup-css' => 'cad18339', + 'phabricator-search-results-css' => '505dd8cf', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', - 'phabricator-source-code-view-css' => '4383192f', - 'phabricator-standard-page-view' => 'eb5b80c5', + 'phabricator-source-code-view-css' => 'aea41829', + 'phabricator-standard-page-view' => '34ee718b', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', - 'phabricator-tooltip' => '8fadb715', + 'phabricator-tooltip' => '358b8c04', 'phabricator-ui-example-css' => '528b19de', - 'phabricator-uiexample-javelin-view' => 'd4a14807', - 'phabricator-uiexample-reactor-button' => 'd19198c8', - 'phabricator-uiexample-reactor-checkbox' => '519705ea', - 'phabricator-uiexample-reactor-focus' => '40a6a403', - 'phabricator-uiexample-reactor-input' => '886fd850', - 'phabricator-uiexample-reactor-mouseover' => '47c794d8', - 'phabricator-uiexample-reactor-radio' => '988040b4', - 'phabricator-uiexample-reactor-select' => 'a155550f', - 'phabricator-uiexample-reactor-sendclass' => '1def2711', - 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', 'phabricator-welcome-page' => 'c370f13b', - 'phabricator-zindex-css' => '0233d039', - 'phame-css' => 'b3a0b3a3', + 'phabricator-zindex-css' => '9d8f7c4b', + 'phame-css' => '8cb3afcd', 'pholio-css' => 'ca89d380', 'pholio-edit-css' => '07676f51', 'pholio-inline-comments-css' => '8e545e49', @@ -838,68 +819,71 @@ return array( 'phortune-invoice-css' => '476055e2', 'phrequent-css' => 'ffc185ad', 'phriction-document-css' => '4282e4ad', - 'phui-action-panel-css' => '91c7b835', + 'phui-action-panel-css' => 'b4798122', 'phui-badge-view-css' => '22c0cf4f', - 'phui-basic-nav-view-css' => 'a0705f53', - 'phui-big-info-view-css' => 'bd903741', - 'phui-box-css' => '269cbc99', - 'phui-button-css' => '8d23596a', - 'phui-calendar-css' => '477acfaa', + 'phui-basic-nav-view-css' => '98c11ab3', + 'phui-big-info-view-css' => 'acc3492c', + 'phui-box-css' => '9f3745fb', + 'phui-button-bar-css' => 'f1ff5494', + 'phui-button-css' => '1863cc6e', + 'phui-button-simple-css' => '8e1baf68', + 'phui-calendar-css' => 'f1ddf11c', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', - 'phui-calendar-month-css' => '8e10e92c', + 'phui-calendar-month-css' => '21154caf', 'phui-chart-css' => '6bf6f78e', 'phui-cms-css' => '504b4b23', - 'phui-comment-form-css' => '57af2e14', + 'phui-comment-form-css' => 'ac68149f', 'phui-comment-panel-css' => 'f50152ad', 'phui-crumbs-view-css' => '6ece3bbb', - 'phui-curtain-view-css' => '679743bb', + 'phui-curtain-view-css' => 'ca363f15', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'c32e8dec', - 'phui-document-view-pro-css' => '62c4dcbf', + 'phui-document-view-pro-css' => '8af7ea27', 'phui-feed-story-css' => '44a9c8e9', 'phui-font-icon-base-css' => '870a7360', 'phui-fontkit-css' => '1320ed01', - 'phui-form-css' => 'a5570f70', - 'phui-form-view-css' => 'f46e13df', + 'phui-form-css' => '7aaa04e3', + 'phui-form-view-css' => 'b446e8ff', 'phui-head-thing-view-css' => 'fd311e5f', - 'phui-header-view-css' => 'e082678d', + 'phui-header-view-css' => '67fab16d', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'f0592bcf', 'phui-icon-set-selector-css' => '87db8fee', - 'phui-icon-view-css' => '12b387a1', + 'phui-icon-view-css' => '5c4a5de6', 'phui-image-mask-css' => 'a8498f9c', - 'phui-info-panel-css' => '27ea50a1', - 'phui-info-view-css' => 'ec92802a', - 'phui-inline-comment-view-css' => 'be663c95', + 'phui-info-view-css' => 'e929f98c', + 'phui-inline-comment-view-css' => '65ae3bc2', 'phui-invisible-character-view-css' => '6993d9f0', + 'phui-left-right-css' => '75227a4d', 'phui-lightbox-css' => '0a035e40', - 'phui-list-view-css' => '12eb8ce6', + 'phui-list-view-css' => '38f8c9bd', 'phui-object-box-css' => '9cff003c', - 'phui-oi-big-ui-css' => '19f9369b', + 'phui-oi-big-ui-css' => '628f59de', 'phui-oi-color-css' => 'cd2b9b77', - 'phui-oi-drag-ui-css' => 'f12cbc9f', + 'phui-oi-drag-ui-css' => '08f4ccc3', 'phui-oi-flush-ui-css' => '9d9685d6', - 'phui-oi-list-view-css' => '7c8ec27a', + 'phui-oi-list-view-css' => 'bf094950', 'phui-oi-simple-ui-css' => 'a8beebea', - 'phui-pager-css' => '77d8a794', + 'phui-pager-css' => 'edcbc226', 'phui-pinboard-view-css' => '2495140e', 'phui-property-list-view-css' => '2dc7993f', 'phui-remarkup-preview-css' => '54a34863', 'phui-segment-bar-view-css' => 'b1d1b892', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => 'd5263e49', - 'phui-tag-view-css' => 'cc4fd402', + 'phui-tag-view-css' => 'b4719c50', 'phui-theme-css' => '9f261c6b', - 'phui-timeline-view-css' => '1d7ef61d', - 'phui-two-column-view-css' => 'ce9fa0b7', + 'phui-timeline-view-css' => 'f21db7ca', + 'phui-two-column-view-css' => '1ade9c5f', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', 'phui-workcard-view-css' => 'cca5fa92', 'phui-workpanel-view-css' => 'a3a63478', 'phuix-action-list-view' => 'b5c256b8', - 'phuix-action-view' => 'b3465b9b', - 'phuix-autocomplete' => 'f6699267', + 'phuix-action-view' => '442efd08', + 'phuix-autocomplete' => '4b7430ab', + 'phuix-button-view' => '8a91e1ac', 'phuix-dropdown-menu' => '8018ee50', 'phuix-form-control-view' => '83e03671', 'phuix-icon-view' => 'bff6884b', @@ -907,19 +891,19 @@ return array( 'policy-edit-css' => '815c66f7', 'policy-transaction-detail-css' => '82100a43', 'ponder-view-css' => 'fbd45f96', - 'project-card-view-css' => '3d3c1f91', + 'project-card-view-css' => '0010bb52', 'project-view-css' => '792c9057', 'releeph-core' => '9b3c5733', 'releeph-preview-branch' => 'b7a6f4a5', 'releeph-request-differential-create-dialog' => '8d8b92cd', 'releeph-request-typeahead-css' => '667a48ae', - 'setup-issue-css' => 'f794cfc3', - 'sprite-login-css' => '587d92d7', + 'setup-issue-css' => '7dae7f18', + 'sprite-login-css' => '396f3c3a', 'sprite-tokens-css' => '9cdfd599', 'syntax-default-css' => '9923583c', 'syntax-highlighting-css' => 'cae95e89', 'tokens-css' => '3d0f239e', - 'typeahead-browse-css' => '8904346a', + 'typeahead-browse-css' => 'f2818435', 'unhandled-exception-css' => '4c96257a', ), 'requires' => array( @@ -963,21 +947,8 @@ return array( 'javelin-stratcom', 'javelin-util', ), - '08675c6d' => array( - 'javelin-behavior', - 'javelin-behavior-device', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-magical-init', - 'javelin-vector', - 'javelin-request', - 'javelin-util', - ), - '087e919c' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-vector', + '08f4ccc3' => array( + 'phui-oi-list-view-css', ), '0a0b10e9' => array( 'javelin-behavior', @@ -1007,6 +978,10 @@ return array( 'javelin-dom', 'javelin-history', ), + '15d5ff71' => array( + 'aphront-typeahead-control-css', + 'phui-tag-view-css', + ), '17bb8539' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1020,9 +995,6 @@ return array( '185bbd53' => array( 'javelin-install', ), - '19f9369b' => array( - 'phui-oi-list-view-css', - ), '1ad0a787' => array( 'javelin-install', 'javelin-reactor', @@ -1041,11 +1013,6 @@ return array( 'javelin-request', 'javelin-uri', ), - '1def2711' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), '1e911d0f' => array( 'javelin-stratcom', 'javelin-request', @@ -1069,10 +1036,6 @@ return array( 'javelin-install', 'javelin-dom', ), - '21df4ff5' => array( - 'javelin-install', - 'javelin-workboard-card', - ), '2290aeef' => array( 'javelin-install', 'javelin-dom', @@ -1090,6 +1053,13 @@ return array( 'phabricator-drag-and-drop-file-upload', 'javelin-workboard-board', ), + '27ca6289' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-uri', + 'phabricator-notification', + ), '2926fff2' => array( 'javelin-behavior', 'javelin-dom', @@ -1098,6 +1068,16 @@ return array( 'javelin-install', 'javelin-util', ), + '2ae077e1' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-behavior-device', + 'javelin-scrollbar', + 'javelin-quicksand', + 'phabricator-keyboard-shortcut', + 'conpherence-thread-manager', + ), '2b8de964' => array( 'javelin-install', 'javelin-util', @@ -1106,14 +1086,6 @@ return array( 'javelin-install', 'javelin-event', ), - '2e3f9738' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-request', - 'javelin-workflow', - ), '2ee659ce' => array( 'javelin-install', ), @@ -1131,6 +1103,12 @@ return array( 'javelin-dom', 'javelin-workflow', ), + '358b8c04' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-vector', + ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1139,20 +1117,6 @@ return array( 'javelin-dom', 'javelin-magical-init', ), - '3c547a81' => array( - 'javelin-behavior', - 'javelin-aphlict', - 'javelin-stratcom', - 'javelin-request', - 'javelin-uri', - 'javelin-dom', - 'javelin-json', - 'javelin-router', - 'javelin-util', - 'javelin-leader', - 'javelin-sound', - 'phabricator-notification', - ), '3cb0b2fc' => array( 'javelin-behavior', 'javelin-dom', @@ -1179,13 +1143,13 @@ return array( 'javelin-workflow', 'phabricator-draggable-list', ), - '40a6a403' => array( - 'javelin-install', + '419998ab' => array( + 'javelin-behavior', 'javelin-dom', - 'javelin-reactor-dom', - ), - '41af6d25' => array( - 'phui-inline-comment-view-css', + 'javelin-stratcom', + 'phabricator-tooltip', + 'phabricator-diff-changeset-list', + 'phabricator-diff-changeset', ), 42126667 => array( 'javelin-behavior', @@ -1201,6 +1165,11 @@ return array( 'javelin-workflow', 'javelin-workboard-controller', ), + '442efd08' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-util', + ), '44959b73' => array( 'javelin-util', 'javelin-uri', @@ -1221,11 +1190,6 @@ return array( 'javelin-view-renderer', 'javelin-install', ), - '47c794d8' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), 48086888 => array( 'javelin-behavior', 'javelin-dom', @@ -1245,17 +1209,12 @@ return array( 'javelin-uri', 'phabricator-notification', ), - '49ae8328' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - ), - '4a021c10' => array( + '4976858c' => array( + 'javelin-magical-init', 'javelin-install', 'javelin-util', - 'javelin-stratcom', - 'javelin-dom', 'javelin-vector', + 'javelin-stratcom', ), '4b3c4443' => array( 'phuix-icon-view', @@ -1266,6 +1225,12 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), + '4b7430ab' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-icon-view', + 'phabricator-prefab', + ), '4c193c96' => array( 'javelin-behavior', 'javelin-uri', @@ -1287,28 +1252,18 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - '4fbbc3e9' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'differential-inline-comment-editor', - ), - '4fdb476d' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - ), '503e17fd' => array( 'javelin-install', 'javelin-typeahead-source', 'javelin-util', ), - '519705ea' => array( - 'javelin-install', + '51c5ad07' => array( + 'javelin-behavior', + 'javelin-stratcom', 'javelin-dom', - 'javelin-reactor-dom', + 'javelin-request', + 'javelin-util', + 'phabricator-shaped-request', ), '522431f7' => array( 'javelin-behavior', @@ -1343,6 +1298,15 @@ return array( 'javelin-vector', 'javelin-dom', ), + '560f41da' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-mask', + 'javelin-util', + 'phuix-icon-view', + 'phabricator-busy', + ), '58dea2fa' => array( 'javelin-install', 'javelin-util', @@ -1362,6 +1326,13 @@ return array( 'javelin-vector', 'javelin-dom', ), + '5c3349b2' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + 'phabricator-notification-css', + ), '5c54cbf3' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1381,10 +1352,6 @@ return array( 'phabricator-prefab', 'javelin-json', ), - 60479091 => array( - 'phabricator-busy', - 'javelin-behavior', - ), '60821bc7' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1394,6 +1361,9 @@ return array( 'javelin-magical-init', 'javelin-util', ), + '628f59de' => array( + 'phui-oi-list-view-css', + ), '62dfea03' => array( 'javelin-install', 'javelin-util', @@ -1412,6 +1382,11 @@ return array( '6882e80a' => array( 'javelin-dom', ), + '68af71ca' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-button-view', + ), '69adf288' => array( 'javelin-install', ), @@ -1461,6 +1436,15 @@ return array( 'javelin-vector', 'javelin-dom', ), + '758b4758' => array( + 'javelin-install', + 'javelin-workboard-card', + ), + '75b83cbb' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), '76b9fc3e' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1473,6 +1457,15 @@ return array( 'javelin-reactor', 'javelin-util', ), + '77c1f0b0' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-request', + 'javelin-util', + ), + '7821ca89' => array( + 'phui-theme-css', + ), '782ab6e7' => array( 'javelin-behavior', 'javelin-dom', @@ -1516,13 +1509,6 @@ return array( 'javelin-vector', 'javelin-stratcom', ), - '805b806a' => array( - 'javelin-magical-init', - 'javelin-install', - 'javelin-util', - 'javelin-vector', - 'javelin-stratcom', - ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', @@ -1548,28 +1534,11 @@ return array( 'phabricator-notification', 'conpherence-thread-manager', ), - '865b827f' => array( - 'phui-theme-css', - ), - '8694b1df' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'changeset-view-manager', - ), '88236f00' => array( 'javelin-behavior', 'phabricator-keyboard-shortcut', 'javelin-stratcom', ), - '886fd850' => array( - 'javelin-install', - 'javelin-reactor-dom', - 'javelin-view-html', - 'javelin-view-interpreter', - 'javelin-view-renderer', - ), '887ad43f' => array( 'javelin-behavior', 'javelin-request', @@ -1589,6 +1558,10 @@ return array( 'javelin-install', 'javelin-dom', ), + '8a91e1ac' => array( + 'javelin-install', + 'javelin-dom', + ), '8ce821c5' => array( 'phabricator-notification', 'javelin-stratcom', @@ -1600,11 +1573,18 @@ return array( 'javelin-stratcom', 'javelin-install', ), - '8fadb715' => array( + '8e1baf68' => array( + 'phui-button-css', + ), + '8f1cd52c' => array( 'javelin-install', - 'javelin-util', + 'phuix-button-view', + ), + '8f29b364' => array( + 'javelin-behavior', + 'javelin-stratcom', 'javelin-dom', - 'javelin-vector', + 'phabricator-busy', ), '8ff5e24c' => array( 'javelin-behavior', @@ -1616,11 +1596,11 @@ return array( 'javelin-dom', 'javelin-request', ), - 92904457 => array( - 'javelin-behavior', + '9065f639' => array( + 'javelin-install', 'javelin-dom', 'javelin-stratcom', - 'phabricator-keyboard-shortcut', + 'javelin-vector', ), '92b9ec77' => array( 'javelin-behavior', @@ -1633,6 +1613,16 @@ return array( 'javelin-workflow', 'javelin-dom', ), + '947753e0' => array( + 'javelin-behavior', + 'javelin-behavior-device', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-magical-init', + 'javelin-vector', + 'javelin-request', + 'javelin-util', + ), '949c0fe5' => array( 'javelin-install', ), @@ -1646,12 +1636,6 @@ return array( 'javelin-resource', 'javelin-routable', ), - '94c65b72' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'phabricator-busy', - ), '960f6a39' => array( 'javelin-behavior', 'javelin-dom', @@ -1659,22 +1643,16 @@ return array( 'javelin-mask', 'phabricator-drag-and-drop-file-upload', ), - '988040b4' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), - '9a6b9324' => array( - 'javelin-behavior', + '99abf4cd' => array( 'javelin-dom', 'javelin-util', 'javelin-stratcom', + 'javelin-install', 'javelin-workflow', - 'phuix-dropdown-menu', - 'phuix-action-list-view', - 'phuix-action-view', - 'phabricator-phtize', - 'changeset-view-manager', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', ), '9a6dd75c' => array( 'javelin-behavior', @@ -1685,10 +1663,6 @@ return array( 'phuix-icon-view', 'javelin-behavior-phabricator-gesture', ), - '9a8cb501' => array( - 'aphront-typeahead-control-css', - 'phui-tag-view-css', - ), '9bbf3762' => array( 'javelin-behavior', 'javelin-dom', @@ -1713,20 +1687,19 @@ return array( 'javelin-util', 'phabricator-keyboard-shortcut', ), - 'a155550f' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), - 'a2828756' => array( - 'javelin-dom', - 'javelin-util', + 'a14cbdfc' => array( + 'javelin-behavior', + 'javelin-aphlict', 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', + 'javelin-request', + 'javelin-uri', + 'javelin-dom', + 'javelin-json', 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', + 'javelin-util', + 'javelin-leader', + 'javelin-sound', + 'phabricator-notification', ), 'a3a63478' => array( 'phui-workcard-view-css', @@ -1736,15 +1709,6 @@ return array( 'javelin-uri', 'phabricator-notification', ), - 'a5c57c24' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-mask', - 'javelin-util', - 'phuix-icon-view', - 'phabricator-busy', - ), 'a6b98425' => array( 'javelin-behavior', 'javelin-dom', @@ -1773,13 +1737,6 @@ return array( 'javelin-uri', 'phabricator-keyboard-shortcut', ), - 'a9210d03' => array( - 'javelin-behavior', - 'javelin-util', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-vector', - ), 'a9f88de2' => array( 'javelin-behavior', 'javelin-dom', @@ -1796,16 +1753,6 @@ return array( 'javelin-util', 'phabricator-prefab', ), - 'aa3bd034' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-behavior-device', - 'javelin-scrollbar', - 'javelin-quicksand', - 'phabricator-keyboard-shortcut', - 'conpherence-thread-manager', - ), 'ab2f381b' => array( 'javelin-request', 'javelin-behavior', @@ -1831,18 +1778,10 @@ return array( 'javelin-dom', 'phuix-dropdown-menu', ), - 'b064af76' => array( + 'b0b8f86d' => array( 'javelin-behavior', + 'javelin-dom', 'javelin-stratcom', - 'javelin-dom', - 'javelin-request', - 'javelin-util', - 'phabricator-shaped-request', - ), - 'b1f0ccee' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', ), 'b23b49e6' => array( 'javelin-behavior', @@ -1857,11 +1796,6 @@ return array( 'javelin-uri', 'javelin-request', ), - 'b3465b9b' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-util', - ), 'b3a4b884' => array( 'javelin-behavior', 'phabricator-prefab', @@ -1947,10 +1881,20 @@ return array( 'javelin-stratcom', 'javelin-dom', ), + 'bf84345b' => array( + 'phui-inline-comment-view-css', + ), 'bff6884b' => array( 'javelin-install', 'javelin-dom', ), + 'c19dd9b9' => array( + 'javelin-install', + 'javelin-util', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-vector', + ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -2008,12 +1952,6 @@ return array( 'phabricator-shaped-request', 'conpherence-thread-manager', ), - 'ca3f91eb' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-phtize', - ), 'caade6f2' => array( 'javelin-behavior', 'javelin-request', @@ -2028,16 +1966,20 @@ return array( 'cae95e89' => array( 'syntax-default-css', ), - 'ccf1cbf8' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - 'phabricator-notification-css', - ), 'cd2b9b77' => array( 'phui-oi-list-view-css', ), + 'd0a99ab4' => array( + 'javelin-behavior', + 'javelin-typeahead-ondemand-source', + 'javelin-typeahead', + 'javelin-dom', + 'javelin-uri', + 'javelin-util', + 'javelin-stratcom', + 'phabricator-prefab', + 'phuix-icon-view', + ), 'd0c516d5' => array( 'javelin-behavior', 'javelin-dom', @@ -2048,13 +1990,6 @@ return array( 'javelin-workflow', 'phuix-icon-view', ), - 'd19198c8' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-util', - 'javelin-dynval', - 'javelin-reactor-dom', - ), 'd254d646' => array( 'javelin-util', ), @@ -2064,23 +1999,11 @@ return array( 'javelin-uri', 'javelin-util', ), - 'd4a14807' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-view', - ), 'd4eecc63' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', ), - 'd5a2d665' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-uri', - 'phabricator-notification', - ), 'd6a7e717' => array( 'multirow-row-manager', 'javelin-install', @@ -2110,12 +2033,6 @@ return array( 'javelin-typeahead-ondemand-source', 'javelin-dom', ), - 'e0ec7f2f' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-request', - 'javelin-util', - ), 'e1d25dfb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2172,6 +2089,9 @@ return array( 'javelin-workflow', 'javelin-magical-init', ), + 'e83d28f3' => array( + 'javelin-dom', + ), 'e9581f08' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2179,16 +2099,12 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), - 'eded9ee8' => array( + 'ecf4e799' => array( 'javelin-behavior', - 'javelin-typeahead-ondemand-source', - 'javelin-typeahead', - 'javelin-dom', - 'javelin-uri', 'javelin-util', + 'javelin-dom', 'javelin-stratcom', - 'phabricator-prefab', - 'phuix-icon-view', + 'javelin-vector', ), 'edf8a145' => array( 'javelin-behavior', @@ -2205,8 +2121,9 @@ return array( 'javelin-workflow', 'javelin-json', ), - 'f12cbc9f' => array( - 'phui-oi-list-view-css', + 'f1ff5494' => array( + 'phui-button-css', + 'phui-button-simple-css', ), 'f50152ad' => array( 'phui-timeline-view-css', @@ -2217,12 +2134,6 @@ return array( 'javelin-util', 'javelin-reactor', ), - 'f6699267' => array( - 'javelin-install', - 'javelin-dom', - 'phuix-icon-view', - 'phabricator-prefab', - ), 'f7fc67ec' => array( 'javelin-install', 'javelin-typeahead', @@ -2283,6 +2194,7 @@ return array( 'phabricator-core-css', 'phabricator-zindex-css', 'phui-button-css', + 'phui-button-simple-css', 'phui-theme-css', 'phabricator-standard-page-view', 'aphront-dialog-view-css', @@ -2452,21 +2364,17 @@ return array( 'phabricator-drag-and-drop-file-upload', 'phabricator-shaped-request', 'javelin-behavior-differential-feedback-preview', - 'javelin-behavior-differential-edit-inline-comments', 'javelin-behavior-differential-populate', 'javelin-behavior-differential-diff-radios', - 'javelin-behavior-differential-comment-jump', - 'javelin-behavior-differential-keyboard-navigation', 'javelin-behavior-aphront-drag-and-drop-textarea', 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', 'javelin-behavior-load-blame', - 'differential-inline-comment-editor', - 'javelin-behavior-differential-dropdown-menus', - 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', - 'changeset-view-manager', + 'phabricator-diff-inline', + 'phabricator-diff-changeset', + 'phabricator-diff-changeset-list', ), 'diffusion.pkg.css' => array( 'diffusion-icons-css', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index d906c738da..abbf1d77e2 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -93,6 +93,7 @@ return array( 'phabricator-core-css', 'phabricator-zindex-css', 'phui-button-css', + 'phui-button-simple-css', 'phui-theme-css', 'phabricator-standard-page-view', 'aphront-dialog-view-css', @@ -193,22 +194,19 @@ return array( 'phabricator-shaped-request', 'javelin-behavior-differential-feedback-preview', - 'javelin-behavior-differential-edit-inline-comments', 'javelin-behavior-differential-populate', 'javelin-behavior-differential-diff-radios', - 'javelin-behavior-differential-comment-jump', - 'javelin-behavior-differential-keyboard-navigation', 'javelin-behavior-aphront-drag-and-drop-textarea', 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', 'javelin-behavior-load-blame', - 'differential-inline-comment-editor', - 'javelin-behavior-differential-dropdown-menus', - 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', - 'changeset-view-manager', + + 'phabricator-diff-inline', + 'phabricator-diff-changeset', + 'phabricator-diff-changeset-list', ), 'diffusion.pkg.css' => array( 'diffusion-icons-css', diff --git a/resources/chatbot/example_config.json b/resources/chatbot/example_config.json deleted file mode 100644 index 7d150e65ae..0000000000 --- a/resources/chatbot/example_config.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "server" : "irc.freenode.net", - "port" : 6667, - "nick" : "phabot", - "join" : [ - "#phabot-test" - ], - "handlers" : [ - "PhabricatorBotObjectNameHandler", - "PhabricatorBotSymbolHandler", - "PhabricatorBotLogHandler", - "PhabricatorBotFeedNotificationHandler", - "PhabricatorBotWhatsNewHandler", - "PhabricatorBotMacroHandler" - ], - - "conduit.uri" : null, - "conduit.token" : null, - - "macro.size" : 48, - "macro.aspect" : 0.66, - - "notification.channels" : ["#phabot-test"] -} diff --git a/resources/sprite/login_1x/Dropbox.png b/resources/sprite/login_1x/Dropbox.png deleted file mode 100644 index 1d68eee5ca..0000000000 Binary files a/resources/sprite/login_1x/Dropbox.png and /dev/null differ diff --git a/resources/sprite/login_1x/Linkedin.png b/resources/sprite/login_1x/Linkedin.png deleted file mode 100644 index 2cf76885de..0000000000 Binary files a/resources/sprite/login_1x/Linkedin.png and /dev/null differ diff --git a/resources/sprite/login_1x/Openid.png b/resources/sprite/login_1x/Openid.png deleted file mode 100644 index 7259db47fd..0000000000 Binary files a/resources/sprite/login_1x/Openid.png and /dev/null differ diff --git a/resources/sprite/login_1x/Phabricator.png b/resources/sprite/login_1x/Phabricator.png index 5657d9c3c6..b998007a3c 100644 Binary files a/resources/sprite/login_1x/Phabricator.png and b/resources/sprite/login_1x/Phabricator.png differ diff --git a/resources/sprite/login_1x/Yahoo.png b/resources/sprite/login_1x/Yahoo.png deleted file mode 100644 index 47a01c53d7..0000000000 Binary files a/resources/sprite/login_1x/Yahoo.png and /dev/null differ diff --git a/resources/sprite/login_2x/Dropbox.png b/resources/sprite/login_2x/Dropbox.png deleted file mode 100644 index 953ed4eec4..0000000000 Binary files a/resources/sprite/login_2x/Dropbox.png and /dev/null differ diff --git a/resources/sprite/login_2x/Linkedin.png b/resources/sprite/login_2x/Linkedin.png deleted file mode 100644 index 9999d55c5c..0000000000 Binary files a/resources/sprite/login_2x/Linkedin.png and /dev/null differ diff --git a/resources/sprite/login_2x/Openid.png b/resources/sprite/login_2x/Openid.png deleted file mode 100644 index 360aa6e6ef..0000000000 Binary files a/resources/sprite/login_2x/Openid.png and /dev/null differ diff --git a/resources/sprite/login_2x/Phabricator.png b/resources/sprite/login_2x/Phabricator.png index 89c0372fbc..3792f723fc 100644 Binary files a/resources/sprite/login_2x/Phabricator.png and b/resources/sprite/login_2x/Phabricator.png differ diff --git a/resources/sprite/login_2x/Yahoo.png b/resources/sprite/login_2x/Yahoo.png deleted file mode 100644 index 5aa8c4fe74..0000000000 Binary files a/resources/sprite/login_2x/Yahoo.png and /dev/null differ diff --git a/resources/sprite/manifest/login.json b/resources/sprite/manifest/login.json index f5d15dd890..e9bbb9f2cb 100644 --- a/resources/sprite/manifest/login.json +++ b/resources/sprite/manifest/login.json @@ -21,11 +21,6 @@ "rule": ".login-Disqus", "hash": "77b29d56329a3c30b79d6b6673b0e39b" }, - "login-Dropbox": { - "name": "login-Dropbox", - "rule": ".login-Dropbox", - "hash": "5eaf07ae4598227fbbba3474675d18c7" - }, "login-Facebook": { "name": "login-Facebook", "rule": ".login-Facebook", @@ -61,21 +56,11 @@ "rule": ".login-LDAP", "hash": "e31df2e9faf8ca0925ef93128a82fa7a" }, - "login-Linkedin": { - "name": "login-Linkedin", - "rule": ".login-Linkedin", - "hash": "b7ee1e92c923462531f3a34093e57127" - }, "login-MediaWiki": { "name": "login-MediaWiki", "rule": ".login-MediaWiki", "hash": "f1f0a9382434081a9a84e7584828c2dd" }, - "login-Openid": { - "name": "login-Openid", - "rule": ".login-Openid", - "hash": "886f65ad44435344a1fa1c13e7758155" - }, "login-PayPal": { "name": "login-PayPal", "rule": ".login-PayPal", @@ -84,7 +69,7 @@ "login-Phabricator": { "name": "login-Phabricator", "rule": ".login-Phabricator", - "hash": "d0f830803593bbcc025d7d5a29ee3ecd" + "hash": "54f5ddae4b9d138c438ec00ed42544d2" }, "login-Slack": { "name": "login-Slack", @@ -120,11 +105,6 @@ "name": "login-WordPressCOM", "rule": ".login-WordPressCOM", "hash": "9eae4205dbed0c42a18ee4f8e0fa151b" - }, - "login-Yahoo": { - "name": "login-Yahoo", - "rule": ".login-Yahoo", - "hash": "f37822c769f6b8ebd1dda6b3ac89b83b" } }, "scales": [ diff --git a/resources/sql/autopatches/20140321.mstatus.2.mig.php b/resources/sql/autopatches/20140321.mstatus.2.mig.php index d875702755..213451e7ca 100644 --- a/resources/sql/autopatches/20140321.mstatus.2.mig.php +++ b/resources/sql/autopatches/20140321.mstatus.2.mig.php @@ -36,7 +36,8 @@ foreach (new LiskMigrationIterator(new ManiphestTransaction()) as $xaction) { $id = $xaction->getID(); echo pht('Migrating %d...', $id)."\n"; - if ($xaction->getTransactionType() == ManiphestTransaction::TYPE_STATUS) { + $xn_type = ManiphestTaskStatusTransaction::TRANSACTIONTYPE; + if ($xaction->getTransactionType() == $xn_type) { $old = $xaction->getOldValue(); if ($old !== null && isset($status_map[$old])) { $old = $status_map[$old]; diff --git a/resources/sql/autopatches/20141106.uniqdrafts.php b/resources/sql/autopatches/20141106.uniqdrafts.php index 31d6a53cb7..85b05f9ced 100644 --- a/resources/sql/autopatches/20141106.uniqdrafts.php +++ b/resources/sql/autopatches/20141106.uniqdrafts.php @@ -1,30 +1,3 @@ establishConnection('w'); - -$duplicates = queryfx_all( - $conn_w, - 'SELECT DISTINCT u.id id FROM %T u - JOIN %T v - ON u.objectPHID = v.objectPHID - AND u.authorPHID = v.authorPHID - AND u.draftKey = v.draftKey - AND u.id < v.id', - $table->getTableName(), - $table->getTableName()); - -$duplicates = ipull($duplicates, 'id'); -foreach (PhabricatorLiskDAO::chunkSQL($duplicates) as $chunk) { - queryfx( - $conn_w, - 'DELETE FROM %T WHERE id IN (%Q)', - $table->getTableName(), - $chunk); -} +// This table has been removed; see T12104 for details. diff --git a/resources/sql/autopatches/20170404.files.retroactive-content-hash.sql b/resources/sql/autopatches/20170404.files.retroactive-content-hash.sql new file mode 100644 index 0000000000..7c4eb0f013 --- /dev/null +++ b/resources/sql/autopatches/20170404.files.retroactive-content-hash.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_file.file + CHANGE contentHash contentHash BINARY(64); diff --git a/resources/sql/autopatches/20170427.owners.01.long.sql b/resources/sql/autopatches/20170427.owners.01.long.sql new file mode 100644 index 0000000000..01a463f52a --- /dev/null +++ b/resources/sql/autopatches/20170427.owners.01.long.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_owners.owners_package + DROP originalName; diff --git a/resources/sql/autopatches/20170504.1.slowvote.shuffle.sql b/resources/sql/autopatches/20170504.1.slowvote.shuffle.sql new file mode 100644 index 0000000000..5797f3fd5c --- /dev/null +++ b/resources/sql/autopatches/20170504.1.slowvote.shuffle.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_slowvote.slowvote_poll + MODIFY shuffle BOOL NOT NULL DEFAULT 0; diff --git a/resources/sql/autopatches/20170522.nuance.01.itemkey.sql b/resources/sql/autopatches/20170522.nuance.01.itemkey.sql new file mode 100644 index 0000000000..75118205ce --- /dev/null +++ b/resources/sql/autopatches/20170522.nuance.01.itemkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_item + MODIFY itemKey VARCHAR(64) COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170524.nuance.01.command.sql b/resources/sql/autopatches/20170524.nuance.01.command.sql new file mode 100644 index 0000000000..529756e748 --- /dev/null +++ b/resources/sql/autopatches/20170524.nuance.01.command.sql @@ -0,0 +1,8 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_itemcommand + ADD dateCreated INT UNSIGNED NOT NULL; + +ALTER TABLE {$NAMESPACE}_nuance.nuance_itemcommand + ADD dateModified INT UNSIGNED NOT NULL; + +ALTER TABLE {$NAMESPACE}_nuance.nuance_itemcommand + ADD queuePHID VARBINARY(64); diff --git a/resources/sql/autopatches/20170524.nuance.02.commandstatus.sql b/resources/sql/autopatches/20170524.nuance.02.commandstatus.sql new file mode 100644 index 0000000000..14f57af053 --- /dev/null +++ b/resources/sql/autopatches/20170524.nuance.02.commandstatus.sql @@ -0,0 +1,5 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_itemcommand + ADD status VARCHAR(64) NOT NULL; + +UPDATE {$NAMESPACE}_nuance.nuance_itemcommand + SET status = 'done' WHERE status = ''; diff --git a/resources/sql/autopatches/20170526.dropdifferentialdrafts.sql b/resources/sql/autopatches/20170526.dropdifferentialdrafts.sql new file mode 100644 index 0000000000..057bcb0d90 --- /dev/null +++ b/resources/sql/autopatches/20170526.dropdifferentialdrafts.sql @@ -0,0 +1 @@ +DROP TABLE {$NAMESPACE}_differential.differential_draft; diff --git a/resources/sql/autopatches/20170526.milestones.php b/resources/sql/autopatches/20170526.milestones.php new file mode 100644 index 0000000000..2e30ac4775 --- /dev/null +++ b/resources/sql/autopatches/20170526.milestones.php @@ -0,0 +1,11 @@ +getPHID(), + array( + 'force' => true, + )); +} diff --git a/resources/sql/autopatches/20170528.maniphestdupes.php b/resources/sql/autopatches/20170528.maniphestdupes.php new file mode 100644 index 0000000000..aea8ae93d2 --- /dev/null +++ b/resources/sql/autopatches/20170528.maniphestdupes.php @@ -0,0 +1,68 @@ +getTransactionType(); + + if ($txn_type == 'mergedinto') { + // dupe handling as implemented in D10427, which creates a specific txn + $add_edges[] = array( + 'src' => $txn->getObjectPHID(), + 'dst' => $txn->getNewValue(), + ); + } else if ($txn_type == 'status' && $txn->getNewValue() == 'duplicate') { + // dupe handling as originally implemented, which just changes the status + // and adds a comment + $src_phid = $txn->getObjectPHID(); + + // get all the comment transactions associated with this task + $viewer = PhabricatorUser::getOmnipotentUser(); + $comment_txns = id(new ManiphestTransactionQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($src_phid)) + ->needComments(true) + ->execute(); + + // check each comment, looking for the "Merged Into" message + foreach ($comment_txns as $comment_txn) { + if ($comment_txn->hasComment()) { + $comment = $comment_txn->getComment()->getContent(); + $pattern = '/^\xE2\x9C\x98 Merged into T(\d+)\.$/'; + $matches = array(); + + if (preg_match($pattern, $comment, $matches)) { + $dst_task = id(new ManiphestTaskQuery()) + ->setViewer($viewer) + ->withIDs(array($matches[1])) + ->executeOne(); + + if ($dst_task) { + $dst_phid = $dst_task->getPHID(); + $add_edges[] = array( + 'src' => $src_phid, + 'dst' => $dst_phid, + ); + } + } + } + } + } +} + +if ($add_edges) { + foreach ($add_edges as $edge) { + $src_phid = $edge['src']; + $dst_phid = $edge['dst']; + + $type = ManiphestTaskIsDuplicateOfTaskEdgeType::EDGECONST; + try { + $editor = id(new PhabricatorEdgeEditor()) + ->addEdge($src_phid, $type, $dst_phid) + ->save(); + } catch (PhabricatorEdgeCycleException $ex) { + // Some earlier or later merge made this invalid, just skip it. + } + } +} diff --git a/resources/sql/autopatches/20170612.repository.image.01.sql b/resources/sql/autopatches/20170612.repository.image.01.sql new file mode 100644 index 0000000000..662c398855 --- /dev/null +++ b/resources/sql/autopatches/20170612.repository.image.01.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_repository.repository + ADD profileImagePHID VARBINARY(64); diff --git a/resources/sql/autopatches/20170614.taskstatus.sql b/resources/sql/autopatches/20170614.taskstatus.sql new file mode 100644 index 0000000000..2543632093 --- /dev/null +++ b/resources/sql/autopatches/20170614.taskstatus.sql @@ -0,0 +1,4 @@ +/* Extend from 12 characters to 64. */ + +ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task + CHANGE status status VARCHAR(64) COLLATE {$COLLATE_TEXT} NOT NULL; diff --git a/resources/sql/autopatches/20170725.legalpad.date.01.sql b/resources/sql/autopatches/20170725.legalpad.date.01.sql new file mode 100644 index 0000000000..a091220894 --- /dev/null +++ b/resources/sql/autopatches/20170725.legalpad.date.01.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_legalpad.legalpad_documentbody + SET dateCreated = dateModified; diff --git a/resources/sql/autopatches/20170811.differential.01.status.php b/resources/sql/autopatches/20170811.differential.01.status.php new file mode 100644 index 0000000000..9d57d033af --- /dev/null +++ b/resources/sql/autopatches/20170811.differential.01.status.php @@ -0,0 +1,48 @@ +" +// control with hard-coded status groups for status selection to using a +// tokenizer with status functions. + +$table = new PhabricatorSavedQuery(); +$conn = $table->establishConnection('w'); + +$status_map = array( + 'status-open' => array('open()'), + 'status-closed' => array('closed()'), + + 'status-accepted' => array('accepted'), + 'status-needs-review' => array('needs-review'), + 'status-needs-revision' => array('needs-revision'), + 'status-abandoned' => array('abandoned'), +); + +foreach (new LiskMigrationIterator($table) as $query) { + if ($query->getEngineClassName() !== 'DifferentialRevisionSearchEngine') { + // This isn't a revision query. + continue; + } + + $parameters = $query->getParameters(); + $status = idx($parameters, 'status'); + + if (!$status) { + // This query didn't specify a "status" value. + continue; + } + + if (!isset($status_map[$status])) { + // The "status" value is unknown, or does not correspond to a + // modern "status" constraint. + continue; + } + + $parameters['statuses'] = $status_map[$status]; + + queryfx( + $conn, + 'UPDATE %T SET parameters = %s WHERE id = %d', + $table->getTableName(), + phutil_json_encode($parameters), + $query->getID()); +} diff --git a/resources/sql/autopatches/20170811.differential.02.modernstatus.sql b/resources/sql/autopatches/20170811.differential.02.modernstatus.sql new file mode 100644 index 0000000000..a305206411 --- /dev/null +++ b/resources/sql/autopatches/20170811.differential.02.modernstatus.sql @@ -0,0 +1,17 @@ +UPDATE {$NAMESPACE}_differential.differential_revision + SET status = "needs-review" WHERE status = "0"; + +UPDATE {$NAMESPACE}_differential.differential_revision + SET status = "needs-revision" WHERE status = "1"; + +UPDATE {$NAMESPACE}_differential.differential_revision + SET status = "accepted" WHERE status = "2"; + +UPDATE {$NAMESPACE}_differential.differential_revision + SET status = "published" WHERE status = "3"; + +UPDATE {$NAMESPACE}_differential.differential_revision + SET status = "abandoned" WHERE status = "4"; + +UPDATE {$NAMESPACE}_differential.differential_revision + SET status = "changes-planned" WHERE status = "5"; diff --git a/resources/sql/autopatches/20170811.differential.03.modernxaction.php b/resources/sql/autopatches/20170811.differential.03.modernxaction.php new file mode 100644 index 0000000000..e84e3ce95b --- /dev/null +++ b/resources/sql/autopatches/20170811.differential.03.modernxaction.php @@ -0,0 +1,38 @@ + 'needs-review', + '1' => 'needs-revision', + '2' => 'accepted', + '3' => 'published', + '4' => 'abandoned', + '5' => 'changes-planned', +); + +$table = new DifferentialTransaction(); +$conn = $table->establishConnection('w'); + +foreach (new LiskMigrationIterator($table) as $xaction) { + $type = $xaction->getTransactionType(); + + if (($type != 'differential:status') && + ($type != 'differential.revision.status')) { + continue; + } + + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + + $old = idx($map, $old, $old); + $new = idx($map, $new, $new); + + queryfx( + $conn, + 'UPDATE %T SET transactionType = %s, oldValue = %s, newValue = %s + WHERE id = %d', + $table->getTableName(), + 'differential.revision.status', + json_encode($old), + json_encode($new), + $xaction->getID()); +} diff --git a/resources/sql/autopatches/20170814.search.01.qconfig.sql b/resources/sql/autopatches/20170814.search.01.qconfig.sql new file mode 100644 index 0000000000..7914336dc4 --- /dev/null +++ b/resources/sql/autopatches/20170814.search.01.qconfig.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_search.search_namedqueryconfig ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + engineClassName VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT}, + scopePHID VARBINARY(64) NOT NULL, + properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_scope` (engineClassName, scopePHID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170820.phame.01.post.views.sql b/resources/sql/autopatches/20170820.phame.01.post.views.sql new file mode 100644 index 0000000000..f5f72294f6 --- /dev/null +++ b/resources/sql/autopatches/20170820.phame.01.post.views.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phame.phame_post + ADD views INTEGER NOT NULL; diff --git a/resources/sql/autopatches/20170820.phame.02.post.views.sql b/resources/sql/autopatches/20170820.phame.02.post.views.sql new file mode 100644 index 0000000000..00b9b29203 --- /dev/null +++ b/resources/sql/autopatches/20170820.phame.02.post.views.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_phame.phame_post + SET views = 0; diff --git a/resources/sql/autopatches/20170824.search.01.saved.php b/resources/sql/autopatches/20170824.search.01.saved.php new file mode 100644 index 0000000000..ab1485ebd5 --- /dev/null +++ b/resources/sql/autopatches/20170824.search.01.saved.php @@ -0,0 +1,46 @@ +establishConnection('w'); + +$config_table = new PhabricatorNamedQueryConfig(); + +foreach (new LiskMigrationIterator($table) as $named_query) { + + // If this isn't a builtin query, it isn't changing. Leave it alone. + if (!$named_query->getIsBuiltin()) { + continue; + } + + // If the user reordered things but left a builtin query at the top, pin + // the query before we remove the row. + if ($named_query->getSequence() == 1) { + queryfx( + $conn, + 'INSERT IGNORE INTO %T + (engineClassName, scopePHID, properties, dateCreated, dateModified) + VALUES + (%s, %s, %s, %d, %d)', + $config_table->getTableName(), + $named_query->getEngineClassName(), + $named_query->getUserPHID(), + phutil_json_encode( + array( + PhabricatorNamedQueryConfig::PROPERTY_PINNED => + $named_query->getQueryKey(), + )), + PhabricatorTime::getNow(), + PhabricatorTime::getNow()); + } + + $named_query->delete(); +} diff --git a/resources/sql/autopatches/20170825.phame.01.post.views.sql b/resources/sql/autopatches/20170825.phame.01.post.views.sql new file mode 100644 index 0000000000..5cb5c9c7b6 --- /dev/null +++ b/resources/sql/autopatches/20170825.phame.01.post.views.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phame.phame_post + DROP COLUMN views; diff --git a/resources/sql/autopatches/20170828.ferret.01.taskdoc.sql b/resources/sql/autopatches/20170828.ferret.01.taskdoc.sql new file mode 100644 index 0000000000..8cb6835602 --- /dev/null +++ b/resources/sql/autopatches/20170828.ferret.01.taskdoc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_maniphest.maniphest_task_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170828.ferret.02.taskfield.sql b/resources/sql/autopatches/20170828.ferret.02.taskfield.sql new file mode 100644 index 0000000000..5528feec8f --- /dev/null +++ b/resources/sql/autopatches/20170828.ferret.02.taskfield.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_maniphest.maniphest_task_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170828.ferret.03.taskngrams.sql b/resources/sql/autopatches/20170828.ferret.03.taskngrams.sql new file mode 100644 index 0000000000..a7b5180642 --- /dev/null +++ b/resources/sql/autopatches/20170828.ferret.03.taskngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_maniphest.maniphest_task_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170830.ferret.01.unique.sql b/resources/sql/autopatches/20170830.ferret.01.unique.sql new file mode 100644 index 0000000000..f76c5050e8 --- /dev/null +++ b/resources/sql/autopatches/20170830.ferret.01.unique.sql @@ -0,0 +1,4 @@ +TRUNCATE TABLE {$NAMESPACE}_maniphest.maniphest_task_ffield; + +ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task_ffield + ADD UNIQUE KEY `key_documentfield` (documentID, fieldKey); diff --git a/resources/sql/autopatches/20170830.ferret.02.term.sql b/resources/sql/autopatches/20170830.ferret.02.term.sql new file mode 100644 index 0000000000..81a619d85d --- /dev/null +++ b/resources/sql/autopatches/20170830.ferret.02.term.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task_ffield + ADD termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}; diff --git a/resources/sql/autopatches/20170905.ferret.01.diff.doc.sql b/resources/sql/autopatches/20170905.ferret.01.diff.doc.sql new file mode 100644 index 0000000000..9fdadbf11c --- /dev/null +++ b/resources/sql/autopatches/20170905.ferret.01.diff.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_differential.differential_revision_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170905.ferret.02.diff.field.sql b/resources/sql/autopatches/20170905.ferret.02.diff.field.sql new file mode 100644 index 0000000000..ff5f065a39 --- /dev/null +++ b/resources/sql/autopatches/20170905.ferret.02.diff.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_differential.differential_revision_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170905.ferret.03.diff.ngrams.sql b/resources/sql/autopatches/20170905.ferret.03.diff.ngrams.sql new file mode 100644 index 0000000000..ec12354e38 --- /dev/null +++ b/resources/sql/autopatches/20170905.ferret.03.diff.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_differential.differential_revision_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.01.user.doc.sql b/resources/sql/autopatches/20170907.ferret.01.user.doc.sql new file mode 100644 index 0000000000..39496a0de0 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.01.user.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_user.user_user_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.02.user.field.sql b/resources/sql/autopatches/20170907.ferret.02.user.field.sql new file mode 100644 index 0000000000..3179e58e5b --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.02.user.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_user.user_user_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.03.user.ngrams.sql b/resources/sql/autopatches/20170907.ferret.03.user.ngrams.sql new file mode 100644 index 0000000000..2105a7b7af --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.03.user.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_user.user_user_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.04.fund.doc.sql b/resources/sql/autopatches/20170907.ferret.04.fund.doc.sql new file mode 100644 index 0000000000..a7f8324594 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.04.fund.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_fund.fund_initiative_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.05.fund.field.sql b/resources/sql/autopatches/20170907.ferret.05.fund.field.sql new file mode 100644 index 0000000000..b8c544c2a7 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.05.fund.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_fund.fund_initiative_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.06.fund.ngrams.sql b/resources/sql/autopatches/20170907.ferret.06.fund.ngrams.sql new file mode 100644 index 0000000000..a509087bae --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.06.fund.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_fund.fund_initiative_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.07.passphrase.doc.sql b/resources/sql/autopatches/20170907.ferret.07.passphrase.doc.sql new file mode 100644 index 0000000000..6787528d0e --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.07.passphrase.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_passphrase.passphrase_credential_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.08.passphrase.field.sql b/resources/sql/autopatches/20170907.ferret.08.passphrase.field.sql new file mode 100644 index 0000000000..6dc62d477e --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.08.passphrase.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_passphrase.passphrase_credential_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.09.passphrase.ngrams.sql b/resources/sql/autopatches/20170907.ferret.09.passphrase.ngrams.sql new file mode 100644 index 0000000000..2b64beb7ed --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.09.passphrase.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_passphrase.passphrase_credential_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.10.owners.doc.sql b/resources/sql/autopatches/20170907.ferret.10.owners.doc.sql new file mode 100644 index 0000000000..aaaa36623b --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.10.owners.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_owners.owners_package_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.11.owners.field.sql b/resources/sql/autopatches/20170907.ferret.11.owners.field.sql new file mode 100644 index 0000000000..ebd72806f4 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.11.owners.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_owners.owners_package_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.12.owners.ngrams.sql b/resources/sql/autopatches/20170907.ferret.12.owners.ngrams.sql new file mode 100644 index 0000000000..0f8c6865bf --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.12.owners.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_owners.owners_package_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.13.blog.doc.sql b/resources/sql/autopatches/20170907.ferret.13.blog.doc.sql new file mode 100644 index 0000000000..d75232fae1 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.13.blog.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_phame.phame_blog_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.14.blog.field.sql b/resources/sql/autopatches/20170907.ferret.14.blog.field.sql new file mode 100644 index 0000000000..9982914229 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.14.blog.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_phame.phame_blog_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.15.blog.ngrams.sql b/resources/sql/autopatches/20170907.ferret.15.blog.ngrams.sql new file mode 100644 index 0000000000..b20bb8fcbb --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.15.blog.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_phame.phame_blog_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.16.post.doc.sql b/resources/sql/autopatches/20170907.ferret.16.post.doc.sql new file mode 100644 index 0000000000..9f9155aa49 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.16.post.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_phame.phame_post_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.17.post.field.sql b/resources/sql/autopatches/20170907.ferret.17.post.field.sql new file mode 100644 index 0000000000..26d729d05d --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.17.post.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_phame.phame_post_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.18.post.ngrams.sql b/resources/sql/autopatches/20170907.ferret.18.post.ngrams.sql new file mode 100644 index 0000000000..18e534e948 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.18.post.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_phame.phame_post_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.19.project.doc.sql b/resources/sql/autopatches/20170907.ferret.19.project.doc.sql new file mode 100644 index 0000000000..26272439cf --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.19.project.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_project.project_project_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.20.project.field.sql b/resources/sql/autopatches/20170907.ferret.20.project.field.sql new file mode 100644 index 0000000000..36514eb55d --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.20.project.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_project.project_project_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.21.project.ngrams.sql b/resources/sql/autopatches/20170907.ferret.21.project.ngrams.sql new file mode 100644 index 0000000000..dec12b0e56 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.21.project.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_project.project_project_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.22.phriction.doc.sql b/resources/sql/autopatches/20170907.ferret.22.phriction.doc.sql new file mode 100644 index 0000000000..9de7124255 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.22.phriction.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_phriction.phriction_document_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.23.phriction.field.sql b/resources/sql/autopatches/20170907.ferret.23.phriction.field.sql new file mode 100644 index 0000000000..0fc5b959d1 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.23.phriction.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_phriction.phriction_document_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.24.phriction.ngrams.sql b/resources/sql/autopatches/20170907.ferret.24.phriction.ngrams.sql new file mode 100644 index 0000000000..abbb90a1e4 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.24.phriction.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_phriction.phriction_document_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.25.event.doc.sql b/resources/sql/autopatches/20170907.ferret.25.event.doc.sql new file mode 100644 index 0000000000..d7298fad31 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.25.event.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_event_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.26.event.field.sql b/resources/sql/autopatches/20170907.ferret.26.event.field.sql new file mode 100644 index 0000000000..2ec76c3511 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.26.event.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_event_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.27.event.ngrams.sql b/resources/sql/autopatches/20170907.ferret.27.event.ngrams.sql new file mode 100644 index 0000000000..e802e2d97e --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.27.event.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_event_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.28.mock.doc.sql b/resources/sql/autopatches/20170907.ferret.28.mock.doc.sql new file mode 100644 index 0000000000..eb80ef3937 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.28.mock.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_pholio.pholio_mock_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.29.mock.field.sql b/resources/sql/autopatches/20170907.ferret.29.mock.field.sql new file mode 100644 index 0000000000..0cb0e97d05 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.29.mock.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_pholio.pholio_mock_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.30.mock.ngrams.sql b/resources/sql/autopatches/20170907.ferret.30.mock.ngrams.sql new file mode 100644 index 0000000000..e343ccf83b --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.30.mock.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_pholio.pholio_mock_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.31.repo.doc.sql b/resources/sql/autopatches/20170907.ferret.31.repo.doc.sql new file mode 100644 index 0000000000..4f37de60be --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.31.repo.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_repository_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.32.repo.field.sql b/resources/sql/autopatches/20170907.ferret.32.repo.field.sql new file mode 100644 index 0000000000..c7d75eb29d --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.32.repo.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_repository_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.33.repo.ngrams.sql b/resources/sql/autopatches/20170907.ferret.33.repo.ngrams.sql new file mode 100644 index 0000000000..db7ad4f3a0 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.33.repo.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_repository_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.34.commit.doc.sql b/resources/sql/autopatches/20170907.ferret.34.commit.doc.sql new file mode 100644 index 0000000000..9c275b09b7 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.34.commit.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_commit_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.35.commit.field.sql b/resources/sql/autopatches/20170907.ferret.35.commit.field.sql new file mode 100644 index 0000000000..c2520b693b --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.35.commit.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_commit_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.36.commit.ngrams.sql b/resources/sql/autopatches/20170907.ferret.36.commit.ngrams.sql new file mode 100644 index 0000000000..32ed2275c3 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.36.commit.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_commit_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/patches/20131020.pxactionmig.php b/resources/sql/patches/20131020.pxactionmig.php index 3d593d6e34..9a7b82f3e8 100644 --- a/resources/sql/patches/20131020.pxactionmig.php +++ b/resources/sql/patches/20131020.pxactionmig.php @@ -32,9 +32,9 @@ foreach ($rows as $row) { $project_phid = $project_row['phid']; $type_map = array( - 'name' => PhabricatorProjectTransaction::TYPE_NAME, + 'name' => PhabricatorProjectNameTransaction::TRANSACTIONTYPE, 'members' => PhabricatorProjectTransaction::TYPE_MEMBERS, - 'status' => PhabricatorProjectTransaction::TYPE_STATUS, + 'status' => PhabricatorProjectStatusTransaction::TRANSACTIONTYPE, 'canview' => PhabricatorTransactions::TYPE_VIEW_POLICY, 'canedit' => PhabricatorTransactions::TYPE_EDIT_POLICY, 'canjoin' => PhabricatorTransactions::TYPE_JOIN_POLICY, diff --git a/resources/sshd/sshd_config.phabricator.example b/resources/sshd/sshd_config.phabricator.example index 2f2a581223..506d32bbbf 100644 --- a/resources/sshd/sshd_config.phabricator.example +++ b/resources/sshd/sshd_config.phabricator.example @@ -18,6 +18,7 @@ AllowTcpForwarding no PrintMotd no PrintLastLog no PasswordAuthentication no +ChallengeResponseAuthentication no AuthorizedKeysFile none PidFile /var/run/sshd-phabricator.pid diff --git a/scripts/repository/commit_hook.php b/scripts/repository/commit_hook.php index 44c772225c..51abcb6c89 100755 --- a/scripts/repository/commit_hook.php +++ b/scripts/repository/commit_hook.php @@ -48,8 +48,13 @@ if (!$repository) { } if (!$repository->isHosted()) { - // This should be redundant, but double check just in case. - throw new Exception(pht('Repository "%s" is not hosted!', $argv[1])); + // In Mercurial, the "pretxnchangegroup" hook fires for both pulls and + // pushes. Normally we only install the hook for hosted repositories, but + // if a hosted repository is later converted into an observed repository we + // can end up with an observed repository that has the hook installed. + // If we're running hooks from an observed repository, just exit without + // taking action. For more discussion, see PHI24. + return 0; } $engine->setRepository($repository); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fae3311e77..a5dbc1b0e2 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -223,6 +223,7 @@ phutil_register_library_map(array( 'CalendarTimeUtil' => 'applications/calendar/util/CalendarTimeUtil.php', 'CalendarTimeUtilTestCase' => 'applications/calendar/__tests__/CalendarTimeUtilTestCase.php', 'CelerityAPI' => 'applications/celerity/CelerityAPI.php', + 'CelerityDarkModePostprocessor' => 'applications/celerity/postprocessor/CelerityDarkModePostprocessor.php', 'CelerityDefaultPostprocessor' => 'applications/celerity/postprocessor/CelerityDefaultPostprocessor.php', 'CelerityHighContrastPostprocessor' => 'applications/celerity/postprocessor/CelerityHighContrastPostprocessor.php', 'CelerityLargeFontPostprocessor' => 'applications/celerity/postprocessor/CelerityLargeFontPostprocessor.php', @@ -292,13 +293,14 @@ phutil_register_library_map(array( 'ConpherenceCreateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php', 'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php', 'ConpherenceDurableColumnView' => 'applications/conpherence/view/ConpherenceDurableColumnView.php', + 'ConpherenceEditConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceEditConduitAPIMethod.php', + 'ConpherenceEditEngine' => 'applications/conpherence/editor/ConpherenceEditEngine.php', 'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php', 'ConpherenceFulltextQuery' => 'applications/conpherence/query/ConpherenceFulltextQuery.php', 'ConpherenceIndex' => 'applications/conpherence/storage/ConpherenceIndex.php', 'ConpherenceLayoutView' => 'applications/conpherence/view/ConpherenceLayoutView.php', 'ConpherenceListController' => 'applications/conpherence/controller/ConpherenceListController.php', 'ConpherenceMenuItemView' => 'applications/conpherence/view/ConpherenceMenuItemView.php', - 'ConpherenceNewRoomController' => 'applications/conpherence/controller/ConpherenceNewRoomController.php', 'ConpherenceNotificationPanelController' => 'applications/conpherence/controller/ConpherenceNotificationPanelController.php', 'ConpherenceParticipant' => 'applications/conpherence/storage/ConpherenceParticipant.php', 'ConpherenceParticipantController' => 'applications/conpherence/controller/ConpherenceParticipantController.php', @@ -308,6 +310,7 @@ phutil_register_library_map(array( 'ConpherenceQueryThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php', 'ConpherenceQueryTransactionConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryTransactionConduitAPIMethod.php', 'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php', + 'ConpherenceRoomEditController' => 'applications/conpherence/controller/ConpherenceRoomEditController.php', 'ConpherenceRoomListController' => 'applications/conpherence/controller/ConpherenceRoomListController.php', 'ConpherenceRoomPictureController' => 'applications/conpherence/controller/ConpherenceRoomPictureController.php', 'ConpherenceRoomPreferencesController' => 'applications/conpherence/controller/ConpherenceRoomPreferencesController.php', @@ -439,11 +442,9 @@ phutil_register_library_map(array( 'DifferentialDiffTransactionQuery' => 'applications/differential/query/DifferentialDiffTransactionQuery.php', 'DifferentialDiffViewController' => 'applications/differential/controller/DifferentialDiffViewController.php', 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php', - 'DifferentialDraft' => 'applications/differential/storage/DifferentialDraft.php', 'DifferentialExactUserFunctionDatasource' => 'applications/differential/typeahead/DifferentialExactUserFunctionDatasource.php', 'DifferentialFieldParseException' => 'applications/differential/exception/DifferentialFieldParseException.php', 'DifferentialFieldValidationException' => 'applications/differential/exception/DifferentialFieldValidationException.php', - 'DifferentialFindConduitAPIMethod' => 'applications/differential/conduit/DifferentialFindConduitAPIMethod.php', 'DifferentialGetAllDiffsConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetAllDiffsConduitAPIMethod.php', 'DifferentialGetCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetCommitMessageConduitAPIMethod.php', 'DifferentialGetCommitPathsConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetCommitPathsConduitAPIMethod.php', @@ -452,13 +453,10 @@ phutil_register_library_map(array( 'DifferentialGetRevisionCommentsConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetRevisionCommentsConduitAPIMethod.php', 'DifferentialGetRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php', 'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php', - 'DifferentialGitHubLandingStrategy' => 'applications/differential/landing/DifferentialGitHubLandingStrategy.php', 'DifferentialGitSVNIDCommitMessageField' => 'applications/differential/field/DifferentialGitSVNIDCommitMessageField.php', 'DifferentialHarbormasterField' => 'applications/differential/customfield/DifferentialHarbormasterField.php', 'DifferentialHiddenComment' => 'applications/differential/storage/DifferentialHiddenComment.php', 'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php', - 'DifferentialHostedGitLandingStrategy' => 'applications/differential/landing/DifferentialHostedGitLandingStrategy.php', - 'DifferentialHostedMercurialLandingStrategy' => 'applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php', 'DifferentialHovercardEngineExtension' => 'applications/differential/engineextension/DifferentialHovercardEngineExtension.php', 'DifferentialHunk' => 'applications/differential/storage/DifferentialHunk.php', 'DifferentialHunkParser' => 'applications/differential/parser/DifferentialHunkParser.php', @@ -471,9 +469,8 @@ phutil_register_library_map(array( 'DifferentialInlineCommentQuery' => 'applications/differential/query/DifferentialInlineCommentQuery.php', 'DifferentialJIRAIssuesCommitMessageField' => 'applications/differential/field/DifferentialJIRAIssuesCommitMessageField.php', 'DifferentialJIRAIssuesField' => 'applications/differential/customfield/DifferentialJIRAIssuesField.php', - 'DifferentialLandingActionMenuEventListener' => 'applications/differential/landing/DifferentialLandingActionMenuEventListener.php', - 'DifferentialLandingStrategy' => 'applications/differential/landing/DifferentialLandingStrategy.php', 'DifferentialLegacyHunk' => 'applications/differential/storage/DifferentialLegacyHunk.php', + 'DifferentialLegacyQuery' => 'applications/differential/constants/DifferentialLegacyQuery.php', 'DifferentialLineAdjustmentMap' => 'applications/differential/parser/DifferentialLineAdjustmentMap.php', 'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php', 'DifferentialLintStatus' => 'applications/differential/constants/DifferentialLintStatus.php', @@ -523,6 +520,7 @@ phutil_register_library_map(array( 'DifferentialRevisionAuthorProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php', 'DifferentialRevisionCloseDetailsController' => 'applications/differential/controller/DifferentialRevisionCloseDetailsController.php', 'DifferentialRevisionCloseTransaction' => 'applications/differential/xaction/DifferentialRevisionCloseTransaction.php', + 'DifferentialRevisionClosedStatusDatasource' => 'applications/differential/typeahead/DifferentialRevisionClosedStatusDatasource.php', 'DifferentialRevisionCommandeerTransaction' => 'applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php', 'DifferentialRevisionContentAddedHeraldField' => 'applications/differential/herald/DifferentialRevisionContentAddedHeraldField.php', 'DifferentialRevisionContentHeraldField' => 'applications/differential/herald/DifferentialRevisionContentHeraldField.php', @@ -534,6 +532,7 @@ phutil_register_library_map(array( 'DifferentialRevisionEditConduitAPIMethod' => 'applications/differential/conduit/DifferentialRevisionEditConduitAPIMethod.php', 'DifferentialRevisionEditController' => 'applications/differential/controller/DifferentialRevisionEditController.php', 'DifferentialRevisionEditEngine' => 'applications/differential/editor/DifferentialRevisionEditEngine.php', + 'DifferentialRevisionFerretEngine' => 'applications/differential/search/DifferentialRevisionFerretEngine.php', 'DifferentialRevisionFulltextEngine' => 'applications/differential/search/DifferentialRevisionFulltextEngine.php', 'DifferentialRevisionGraph' => 'infrastructure/graph/DifferentialRevisionGraph.php', 'DifferentialRevisionHasChildRelationship' => 'applications/differential/relationships/DifferentialRevisionHasChildRelationship.php', @@ -546,10 +545,12 @@ phutil_register_library_map(array( 'DifferentialRevisionHeraldField' => 'applications/differential/herald/DifferentialRevisionHeraldField.php', 'DifferentialRevisionHeraldFieldGroup' => 'applications/differential/herald/DifferentialRevisionHeraldFieldGroup.php', 'DifferentialRevisionIDCommitMessageField' => 'applications/differential/field/DifferentialRevisionIDCommitMessageField.php', - 'DifferentialRevisionLandController' => 'applications/differential/controller/DifferentialRevisionLandController.php', + 'DifferentialRevisionInlineTransaction' => 'applications/differential/xaction/DifferentialRevisionInlineTransaction.php', + 'DifferentialRevisionInlinesController' => 'applications/differential/controller/DifferentialRevisionInlinesController.php', 'DifferentialRevisionListController' => 'applications/differential/controller/DifferentialRevisionListController.php', 'DifferentialRevisionListView' => 'applications/differential/view/DifferentialRevisionListView.php', 'DifferentialRevisionMailReceiver' => 'applications/differential/mail/DifferentialRevisionMailReceiver.php', + 'DifferentialRevisionOpenStatusDatasource' => 'applications/differential/typeahead/DifferentialRevisionOpenStatusDatasource.php', 'DifferentialRevisionOperationController' => 'applications/differential/controller/DifferentialRevisionOperationController.php', 'DifferentialRevisionPHIDType' => 'applications/differential/phid/DifferentialRevisionPHIDType.php', 'DifferentialRevisionPackageHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageHeraldField.php', @@ -574,6 +575,9 @@ phutil_register_library_map(array( 'DifferentialRevisionSearchConduitAPIMethod' => 'applications/differential/conduit/DifferentialRevisionSearchConduitAPIMethod.php', 'DifferentialRevisionSearchEngine' => 'applications/differential/query/DifferentialRevisionSearchEngine.php', 'DifferentialRevisionStatus' => 'applications/differential/constants/DifferentialRevisionStatus.php', + 'DifferentialRevisionStatusDatasource' => 'applications/differential/typeahead/DifferentialRevisionStatusDatasource.php', + 'DifferentialRevisionStatusFunctionDatasource' => 'applications/differential/typeahead/DifferentialRevisionStatusFunctionDatasource.php', + 'DifferentialRevisionStatusTransaction' => 'applications/differential/xaction/DifferentialRevisionStatusTransaction.php', 'DifferentialRevisionSummaryHeraldField' => 'applications/differential/herald/DifferentialRevisionSummaryHeraldField.php', 'DifferentialRevisionSummaryTransaction' => 'applications/differential/xaction/DifferentialRevisionSummaryTransaction.php', 'DifferentialRevisionTestPlanTransaction' => 'applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php', @@ -611,6 +615,7 @@ phutil_register_library_map(array( 'DiffusionBlameConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php', 'DiffusionBlameQuery' => 'applications/diffusion/query/blame/DiffusionBlameQuery.php', 'DiffusionBlockHeraldAction' => 'applications/diffusion/herald/DiffusionBlockHeraldAction.php', + 'DiffusionBranchListView' => 'applications/diffusion/view/DiffusionBranchListView.php', 'DiffusionBranchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php', 'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php', 'DiffusionBranchTableView' => 'applications/diffusion/view/DiffusionBranchTableView.php', @@ -622,6 +627,7 @@ phutil_register_library_map(array( 'DiffusionCachedResolveRefsQuery' => 'applications/diffusion/query/DiffusionCachedResolveRefsQuery.php', 'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php', 'DiffusionChangeHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionChangeHeraldFieldGroup.php', + 'DiffusionCloneController' => 'applications/diffusion/controller/DiffusionCloneController.php', 'DiffusionCloneURIView' => 'applications/diffusion/view/DiffusionCloneURIView.php', 'DiffusionCommandEngine' => 'applications/diffusion/protocol/DiffusionCommandEngine.php', 'DiffusionCommandEngineTestCase' => 'applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php', @@ -646,6 +652,7 @@ phutil_register_library_map(array( 'DiffusionCommitEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCommitEditConduitAPIMethod.php', 'DiffusionCommitEditController' => 'applications/diffusion/controller/DiffusionCommitEditController.php', 'DiffusionCommitEditEngine' => 'applications/diffusion/editor/DiffusionCommitEditEngine.php', + 'DiffusionCommitFerretEngine' => 'applications/repository/search/DiffusionCommitFerretEngine.php', 'DiffusionCommitFulltextEngine' => 'applications/repository/search/DiffusionCommitFulltextEngine.php', 'DiffusionCommitHasPackageEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasPackageEdgeType.php', 'DiffusionCommitHasRevisionEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasRevisionEdgeType.php', @@ -659,6 +666,7 @@ phutil_register_library_map(array( 'DiffusionCommitHookEngine' => 'applications/diffusion/engine/DiffusionCommitHookEngine.php', 'DiffusionCommitHookRejectException' => 'applications/diffusion/exception/DiffusionCommitHookRejectException.php', 'DiffusionCommitListController' => 'applications/diffusion/controller/DiffusionCommitListController.php', + 'DiffusionCommitListView' => 'applications/diffusion/view/DiffusionCommitListView.php', 'DiffusionCommitMergeHeraldField' => 'applications/diffusion/herald/DiffusionCommitMergeHeraldField.php', 'DiffusionCommitMessageHeraldField' => 'applications/diffusion/herald/DiffusionCommitMessageHeraldField.php', 'DiffusionCommitPackageAuditHeraldField' => 'applications/diffusion/herald/DiffusionCommitPackageAuditHeraldField.php', @@ -725,9 +733,12 @@ phutil_register_library_map(array( 'DiffusionGitResponse' => 'applications/diffusion/response/DiffusionGitResponse.php', 'DiffusionGitSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitSSHWorkflow.php', 'DiffusionGitUploadPackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php', + 'DiffusionGraphController' => 'applications/diffusion/controller/DiffusionGraphController.php', 'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php', + 'DiffusionHistoryListView' => 'applications/diffusion/view/DiffusionHistoryListView.php', 'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php', 'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php', + 'DiffusionHistoryView' => 'applications/diffusion/view/DiffusionHistoryView.php', 'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php', 'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php', 'DiffusionInlineCommentPreviewController' => 'applications/diffusion/controller/DiffusionInlineCommentPreviewController.php', @@ -744,7 +755,6 @@ phutil_register_library_map(array( 'DiffusionLowLevelGitRefQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php', 'DiffusionLowLevelMercurialBranchesQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialBranchesQuery.php', 'DiffusionLowLevelMercurialPathsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php', - 'DiffusionLowLevelMercurialPathsQueryTests' => 'applications/diffusion/query/lowlevel/__tests__/DiffusionLowLevelMercurialPathsQueryTests.php', 'DiffusionLowLevelParentsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php', 'DiffusionLowLevelQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php', 'DiffusionLowLevelResolveRefsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php', @@ -769,6 +779,7 @@ phutil_register_library_map(array( 'DiffusionPathQueryTestCase' => 'applications/diffusion/query/pathid/__tests__/DiffusionPathQueryTestCase.php', 'DiffusionPathTreeController' => 'applications/diffusion/controller/DiffusionPathTreeController.php', 'DiffusionPathValidateController' => 'applications/diffusion/controller/DiffusionPathValidateController.php', + 'DiffusionPatternSearchView' => 'applications/diffusion/view/DiffusionPatternSearchView.php', 'DiffusionPhpExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionPhpExternalSymbolsSource.php', 'DiffusionPreCommitContentAffectedFilesHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentAffectedFilesHeraldField.php', 'DiffusionPreCommitContentAuthorHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentAuthorHeraldField.php', @@ -830,7 +841,6 @@ phutil_register_library_map(array( 'DiffusionRepositoryController' => 'applications/diffusion/controller/DiffusionRepositoryController.php', 'DiffusionRepositoryDatasource' => 'applications/diffusion/typeahead/DiffusionRepositoryDatasource.php', 'DiffusionRepositoryDefaultController' => 'applications/diffusion/controller/DiffusionRepositoryDefaultController.php', - 'DiffusionRepositoryDocumentationManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php', 'DiffusionRepositoryEditActivateController' => 'applications/diffusion/controller/DiffusionRepositoryEditActivateController.php', 'DiffusionRepositoryEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRepositoryEditConduitAPIMethod.php', 'DiffusionRepositoryEditController' => 'applications/diffusion/controller/DiffusionRepositoryEditController.php', @@ -846,11 +856,11 @@ phutil_register_library_map(array( 'DiffusionRepositoryManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryManagementPanel.php', 'DiffusionRepositoryPath' => 'applications/diffusion/data/DiffusionRepositoryPath.php', 'DiffusionRepositoryPoliciesManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php', + 'DiffusionRepositoryProfilePictureController' => 'applications/diffusion/controller/DiffusionRepositoryProfilePictureController.php', 'DiffusionRepositoryRef' => 'applications/diffusion/data/DiffusionRepositoryRef.php', 'DiffusionRepositoryRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryRemarkupRule.php', 'DiffusionRepositorySearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRepositorySearchConduitAPIMethod.php', 'DiffusionRepositoryStagingManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php', - 'DiffusionRepositoryStatusManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php', 'DiffusionRepositoryStorageManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php', 'DiffusionRepositorySubversionManagementPanel' => 'applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php', 'DiffusionRepositorySymbolsManagementPanel' => 'applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php', @@ -885,6 +895,7 @@ phutil_register_library_map(array( 'DiffusionSymbolQuery' => 'applications/diffusion/query/DiffusionSymbolQuery.php', 'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php', 'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php', + 'DiffusionTagTableView' => 'applications/diffusion/view/DiffusionTagTableView.php', 'DiffusionTaggedRepositoriesFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionTaggedRepositoriesFunctionDatasource.php', 'DiffusionTagsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php', 'DiffusionURIEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionURIEditConduitAPIMethod.php', @@ -1122,29 +1133,41 @@ phutil_register_library_map(array( 'FundBackerPHIDType' => 'applications/fund/phid/FundBackerPHIDType.php', 'FundBackerProduct' => 'applications/fund/phortune/FundBackerProduct.php', 'FundBackerQuery' => 'applications/fund/query/FundBackerQuery.php', + 'FundBackerRefundTransaction' => 'applications/fund/xaction/FundBackerRefundTransaction.php', 'FundBackerSearchEngine' => 'applications/fund/query/FundBackerSearchEngine.php', + 'FundBackerStatusTransaction' => 'applications/fund/xaction/FundBackerStatusTransaction.php', 'FundBackerTransaction' => 'applications/fund/storage/FundBackerTransaction.php', 'FundBackerTransactionQuery' => 'applications/fund/query/FundBackerTransactionQuery.php', + 'FundBackerTransactionType' => 'applications/fund/xaction/FundBackerTransactionType.php', 'FundController' => 'applications/fund/controller/FundController.php', 'FundCreateInitiativesCapability' => 'applications/fund/capability/FundCreateInitiativesCapability.php', 'FundDAO' => 'applications/fund/storage/FundDAO.php', 'FundDefaultViewCapability' => 'applications/fund/capability/FundDefaultViewCapability.php', 'FundInitiative' => 'applications/fund/storage/FundInitiative.php', 'FundInitiativeBackController' => 'applications/fund/controller/FundInitiativeBackController.php', + 'FundInitiativeBackerTransaction' => 'applications/fund/xaction/FundInitiativeBackerTransaction.php', 'FundInitiativeCloseController' => 'applications/fund/controller/FundInitiativeCloseController.php', - 'FundInitiativeCommentController' => 'applications/fund/controller/FundInitiativeCommentController.php', + 'FundInitiativeDescriptionTransaction' => 'applications/fund/xaction/FundInitiativeDescriptionTransaction.php', 'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php', + 'FundInitiativeEditEngine' => 'applications/fund/editor/FundInitiativeEditEngine.php', 'FundInitiativeEditor' => 'applications/fund/editor/FundInitiativeEditor.php', + 'FundInitiativeFerretEngine' => 'applications/fund/search/FundInitiativeFerretEngine.php', 'FundInitiativeFulltextEngine' => 'applications/fund/search/FundInitiativeFulltextEngine.php', 'FundInitiativeListController' => 'applications/fund/controller/FundInitiativeListController.php', + 'FundInitiativeMerchantTransaction' => 'applications/fund/xaction/FundInitiativeMerchantTransaction.php', + 'FundInitiativeNameTransaction' => 'applications/fund/xaction/FundInitiativeNameTransaction.php', 'FundInitiativePHIDType' => 'applications/fund/phid/FundInitiativePHIDType.php', 'FundInitiativeQuery' => 'applications/fund/query/FundInitiativeQuery.php', + 'FundInitiativeRefundTransaction' => 'applications/fund/xaction/FundInitiativeRefundTransaction.php', 'FundInitiativeRemarkupRule' => 'applications/fund/remarkup/FundInitiativeRemarkupRule.php', 'FundInitiativeReplyHandler' => 'applications/fund/mail/FundInitiativeReplyHandler.php', + 'FundInitiativeRisksTransaction' => 'applications/fund/xaction/FundInitiativeRisksTransaction.php', 'FundInitiativeSearchEngine' => 'applications/fund/query/FundInitiativeSearchEngine.php', + 'FundInitiativeStatusTransaction' => 'applications/fund/xaction/FundInitiativeStatusTransaction.php', 'FundInitiativeTransaction' => 'applications/fund/storage/FundInitiativeTransaction.php', 'FundInitiativeTransactionComment' => 'applications/fund/storage/FundInitiativeTransactionComment.php', 'FundInitiativeTransactionQuery' => 'applications/fund/query/FundInitiativeTransactionQuery.php', + 'FundInitiativeTransactionType' => 'applications/fund/xaction/FundInitiativeTransactionType.php', 'FundInitiativeViewController' => 'applications/fund/controller/FundInitiativeViewController.php', 'FundSchemaSpec' => 'applications/fund/storage/FundSchemaSpec.php', 'HarbormasterArcLintBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php', @@ -1380,10 +1403,6 @@ phutil_register_library_map(array( 'HeraldTranscriptTestCase' => 'applications/herald/storage/__tests__/HeraldTranscriptTestCase.php', 'HeraldUtilityActionGroup' => 'applications/herald/action/HeraldUtilityActionGroup.php', 'Javelin' => 'infrastructure/javelin/Javelin.php', - 'JavelinReactorUIExample' => 'applications/uiexample/examples/JavelinReactorUIExample.php', - 'JavelinUIExample' => 'applications/uiexample/examples/JavelinUIExample.php', - 'JavelinViewExampleServerView' => 'applications/uiexample/examples/JavelinViewExampleServerView.php', - 'JavelinViewUIExample' => 'applications/uiexample/examples/JavelinViewUIExample.php', 'LegalpadController' => 'applications/legalpad/controller/LegalpadController.php', 'LegalpadCreateDocumentsCapability' => 'applications/legalpad/capability/LegalpadCreateDocumentsCapability.php', 'LegalpadDAO' => 'applications/legalpad/storage/LegalpadDAO.php', @@ -1391,15 +1410,17 @@ phutil_register_library_map(array( 'LegalpadDefaultViewCapability' => 'applications/legalpad/capability/LegalpadDefaultViewCapability.php', 'LegalpadDocument' => 'applications/legalpad/storage/LegalpadDocument.php', 'LegalpadDocumentBody' => 'applications/legalpad/storage/LegalpadDocumentBody.php', - 'LegalpadDocumentCommentController' => 'applications/legalpad/controller/LegalpadDocumentCommentController.php', 'LegalpadDocumentDatasource' => 'applications/legalpad/typeahead/LegalpadDocumentDatasource.php', 'LegalpadDocumentDoneController' => 'applications/legalpad/controller/LegalpadDocumentDoneController.php', 'LegalpadDocumentEditController' => 'applications/legalpad/controller/LegalpadDocumentEditController.php', + 'LegalpadDocumentEditEngine' => 'applications/legalpad/editor/LegalpadDocumentEditEngine.php', 'LegalpadDocumentEditor' => 'applications/legalpad/editor/LegalpadDocumentEditor.php', 'LegalpadDocumentListController' => 'applications/legalpad/controller/LegalpadDocumentListController.php', 'LegalpadDocumentManageController' => 'applications/legalpad/controller/LegalpadDocumentManageController.php', + 'LegalpadDocumentPreambleTransaction' => 'applications/legalpad/xaction/LegalpadDocumentPreambleTransaction.php', 'LegalpadDocumentQuery' => 'applications/legalpad/query/LegalpadDocumentQuery.php', 'LegalpadDocumentRemarkupRule' => 'applications/legalpad/remarkup/LegalpadDocumentRemarkupRule.php', + 'LegalpadDocumentRequireSignatureTransaction' => 'applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php', 'LegalpadDocumentSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSearchEngine.php', 'LegalpadDocumentSignController' => 'applications/legalpad/controller/LegalpadDocumentSignController.php', 'LegalpadDocumentSignature' => 'applications/legalpad/storage/LegalpadDocumentSignature.php', @@ -1407,8 +1428,12 @@ phutil_register_library_map(array( 'LegalpadDocumentSignatureListController' => 'applications/legalpad/controller/LegalpadDocumentSignatureListController.php', 'LegalpadDocumentSignatureQuery' => 'applications/legalpad/query/LegalpadDocumentSignatureQuery.php', 'LegalpadDocumentSignatureSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php', + 'LegalpadDocumentSignatureTypeTransaction' => 'applications/legalpad/xaction/LegalpadDocumentSignatureTypeTransaction.php', 'LegalpadDocumentSignatureVerificationController' => 'applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php', 'LegalpadDocumentSignatureViewController' => 'applications/legalpad/controller/LegalpadDocumentSignatureViewController.php', + 'LegalpadDocumentTextTransaction' => 'applications/legalpad/xaction/LegalpadDocumentTextTransaction.php', + 'LegalpadDocumentTitleTransaction' => 'applications/legalpad/xaction/LegalpadDocumentTitleTransaction.php', + 'LegalpadDocumentTransactionType' => 'applications/legalpad/xaction/LegalpadDocumentTransactionType.php', 'LegalpadMailReceiver' => 'applications/legalpad/mail/LegalpadMailReceiver.php', 'LegalpadObjectNeedsSignatureEdgeType' => 'applications/legalpad/edge/LegalpadObjectNeedsSignatureEdgeType.php', 'LegalpadReplyHandler' => 'applications/legalpad/mail/LegalpadReplyHandler.php', @@ -1432,6 +1457,7 @@ phutil_register_library_map(array( 'LiskRawMigrationIterator' => 'infrastructure/storage/lisk/LiskRawMigrationIterator.php', 'MacroConduitAPIMethod' => 'applications/macro/conduit/MacroConduitAPIMethod.php', 'MacroCreateMemeConduitAPIMethod' => 'applications/macro/conduit/MacroCreateMemeConduitAPIMethod.php', + 'MacroEditConduitAPIMethod' => 'applications/macro/conduit/MacroEditConduitAPIMethod.php', 'MacroEmojiExample' => 'applications/uiexample/examples/MacroEmojiExample.php', 'MacroQueryConduitAPIMethod' => 'applications/macro/conduit/MacroQueryConduitAPIMethod.php', 'ManiphestAssignEmailCommand' => 'applications/maniphest/command/ManiphestAssignEmailCommand.php', @@ -1471,9 +1497,10 @@ phutil_register_library_map(array( 'ManiphestHovercardEngineExtension' => 'applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php', 'ManiphestInfoConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php', 'ManiphestNameIndex' => 'applications/maniphest/storage/ManiphestNameIndex.php', - 'ManiphestPointsConfigOptionType' => 'applications/maniphest/config/ManiphestPointsConfigOptionType.php', - 'ManiphestPriorityConfigOptionType' => 'applications/maniphest/config/ManiphestPriorityConfigOptionType.php', + 'ManiphestPointsConfigType' => 'applications/maniphest/config/ManiphestPointsConfigType.php', + 'ManiphestPrioritiesConfigType' => 'applications/maniphest/config/ManiphestPrioritiesConfigType.php', 'ManiphestPriorityEmailCommand' => 'applications/maniphest/command/ManiphestPriorityEmailCommand.php', + 'ManiphestPrioritySearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestPrioritySearchConduitAPIMethod.php', 'ManiphestProjectNameFulltextEngineExtension' => 'applications/maniphest/engineextension/ManiphestProjectNameFulltextEngineExtension.php', 'ManiphestQueryConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php', 'ManiphestQueryStatusesConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestQueryStatusesConduitAPIMethod.php', @@ -1483,26 +1510,32 @@ phutil_register_library_map(array( 'ManiphestSchemaSpec' => 'applications/maniphest/storage/ManiphestSchemaSpec.php', 'ManiphestSearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestSearchConduitAPIMethod.php', 'ManiphestSearchController' => 'applications/maniphest/controller/ManiphestSearchController.php', - 'ManiphestStatusConfigOptionType' => 'applications/maniphest/config/ManiphestStatusConfigOptionType.php', 'ManiphestStatusEmailCommand' => 'applications/maniphest/command/ManiphestStatusEmailCommand.php', + 'ManiphestStatusSearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestStatusSearchConduitAPIMethod.php', + 'ManiphestStatusesConfigType' => 'applications/maniphest/config/ManiphestStatusesConfigType.php', 'ManiphestSubpriorityController' => 'applications/maniphest/controller/ManiphestSubpriorityController.php', - 'ManiphestSubtypesConfigOptionsType' => 'applications/maniphest/config/ManiphestSubtypesConfigOptionsType.php', + 'ManiphestSubtypesConfigType' => 'applications/maniphest/config/ManiphestSubtypesConfigType.php', 'ManiphestTask' => 'applications/maniphest/storage/ManiphestTask.php', 'ManiphestTaskAssignHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php', 'ManiphestTaskAssignOtherHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignOtherHeraldAction.php', 'ManiphestTaskAssignSelfHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignSelfHeraldAction.php', 'ManiphestTaskAssigneeHeraldField' => 'applications/maniphest/herald/ManiphestTaskAssigneeHeraldField.php', + 'ManiphestTaskAttachTransaction' => 'applications/maniphest/xaction/ManiphestTaskAttachTransaction.php', 'ManiphestTaskAuthorHeraldField' => 'applications/maniphest/herald/ManiphestTaskAuthorHeraldField.php', 'ManiphestTaskAuthorPolicyRule' => 'applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php', 'ManiphestTaskCloseAsDuplicateRelationship' => 'applications/maniphest/relationship/ManiphestTaskCloseAsDuplicateRelationship.php', 'ManiphestTaskClosedStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskClosedStatusDatasource.php', + 'ManiphestTaskCoverImageTransaction' => 'applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php', 'ManiphestTaskDependedOnByTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependedOnByTaskEdgeType.php', 'ManiphestTaskDependsOnTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependsOnTaskEdgeType.php', 'ManiphestTaskDescriptionHeraldField' => 'applications/maniphest/herald/ManiphestTaskDescriptionHeraldField.php', + 'ManiphestTaskDescriptionTransaction' => 'applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php', 'ManiphestTaskDetailController' => 'applications/maniphest/controller/ManiphestTaskDetailController.php', + 'ManiphestTaskEdgeTransaction' => 'applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php', 'ManiphestTaskEditBulkJobType' => 'applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php', 'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php', 'ManiphestTaskEditEngineLock' => 'applications/maniphest/editor/ManiphestTaskEditEngineLock.php', + 'ManiphestTaskFerretEngine' => 'applications/maniphest/search/ManiphestTaskFerretEngine.php', 'ManiphestTaskFulltextEngine' => 'applications/maniphest/search/ManiphestTaskFulltextEngine.php', 'ManiphestTaskGraph' => 'infrastructure/graph/ManiphestTaskGraph.php', 'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php', @@ -1522,14 +1555,20 @@ phutil_register_library_map(array( 'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php', 'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php', 'ManiphestTaskMergeInRelationship' => 'applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php', + 'ManiphestTaskMergedFromTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php', + 'ManiphestTaskMergedIntoTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php', 'ManiphestTaskOpenStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskOpenStatusDatasource.php', + 'ManiphestTaskOwnerTransaction' => 'applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php', 'ManiphestTaskPHIDResolver' => 'applications/maniphest/httpparametertype/ManiphestTaskPHIDResolver.php', 'ManiphestTaskPHIDType' => 'applications/maniphest/phid/ManiphestTaskPHIDType.php', + 'ManiphestTaskParentTransaction' => 'applications/maniphest/xaction/ManiphestTaskParentTransaction.php', 'ManiphestTaskPoints' => 'applications/maniphest/constants/ManiphestTaskPoints.php', + 'ManiphestTaskPointsTransaction' => 'applications/maniphest/xaction/ManiphestTaskPointsTransaction.php', 'ManiphestTaskPriority' => 'applications/maniphest/constants/ManiphestTaskPriority.php', 'ManiphestTaskPriorityDatasource' => 'applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php', 'ManiphestTaskPriorityHeraldAction' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php', 'ManiphestTaskPriorityHeraldField' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldField.php', + 'ManiphestTaskPriorityTransaction' => 'applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php', 'ManiphestTaskQuery' => 'applications/maniphest/query/ManiphestTaskQuery.php', 'ManiphestTaskRelationship' => 'applications/maniphest/relationship/ManiphestTaskRelationship.php', 'ManiphestTaskRelationshipSource' => 'applications/search/relationship/ManiphestTaskRelationshipSource.php', @@ -1541,9 +1580,14 @@ phutil_register_library_map(array( 'ManiphestTaskStatusHeraldAction' => 'applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php', 'ManiphestTaskStatusHeraldField' => 'applications/maniphest/herald/ManiphestTaskStatusHeraldField.php', 'ManiphestTaskStatusTestCase' => 'applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php', + 'ManiphestTaskStatusTransaction' => 'applications/maniphest/xaction/ManiphestTaskStatusTransaction.php', + 'ManiphestTaskSubpriorityTransaction' => 'applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php', 'ManiphestTaskSubtypeDatasource' => 'applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php', 'ManiphestTaskTestCase' => 'applications/maniphest/__tests__/ManiphestTaskTestCase.php', 'ManiphestTaskTitleHeraldField' => 'applications/maniphest/herald/ManiphestTaskTitleHeraldField.php', + 'ManiphestTaskTitleTransaction' => 'applications/maniphest/xaction/ManiphestTaskTitleTransaction.php', + 'ManiphestTaskTransactionType' => 'applications/maniphest/xaction/ManiphestTaskTransactionType.php', + 'ManiphestTaskUnblockTransaction' => 'applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php', 'ManiphestTransaction' => 'applications/maniphest/storage/ManiphestTransaction.php', 'ManiphestTransactionComment' => 'applications/maniphest/storage/ManiphestTransactionComment.php', 'ManiphestTransactionEditor' => 'applications/maniphest/editor/ManiphestTransactionEditor.php', @@ -1566,11 +1610,13 @@ phutil_register_library_map(array( 'MultimeterLabel' => 'applications/multimeter/storage/MultimeterLabel.php', 'MultimeterSampleController' => 'applications/multimeter/controller/MultimeterSampleController.php', 'MultimeterViewer' => 'applications/multimeter/storage/MultimeterViewer.php', + 'NuanceCommandImplementation' => 'applications/nuance/command/NuanceCommandImplementation.php', 'NuanceConduitAPIMethod' => 'applications/nuance/conduit/NuanceConduitAPIMethod.php', 'NuanceConsoleController' => 'applications/nuance/controller/NuanceConsoleController.php', 'NuanceContentSource' => 'applications/nuance/contentsource/NuanceContentSource.php', 'NuanceController' => 'applications/nuance/controller/NuanceController.php', 'NuanceDAO' => 'applications/nuance/storage/NuanceDAO.php', + 'NuanceFormItemType' => 'applications/nuance/item/NuanceFormItemType.php', 'NuanceGitHubEventItemType' => 'applications/nuance/item/NuanceGitHubEventItemType.php', 'NuanceGitHubImportCursor' => 'applications/nuance/cursor/NuanceGitHubImportCursor.php', 'NuanceGitHubIssuesImportCursor' => 'applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php', @@ -1586,16 +1632,25 @@ phutil_register_library_map(array( 'NuanceItemActionController' => 'applications/nuance/controller/NuanceItemActionController.php', 'NuanceItemCommand' => 'applications/nuance/storage/NuanceItemCommand.php', 'NuanceItemCommandQuery' => 'applications/nuance/query/NuanceItemCommandQuery.php', + 'NuanceItemCommandSpec' => 'applications/nuance/command/NuanceItemCommandSpec.php', + 'NuanceItemCommandTransaction' => 'applications/nuance/xaction/NuanceItemCommandTransaction.php', 'NuanceItemController' => 'applications/nuance/controller/NuanceItemController.php', 'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php', 'NuanceItemListController' => 'applications/nuance/controller/NuanceItemListController.php', 'NuanceItemManageController' => 'applications/nuance/controller/NuanceItemManageController.php', + 'NuanceItemOwnerTransaction' => 'applications/nuance/xaction/NuanceItemOwnerTransaction.php', 'NuanceItemPHIDType' => 'applications/nuance/phid/NuanceItemPHIDType.php', + 'NuanceItemPropertyTransaction' => 'applications/nuance/xaction/NuanceItemPropertyTransaction.php', 'NuanceItemQuery' => 'applications/nuance/query/NuanceItemQuery.php', + 'NuanceItemQueueTransaction' => 'applications/nuance/xaction/NuanceItemQueueTransaction.php', + 'NuanceItemRequestorTransaction' => 'applications/nuance/xaction/NuanceItemRequestorTransaction.php', 'NuanceItemSearchEngine' => 'applications/nuance/query/NuanceItemSearchEngine.php', + 'NuanceItemSourceTransaction' => 'applications/nuance/xaction/NuanceItemSourceTransaction.php', + 'NuanceItemStatusTransaction' => 'applications/nuance/xaction/NuanceItemStatusTransaction.php', 'NuanceItemTransaction' => 'applications/nuance/storage/NuanceItemTransaction.php', 'NuanceItemTransactionComment' => 'applications/nuance/storage/NuanceItemTransactionComment.php', 'NuanceItemTransactionQuery' => 'applications/nuance/query/NuanceItemTransactionQuery.php', + 'NuanceItemTransactionType' => 'applications/nuance/xaction/NuanceItemTransactionType.php', 'NuanceItemType' => 'applications/nuance/item/NuanceItemType.php', 'NuanceItemUpdateWorker' => 'applications/nuance/worker/NuanceItemUpdateWorker.php', 'NuanceItemViewController' => 'applications/nuance/controller/NuanceItemViewController.php', @@ -1611,18 +1666,22 @@ phutil_register_library_map(array( 'NuanceQueueEditEngine' => 'applications/nuance/editor/NuanceQueueEditEngine.php', 'NuanceQueueEditor' => 'applications/nuance/editor/NuanceQueueEditor.php', 'NuanceQueueListController' => 'applications/nuance/controller/NuanceQueueListController.php', + 'NuanceQueueNameTransaction' => 'applications/nuance/xaction/NuanceQueueNameTransaction.php', 'NuanceQueuePHIDType' => 'applications/nuance/phid/NuanceQueuePHIDType.php', 'NuanceQueueQuery' => 'applications/nuance/query/NuanceQueueQuery.php', 'NuanceQueueSearchEngine' => 'applications/nuance/query/NuanceQueueSearchEngine.php', 'NuanceQueueTransaction' => 'applications/nuance/storage/NuanceQueueTransaction.php', 'NuanceQueueTransactionComment' => 'applications/nuance/storage/NuanceQueueTransactionComment.php', 'NuanceQueueTransactionQuery' => 'applications/nuance/query/NuanceQueueTransactionQuery.php', + 'NuanceQueueTransactionType' => 'applications/nuance/xaction/NuanceQueueTransactionType.php', 'NuanceQueueViewController' => 'applications/nuance/controller/NuanceQueueViewController.php', + 'NuanceQueueWorkController' => 'applications/nuance/controller/NuanceQueueWorkController.php', 'NuanceSchemaSpec' => 'applications/nuance/storage/NuanceSchemaSpec.php', 'NuanceSource' => 'applications/nuance/storage/NuanceSource.php', 'NuanceSourceActionController' => 'applications/nuance/controller/NuanceSourceActionController.php', 'NuanceSourceController' => 'applications/nuance/controller/NuanceSourceController.php', 'NuanceSourceDefaultEditCapability' => 'applications/nuance/capability/NuanceSourceDefaultEditCapability.php', + 'NuanceSourceDefaultQueueTransaction' => 'applications/nuance/xaction/NuanceSourceDefaultQueueTransaction.php', 'NuanceSourceDefaultViewCapability' => 'applications/nuance/capability/NuanceSourceDefaultViewCapability.php', 'NuanceSourceDefinition' => 'applications/nuance/source/NuanceSourceDefinition.php', 'NuanceSourceDefinitionTestCase' => 'applications/nuance/source/__tests__/NuanceSourceDefinitionTestCase.php', @@ -1632,14 +1691,17 @@ phutil_register_library_map(array( 'NuanceSourceListController' => 'applications/nuance/controller/NuanceSourceListController.php', 'NuanceSourceManageCapability' => 'applications/nuance/capability/NuanceSourceManageCapability.php', 'NuanceSourceNameNgrams' => 'applications/nuance/storage/NuanceSourceNameNgrams.php', + 'NuanceSourceNameTransaction' => 'applications/nuance/xaction/NuanceSourceNameTransaction.php', 'NuanceSourcePHIDType' => 'applications/nuance/phid/NuanceSourcePHIDType.php', 'NuanceSourceQuery' => 'applications/nuance/query/NuanceSourceQuery.php', 'NuanceSourceSearchEngine' => 'applications/nuance/query/NuanceSourceSearchEngine.php', 'NuanceSourceTransaction' => 'applications/nuance/storage/NuanceSourceTransaction.php', 'NuanceSourceTransactionComment' => 'applications/nuance/storage/NuanceSourceTransactionComment.php', 'NuanceSourceTransactionQuery' => 'applications/nuance/query/NuanceSourceTransactionQuery.php', + 'NuanceSourceTransactionType' => 'applications/nuance/xaction/NuanceSourceTransactionType.php', 'NuanceSourceViewController' => 'applications/nuance/controller/NuanceSourceViewController.php', 'NuanceTransaction' => 'applications/nuance/storage/NuanceTransaction.php', + 'NuanceTrashCommand' => 'applications/nuance/command/NuanceTrashCommand.php', 'NuanceWorker' => 'applications/nuance/worker/NuanceWorker.php', 'OwnersConduitAPIMethod' => 'applications/owners/conduit/OwnersConduitAPIMethod.php', 'OwnersEditConduitAPIMethod' => 'applications/owners/conduit/OwnersEditConduitAPIMethod.php', @@ -1658,6 +1720,7 @@ phutil_register_library_map(array( 'PHUIBadgeExample' => 'applications/uiexample/examples/PHUIBadgeExample.php', 'PHUIBadgeMiniView' => 'view/phui/PHUIBadgeMiniView.php', 'PHUIBadgeView' => 'view/phui/PHUIBadgeView.php', + 'PHUIBigInfoExample' => 'applications/uiexample/examples/PHUIBigInfoExample.php', 'PHUIBigInfoView' => 'view/phui/PHUIBigInfoView.php', 'PHUIBoxExample' => 'applications/uiexample/examples/PHUIBoxExample.php', 'PHUIBoxView' => 'view/phui/PHUIBoxView.php', @@ -1686,6 +1749,7 @@ phutil_register_library_map(array( 'PHUIDiffInlineCommentTableScaffold' => 'infrastructure/diff/view/PHUIDiffInlineCommentTableScaffold.php', 'PHUIDiffInlineCommentUndoView' => 'infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php', 'PHUIDiffInlineCommentView' => 'infrastructure/diff/view/PHUIDiffInlineCommentView.php', + 'PHUIDiffInlineThreader' => 'infrastructure/diff/view/PHUIDiffInlineThreader.php', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php', 'PHUIDiffRevealIconView' => 'infrastructure/diff/view/PHUIDiffRevealIconView.php', 'PHUIDiffTableOfContentsItemView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php', @@ -1716,11 +1780,11 @@ phutil_register_library_map(array( 'PHUIImageMaskExample' => 'applications/uiexample/examples/PHUIImageMaskExample.php', 'PHUIImageMaskView' => 'view/phui/PHUIImageMaskView.php', 'PHUIInfoExample' => 'applications/uiexample/examples/PHUIInfoExample.php', - 'PHUIInfoPanelExample' => 'applications/uiexample/examples/PHUIInfoPanelExample.php', - 'PHUIInfoPanelView' => 'view/phui/PHUIInfoPanelView.php', - 'PHUIInfoView' => 'view/form/PHUIInfoView.php', + 'PHUIInfoView' => 'view/phui/PHUIInfoView.php', 'PHUIInvisibleCharacterTestCase' => 'view/phui/__tests__/PHUIInvisibleCharacterTestCase.php', 'PHUIInvisibleCharacterView' => 'view/phui/PHUIInvisibleCharacterView.php', + 'PHUILeftRightExample' => 'applications/uiexample/examples/PHUILeftRightExample.php', + 'PHUILeftRightView' => 'view/phui/PHUILeftRightView.php', 'PHUIListExample' => 'applications/uiexample/examples/PHUIListExample.php', 'PHUIListItemView' => 'view/phui/PHUIListItemView.php', 'PHUIListView' => 'view/phui/PHUIListView.php', @@ -1755,29 +1819,40 @@ phutil_register_library_map(array( 'PHUIUserAvailabilityView' => 'applications/calendar/view/PHUIUserAvailabilityView.php', 'PHUIWorkboardView' => 'view/phui/PHUIWorkboardView.php', 'PHUIWorkpanelView' => 'view/phui/PHUIWorkpanelView.php', + 'PHUIXComponentsExample' => 'applications/uiexample/examples/PHUIXComponentsExample.php', 'PassphraseAbstractKey' => 'applications/passphrase/keys/PassphraseAbstractKey.php', 'PassphraseConduitAPIMethod' => 'applications/passphrase/conduit/PassphraseConduitAPIMethod.php', 'PassphraseController' => 'applications/passphrase/controller/PassphraseController.php', 'PassphraseCredential' => 'applications/passphrase/storage/PassphraseCredential.php', 'PassphraseCredentialAuthorPolicyRule' => 'applications/passphrase/policyrule/PassphraseCredentialAuthorPolicyRule.php', 'PassphraseCredentialConduitController' => 'applications/passphrase/controller/PassphraseCredentialConduitController.php', + 'PassphraseCredentialConduitTransaction' => 'applications/passphrase/xaction/PassphraseCredentialConduitTransaction.php', 'PassphraseCredentialControl' => 'applications/passphrase/view/PassphraseCredentialControl.php', 'PassphraseCredentialCreateController' => 'applications/passphrase/controller/PassphraseCredentialCreateController.php', + 'PassphraseCredentialDescriptionTransaction' => 'applications/passphrase/xaction/PassphraseCredentialDescriptionTransaction.php', 'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php', + 'PassphraseCredentialDestroyTransaction' => 'applications/passphrase/xaction/PassphraseCredentialDestroyTransaction.php', 'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php', + 'PassphraseCredentialFerretEngine' => 'applications/passphrase/search/PassphraseCredentialFerretEngine.php', 'PassphraseCredentialFulltextEngine' => 'applications/passphrase/search/PassphraseCredentialFulltextEngine.php', 'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php', 'PassphraseCredentialLockController' => 'applications/passphrase/controller/PassphraseCredentialLockController.php', + 'PassphraseCredentialLockTransaction' => 'applications/passphrase/xaction/PassphraseCredentialLockTransaction.php', + 'PassphraseCredentialLookedAtTransaction' => 'applications/passphrase/xaction/PassphraseCredentialLookedAtTransaction.php', + 'PassphraseCredentialNameTransaction' => 'applications/passphrase/xaction/PassphraseCredentialNameTransaction.php', 'PassphraseCredentialPHIDType' => 'applications/passphrase/phid/PassphraseCredentialPHIDType.php', 'PassphraseCredentialPublicController' => 'applications/passphrase/controller/PassphraseCredentialPublicController.php', 'PassphraseCredentialQuery' => 'applications/passphrase/query/PassphraseCredentialQuery.php', 'PassphraseCredentialRevealController' => 'applications/passphrase/controller/PassphraseCredentialRevealController.php', 'PassphraseCredentialSearchEngine' => 'applications/passphrase/query/PassphraseCredentialSearchEngine.php', + 'PassphraseCredentialSecretIDTransaction' => 'applications/passphrase/xaction/PassphraseCredentialSecretIDTransaction.php', 'PassphraseCredentialTransaction' => 'applications/passphrase/storage/PassphraseCredentialTransaction.php', 'PassphraseCredentialTransactionEditor' => 'applications/passphrase/editor/PassphraseCredentialTransactionEditor.php', 'PassphraseCredentialTransactionQuery' => 'applications/passphrase/query/PassphraseCredentialTransactionQuery.php', + 'PassphraseCredentialTransactionType' => 'applications/passphrase/xaction/PassphraseCredentialTransactionType.php', 'PassphraseCredentialType' => 'applications/passphrase/credentialtype/PassphraseCredentialType.php', 'PassphraseCredentialTypeTestCase' => 'applications/passphrase/credentialtype/__tests__/PassphraseCredentialTypeTestCase.php', + 'PassphraseCredentialUsernameTransaction' => 'applications/passphrase/xaction/PassphraseCredentialUsernameTransaction.php', 'PassphraseCredentialViewController' => 'applications/passphrase/controller/PassphraseCredentialViewController.php', 'PassphraseDAO' => 'applications/passphrase/storage/PassphraseDAO.php', 'PassphraseDefaultEditCapability' => 'applications/passphrase/capability/PassphraseDefaultEditCapability.php', @@ -1850,9 +1925,12 @@ phutil_register_library_map(array( 'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php', 'PhabricatorApplicationDocumentation' => 'extensions/documentation/application/PhabricatorApplicationDocumentation.php', 'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php', + 'PhabricatorApplicationEditEngine' => 'applications/meta/editor/PhabricatorApplicationEditEngine.php', 'PhabricatorApplicationEditHTTPParameterHelpView' => 'applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php', + 'PhabricatorApplicationEditor' => 'applications/meta/editor/PhabricatorApplicationEditor.php', 'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php', 'PhabricatorApplicationPanelController' => 'applications/meta/controller/PhabricatorApplicationPanelController.php', + 'PhabricatorApplicationPolicyChangeTransaction' => 'applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php', 'PhabricatorApplicationProfileMenuItem' => 'applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php', 'PhabricatorApplicationQuery' => 'applications/meta/query/PhabricatorApplicationQuery.php', 'PhabricatorApplicationSchemaSpec' => 'applications/meta/storage/PhabricatorApplicationSchemaSpec.php', @@ -2081,7 +2159,6 @@ phutil_register_library_map(array( 'PhabricatorBadgesTransactionComment' => 'applications/badges/storage/PhabricatorBadgesTransactionComment.php', 'PhabricatorBadgesTransactionQuery' => 'applications/badges/query/PhabricatorBadgesTransactionQuery.php', 'PhabricatorBadgesViewController' => 'applications/badges/controller/PhabricatorBadgesViewController.php', - 'PhabricatorBarePageUIExample' => 'applications/uiexample/examples/PhabricatorBarePageUIExample.php', 'PhabricatorBarePageView' => 'view/page/PhabricatorBarePageView.php', 'PhabricatorBaseURISetupCheck' => 'applications/config/check/PhabricatorBaseURISetupCheck.php', 'PhabricatorBcryptPasswordHasher' => 'infrastructure/util/password/PhabricatorBcryptPasswordHasher.php', @@ -2091,13 +2168,14 @@ phutil_register_library_map(array( 'PhabricatorBoardLayoutEngine' => 'applications/project/engine/PhabricatorBoardLayoutEngine.php', 'PhabricatorBoardRenderingEngine' => 'applications/project/engine/PhabricatorBoardRenderingEngine.php', 'PhabricatorBoardResponseEngine' => 'applications/project/engine/PhabricatorBoardResponseEngine.php', + 'PhabricatorBoolConfigType' => 'applications/config/type/PhabricatorBoolConfigType.php', 'PhabricatorBoolEditField' => 'applications/transactions/editfield/PhabricatorBoolEditField.php', 'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php', 'PhabricatorBuildbotController' => 'extensions/buildbot/controller/PhabricatorBuildbotController.php', 'PhabricatorBuiltinDraftEngine' => 'applications/transactions/draft/PhabricatorBuiltinDraftEngine.php', + 'PhabricatorBuiltinFileCachePurger' => 'applications/cache/purger/PhabricatorBuiltinFileCachePurger.php', 'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php', 'PhabricatorBulkContentSource' => 'infrastructure/daemon/contentsource/PhabricatorBulkContentSource.php', - 'PhabricatorBusyUIExample' => 'applications/uiexample/examples/PhabricatorBusyUIExample.php', 'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php', 'PhabricatorCacheEngine' => 'applications/system/engine/PhabricatorCacheEngine.php', 'PhabricatorCacheEngineExtension' => 'applications/system/engine/PhabricatorCacheEngineExtension.php', @@ -2105,6 +2183,7 @@ phutil_register_library_map(array( 'PhabricatorCacheManagementPurgeWorkflow' => 'applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php', 'PhabricatorCacheManagementWorkflow' => 'applications/cache/management/PhabricatorCacheManagementWorkflow.php', 'PhabricatorCacheMarkupGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheMarkupGarbageCollector.php', + 'PhabricatorCachePurger' => 'applications/cache/purger/PhabricatorCachePurger.php', 'PhabricatorCacheSchemaSpec' => 'applications/cache/storage/PhabricatorCacheSchemaSpec.php', 'PhabricatorCacheSetupCheck' => 'applications/config/check/PhabricatorCacheSetupCheck.php', 'PhabricatorCacheSpec' => 'applications/cache/spec/PhabricatorCacheSpec.php', @@ -2134,6 +2213,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventEmailCommand.php', 'PhabricatorCalendarEventEndDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php', 'PhabricatorCalendarEventExportController' => 'applications/calendar/controller/PhabricatorCalendarEventExportController.php', + 'PhabricatorCalendarEventFerretEngine' => 'applications/calendar/search/PhabricatorCalendarEventFerretEngine.php', 'PhabricatorCalendarEventForkTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventForkTransaction.php', 'PhabricatorCalendarEventFrequencyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php', 'PhabricatorCalendarEventFulltextEngine' => 'applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php', @@ -2254,6 +2334,7 @@ phutil_register_library_map(array( 'PhabricatorCelerityApplication' => 'applications/celerity/application/PhabricatorCelerityApplication.php', 'PhabricatorCelerityTestCase' => '__tests__/PhabricatorCelerityTestCase.php', 'PhabricatorChangeParserTestCase' => 'applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php', + 'PhabricatorChangesetCachePurger' => 'applications/cache/purger/PhabricatorChangesetCachePurger.php', 'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php', 'PhabricatorChatLogApplication' => 'applications/chatlog/application/PhabricatorChatLogApplication.php', 'PhabricatorChatLogChannel' => 'applications/chatlog/storage/PhabricatorChatLogChannel.php', @@ -2265,14 +2346,15 @@ phutil_register_library_map(array( 'PhabricatorChatLogEvent' => 'applications/chatlog/storage/PhabricatorChatLogEvent.php', 'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php', 'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php', + 'PhabricatorClassConfigType' => 'applications/config/type/PhabricatorClassConfigType.php', 'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php', - 'PhabricatorClusterDatabasesConfigOptionType' => 'infrastructure/cluster/config/PhabricatorClusterDatabasesConfigOptionType.php', + 'PhabricatorClusterDatabasesConfigType' => 'infrastructure/cluster/config/PhabricatorClusterDatabasesConfigType.php', 'PhabricatorClusterException' => 'infrastructure/cluster/exception/PhabricatorClusterException.php', 'PhabricatorClusterExceptionHandler' => 'infrastructure/cluster/exception/PhabricatorClusterExceptionHandler.php', 'PhabricatorClusterImpossibleWriteException' => 'infrastructure/cluster/exception/PhabricatorClusterImpossibleWriteException.php', 'PhabricatorClusterImproperWriteException' => 'infrastructure/cluster/exception/PhabricatorClusterImproperWriteException.php', 'PhabricatorClusterNoHostForRoleException' => 'infrastructure/cluster/exception/PhabricatorClusterNoHostForRoleException.php', - 'PhabricatorClusterSearchConfigOptionType' => 'infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php', + 'PhabricatorClusterSearchConfigType' => 'infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php', 'PhabricatorClusterServiceHealthRecord' => 'infrastructure/cluster/PhabricatorClusterServiceHealthRecord.php', 'PhabricatorClusterStrandedException' => 'infrastructure/cluster/exception/PhabricatorClusterStrandedException.php', 'PhabricatorColumnProxyInterface' => 'applications/project/interface/PhabricatorColumnProxyInterface.php', @@ -2367,7 +2449,6 @@ phutil_register_library_map(array( 'PhabricatorConfigOption' => 'applications/config/option/PhabricatorConfigOption.php', 'PhabricatorConfigOptionType' => 'applications/config/custom/PhabricatorConfigOptionType.php', 'PhabricatorConfigPHIDModule' => 'applications/config/module/PhabricatorConfigPHIDModule.php', - 'PhabricatorConfigPageView' => 'applications/config/view/PhabricatorConfigPageView.php', 'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php', 'PhabricatorConfigPurgeCacheController' => 'applications/config/controller/PhabricatorConfigPurgeCacheController.php', 'PhabricatorConfigRegexOptionType' => 'applications/config/custom/PhabricatorConfigRegexOptionType.php', @@ -2385,6 +2466,7 @@ phutil_register_library_map(array( 'PhabricatorConfigTableSchema' => 'applications/config/schema/PhabricatorConfigTableSchema.php', 'PhabricatorConfigTransaction' => 'applications/config/storage/PhabricatorConfigTransaction.php', 'PhabricatorConfigTransactionQuery' => 'applications/config/query/PhabricatorConfigTransactionQuery.php', + 'PhabricatorConfigType' => 'applications/config/type/PhabricatorConfigType.php', 'PhabricatorConfigValidationException' => 'applications/config/exception/PhabricatorConfigValidationException.php', 'PhabricatorConfigVersionController' => 'applications/config/controller/PhabricatorConfigVersionController.php', 'PhabricatorConpherenceApplication' => 'applications/conpherence/application/PhabricatorConpherenceApplication.php', @@ -2393,6 +2475,8 @@ phutil_register_library_map(array( 'PhabricatorConpherenceNotificationsSetting' => 'applications/settings/setting/PhabricatorConpherenceNotificationsSetting.php', 'PhabricatorConpherencePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php', 'PhabricatorConpherenceProfileMenuItem' => 'applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php', + 'PhabricatorConpherenceRoomContextFreeGrammar' => 'applications/conpherence/lipsum/PhabricatorConpherenceRoomContextFreeGrammar.php', + 'PhabricatorConpherenceRoomTestDataGenerator' => 'applications/conpherence/lipsum/PhabricatorConpherenceRoomTestDataGenerator.php', 'PhabricatorConpherenceSoundSetting' => 'applications/settings/setting/PhabricatorConpherenceSoundSetting.php', 'PhabricatorConpherenceThreadPHIDType' => 'applications/conpherence/phid/PhabricatorConpherenceThreadPHIDType.php', 'PhabricatorConpherenceWidgetVisibleSetting' => 'applications/settings/setting/PhabricatorConpherenceWidgetVisibleSetting.php', @@ -2458,6 +2542,7 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldStorageQuery' => 'infrastructure/customfield/query/PhabricatorCustomFieldStorageQuery.php', 'PhabricatorCustomFieldStringIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldStringIndexStorage.php', 'PhabricatorCustomLogoConfigType' => 'applications/config/custom/PhabricatorCustomLogoConfigType.php', + 'PhabricatorCustomUIFooterConfigType' => 'applications/config/custom/PhabricatorCustomUIFooterConfigType.php', 'PhabricatorDaemon' => 'infrastructure/daemon/PhabricatorDaemon.php', 'PhabricatorDaemonBulkJobController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobController.php', 'PhabricatorDaemonBulkJobListController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobListController.php', @@ -2570,8 +2655,8 @@ phutil_register_library_map(array( 'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php', 'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php', 'PhabricatorDefaultSyntaxStyle' => 'infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php', - 'PhabricatorDesktopNotificationsSetting' => 'applications/settings/setting/PhabricatorDesktopNotificationsSetting.php', - 'PhabricatorDesktopNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php', + 'PhabricatorDestructibleCodex' => 'applications/system/codex/PhabricatorDestructibleCodex.php', + 'PhabricatorDestructibleCodexInterface' => 'applications/system/interface/PhabricatorDestructibleCodexInterface.php', 'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php', 'PhabricatorDestructionEngine' => 'applications/system/engine/PhabricatorDestructionEngine.php', 'PhabricatorDestructionEngineExtension' => 'applications/system/engine/PhabricatorDestructionEngineExtension.php', @@ -2688,6 +2773,7 @@ phutil_register_library_map(array( 'PhabricatorEmojiRemarkupRule' => 'applications/macro/markup/PhabricatorEmojiRemarkupRule.php', 'PhabricatorEmojiTranslation' => 'infrastructure/internationalization/translation/PhabricatorEmojiTranslation.php', 'PhabricatorEmptyQueryException' => 'infrastructure/query/PhabricatorEmptyQueryException.php', + 'PhabricatorEnumConfigType' => 'applications/config/type/PhabricatorEnumConfigType.php', 'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php', 'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php', 'PhabricatorEpochEditField' => 'applications/transactions/editfield/PhabricatorEpochEditField.php', @@ -2749,6 +2835,13 @@ phutil_register_library_map(array( 'PhabricatorFeedStoryNotification' => 'applications/notification/storage/PhabricatorFeedStoryNotification.php', 'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php', 'PhabricatorFeedStoryReference' => 'applications/feed/storage/PhabricatorFeedStoryReference.php', + 'PhabricatorFerretEngine' => 'applications/search/ferret/PhabricatorFerretEngine.php', + 'PhabricatorFerretEngineTestCase' => 'applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php', + 'PhabricatorFerretFulltextEngineExtension' => 'applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php', + 'PhabricatorFerretFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php', + 'PhabricatorFerretInterface' => 'applications/search/ferret/PhabricatorFerretInterface.php', + 'PhabricatorFerretMetadata' => 'applications/search/ferret/PhabricatorFerretMetadata.php', + 'PhabricatorFerretSearchEngineExtension' => 'applications/search/engineextension/PhabricatorFerretSearchEngineExtension.php', 'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php', 'PhabricatorFileAES256StorageFormat' => 'applications/files/format/PhabricatorFileAES256StorageFormat.php', 'PhabricatorFileBundleLoader' => 'applications/files/query/PhabricatorFileBundleLoader.php', @@ -2825,7 +2918,6 @@ phutil_register_library_map(array( 'PhabricatorFilesManagementGenerateKeyWorkflow' => 'applications/files/management/PhabricatorFilesManagementGenerateKeyWorkflow.php', 'PhabricatorFilesManagementIntegrityWorkflow' => 'applications/files/management/PhabricatorFilesManagementIntegrityWorkflow.php', 'PhabricatorFilesManagementMigrateWorkflow' => 'applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php', - 'PhabricatorFilesManagementPurgeWorkflow' => 'applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php', 'PhabricatorFilesManagementRebuildWorkflow' => 'applications/files/management/PhabricatorFilesManagementRebuildWorkflow.php', 'PhabricatorFilesManagementWorkflow' => 'applications/files/management/PhabricatorFilesManagementWorkflow.php', 'PhabricatorFilesOnDiskBuiltinFile' => 'applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php', @@ -2861,6 +2953,7 @@ phutil_register_library_map(array( 'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementCollectWorkflow.php', 'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementSetPolicyWorkflow.php', 'PhabricatorGarbageCollectorManagementWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementWorkflow.php', + 'PhabricatorGeneralCachePurger' => 'applications/cache/purger/PhabricatorGeneralCachePurger.php', 'PhabricatorGestureUIExample' => 'applications/uiexample/examples/PhabricatorGestureUIExample.php', 'PhabricatorGitGraphStream' => 'applications/repository/daemon/PhabricatorGitGraphStream.php', 'PhabricatorGitHubAuthProvider' => 'applications/auth/provider/PhabricatorGitHubAuthProvider.php', @@ -2930,6 +3023,7 @@ phutil_register_library_map(array( 'PhabricatorInlineCommentPreviewController' => 'infrastructure/diff/PhabricatorInlineCommentPreviewController.php', 'PhabricatorInlineSummaryView' => 'infrastructure/diff/view/PhabricatorInlineSummaryView.php', 'PhabricatorInstructionsEditField' => 'applications/transactions/editfield/PhabricatorInstructionsEditField.php', + 'PhabricatorIntConfigType' => 'applications/config/type/PhabricatorIntConfigType.php', 'PhabricatorInternalSetting' => 'applications/settings/setting/PhabricatorInternalSetting.php', 'PhabricatorInternationalizationManagementExtractWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php', 'PhabricatorInternationalizationManagementWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementWorkflow.php', @@ -2938,6 +3032,7 @@ phutil_register_library_map(array( 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorIteratedMD5PasswordHasherTestCase.php', 'PhabricatorIteratorFileUploadSource' => 'applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php', 'PhabricatorJIRAAuthProvider' => 'applications/auth/provider/PhabricatorJIRAAuthProvider.php', + 'PhabricatorJSONConfigType' => 'applications/config/type/PhabricatorJSONConfigType.php', 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php', 'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php', 'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php', @@ -2963,7 +3058,6 @@ phutil_register_library_map(array( 'PhabricatorLiskFulltextEngineExtension' => 'applications/search/engineextension/PhabricatorLiskFulltextEngineExtension.php', 'PhabricatorLiskSearchEngineExtension' => 'applications/search/engineextension/PhabricatorLiskSearchEngineExtension.php', 'PhabricatorLiskSerializer' => 'infrastructure/storage/lisk/PhabricatorLiskSerializer.php', - 'PhabricatorListFilterUIExample' => 'applications/uiexample/examples/PhabricatorListFilterUIExample.php', 'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php', 'PhabricatorLocalTimeTestCase' => 'view/__tests__/PhabricatorLocalTimeTestCase.php', 'PhabricatorLocaleScopeGuard' => 'infrastructure/internationalization/scope/PhabricatorLocaleScopeGuard.php', @@ -2972,26 +3066,32 @@ phutil_register_library_map(array( 'PhabricatorLogoutController' => 'applications/auth/controller/PhabricatorLogoutController.php', 'PhabricatorLunarPhasePolicyRule' => 'applications/policy/rule/PhabricatorLunarPhasePolicyRule.php', 'PhabricatorMacroApplication' => 'applications/macro/application/PhabricatorMacroApplication.php', + 'PhabricatorMacroAudioBehaviorTransaction' => 'applications/macro/xaction/PhabricatorMacroAudioBehaviorTransaction.php', 'PhabricatorMacroAudioController' => 'applications/macro/controller/PhabricatorMacroAudioController.php', - 'PhabricatorMacroCommentController' => 'applications/macro/controller/PhabricatorMacroCommentController.php', + 'PhabricatorMacroAudioTransaction' => 'applications/macro/xaction/PhabricatorMacroAudioTransaction.php', 'PhabricatorMacroConfigOptions' => 'applications/macro/config/PhabricatorMacroConfigOptions.php', 'PhabricatorMacroController' => 'applications/macro/controller/PhabricatorMacroController.php', 'PhabricatorMacroDatasource' => 'applications/macro/typeahead/PhabricatorMacroDatasource.php', 'PhabricatorMacroDisableController' => 'applications/macro/controller/PhabricatorMacroDisableController.php', + 'PhabricatorMacroDisabledTransaction' => 'applications/macro/xaction/PhabricatorMacroDisabledTransaction.php', 'PhabricatorMacroEditController' => 'applications/macro/controller/PhabricatorMacroEditController.php', + 'PhabricatorMacroEditEngine' => 'applications/macro/editor/PhabricatorMacroEditEngine.php', 'PhabricatorMacroEditor' => 'applications/macro/editor/PhabricatorMacroEditor.php', + 'PhabricatorMacroFileTransaction' => 'applications/macro/xaction/PhabricatorMacroFileTransaction.php', 'PhabricatorMacroListController' => 'applications/macro/controller/PhabricatorMacroListController.php', 'PhabricatorMacroMacroPHIDType' => 'applications/macro/phid/PhabricatorMacroMacroPHIDType.php', 'PhabricatorMacroMailReceiver' => 'applications/macro/mail/PhabricatorMacroMailReceiver.php', 'PhabricatorMacroManageCapability' => 'applications/macro/capability/PhabricatorMacroManageCapability.php', 'PhabricatorMacroMemeController' => 'applications/macro/controller/PhabricatorMacroMemeController.php', 'PhabricatorMacroMemeDialogController' => 'applications/macro/controller/PhabricatorMacroMemeDialogController.php', + 'PhabricatorMacroNameTransaction' => 'applications/macro/xaction/PhabricatorMacroNameTransaction.php', 'PhabricatorMacroQuery' => 'applications/macro/query/PhabricatorMacroQuery.php', 'PhabricatorMacroReplyHandler' => 'applications/macro/mail/PhabricatorMacroReplyHandler.php', 'PhabricatorMacroSearchEngine' => 'applications/macro/query/PhabricatorMacroSearchEngine.php', 'PhabricatorMacroTransaction' => 'applications/macro/storage/PhabricatorMacroTransaction.php', 'PhabricatorMacroTransactionComment' => 'applications/macro/storage/PhabricatorMacroTransactionComment.php', 'PhabricatorMacroTransactionQuery' => 'applications/macro/query/PhabricatorMacroTransactionQuery.php', + 'PhabricatorMacroTransactionType' => 'applications/macro/xaction/PhabricatorMacroTransactionType.php', 'PhabricatorMacroViewController' => 'applications/macro/controller/PhabricatorMacroViewController.php', 'PhabricatorMailEmailHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailHeraldField.php', 'PhabricatorMailEmailHeraldFieldGroup' => 'applications/metamta/herald/PhabricatorMailEmailHeraldFieldGroup.php', @@ -3104,6 +3204,8 @@ phutil_register_library_map(array( 'PhabricatorMySQLSearchHost' => 'infrastructure/cluster/search/PhabricatorMySQLSearchHost.php', 'PhabricatorMySQLSetupCheck' => 'applications/config/check/PhabricatorMySQLSetupCheck.php', 'PhabricatorNamedQuery' => 'applications/search/storage/PhabricatorNamedQuery.php', + 'PhabricatorNamedQueryConfig' => 'applications/search/storage/PhabricatorNamedQueryConfig.php', + 'PhabricatorNamedQueryConfigQuery' => 'applications/search/query/PhabricatorNamedQueryConfigQuery.php', 'PhabricatorNamedQueryQuery' => 'applications/search/query/PhabricatorNamedQueryQuery.php', 'PhabricatorNavigationRemarkupRule' => 'infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php', 'PhabricatorNeverTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorNeverTriggerClock.php', @@ -3121,12 +3223,14 @@ phutil_register_library_map(array( 'PhabricatorNotificationQuery' => 'applications/notification/query/PhabricatorNotificationQuery.php', 'PhabricatorNotificationSearchEngine' => 'applications/notification/query/PhabricatorNotificationSearchEngine.php', 'PhabricatorNotificationServerRef' => 'applications/notification/client/PhabricatorNotificationServerRef.php', - 'PhabricatorNotificationServersConfigOptionType' => 'applications/notification/config/PhabricatorNotificationServersConfigOptionType.php', + 'PhabricatorNotificationServersConfigType' => 'applications/notification/config/PhabricatorNotificationServersConfigType.php', 'PhabricatorNotificationStatusView' => 'applications/notification/view/PhabricatorNotificationStatusView.php', 'PhabricatorNotificationTestController' => 'applications/notification/controller/PhabricatorNotificationTestController.php', 'PhabricatorNotificationTestFeedStory' => 'applications/notification/feed/PhabricatorNotificationTestFeedStory.php', 'PhabricatorNotificationUIExample' => 'applications/uiexample/examples/PhabricatorNotificationUIExample.php', 'PhabricatorNotificationsApplication' => 'applications/notification/application/PhabricatorNotificationsApplication.php', + 'PhabricatorNotificationsSetting' => 'applications/settings/setting/PhabricatorNotificationsSetting.php', + 'PhabricatorNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorNotificationsSettingsPanel.php', 'PhabricatorNuanceApplication' => 'applications/nuance/application/PhabricatorNuanceApplication.php', 'PhabricatorOAuth1AuthProvider' => 'applications/auth/provider/PhabricatorOAuth1AuthProvider.php', 'PhabricatorOAuth1SecretTemporaryTokenType' => 'applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php', @@ -3219,7 +3323,8 @@ phutil_register_library_map(array( 'PhabricatorOwnersPackageDescriptionTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageDescriptionTransaction.php', 'PhabricatorOwnersPackageDominionTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageDominionTransaction.php', 'PhabricatorOwnersPackageEditEngine' => 'applications/owners/editor/PhabricatorOwnersPackageEditEngine.php', - 'PhabricatorOwnersPackageFulltextEngine' => 'applications/owners/query/PhabricatorOwnersPackageFulltextEngine.php', + 'PhabricatorOwnersPackageFerretEngine' => 'applications/owners/search/PhabricatorOwnersPackageFerretEngine.php', + 'PhabricatorOwnersPackageFulltextEngine' => 'applications/owners/search/PhabricatorOwnersPackageFulltextEngine.php', 'PhabricatorOwnersPackageFunctionDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageFunctionDatasource.php', 'PhabricatorOwnersPackageNameNgrams' => 'applications/owners/storage/PhabricatorOwnersPackageNameNgrams.php', 'PhabricatorOwnersPackageNameTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageNameTransaction.php', @@ -3406,10 +3511,12 @@ phutil_register_library_map(array( 'PhabricatorPeopleProfileManageController' => 'applications/people/controller/PhabricatorPeopleProfileManageController.php', 'PhabricatorPeopleProfileMenuEngine' => 'applications/people/engine/PhabricatorPeopleProfileMenuEngine.php', 'PhabricatorPeopleProfilePictureController' => 'applications/people/controller/PhabricatorPeopleProfilePictureController.php', + 'PhabricatorPeopleProfileRevisionsController' => 'applications/people/controller/PhabricatorPeopleProfileRevisionsController.php', 'PhabricatorPeopleProfileTasksController' => 'applications/people/controller/PhabricatorPeopleProfileTasksController.php', 'PhabricatorPeopleProfileViewController' => 'applications/people/controller/PhabricatorPeopleProfileViewController.php', 'PhabricatorPeopleQuery' => 'applications/people/query/PhabricatorPeopleQuery.php', 'PhabricatorPeopleRenameController' => 'applications/people/controller/PhabricatorPeopleRenameController.php', + 'PhabricatorPeopleRevisionsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php', 'PhabricatorPeopleSearchEngine' => 'applications/people/query/PhabricatorPeopleSearchEngine.php', 'PhabricatorPeopleTasksProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleTasksProfileMenuItem.php', 'PhabricatorPeopleTestDataGenerator' => 'applications/people/lipsum/PhabricatorPeopleTestDataGenerator.php', @@ -3532,8 +3639,10 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardManageController' => 'applications/project/controller/PhabricatorProjectBoardManageController.php', 'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php', 'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php', + 'PhabricatorProjectBuiltinsExample' => 'applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php', 'PhabricatorProjectCardView' => 'applications/project/view/PhabricatorProjectCardView.php', - 'PhabricatorProjectColorsConfigOptionType' => 'applications/project/config/PhabricatorProjectColorsConfigOptionType.php', + 'PhabricatorProjectColorTransaction' => 'applications/project/xaction/PhabricatorProjectColorTransaction.php', + 'PhabricatorProjectColorsConfigType' => 'applications/project/config/PhabricatorProjectColorsConfigType.php', 'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php', 'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php', 'PhabricatorProjectColumnEditController' => 'applications/project/controller/PhabricatorProjectColumnEditController.php', @@ -3563,19 +3672,25 @@ phutil_register_library_map(array( 'PhabricatorProjectEditController' => 'applications/project/controller/PhabricatorProjectEditController.php', 'PhabricatorProjectEditEngine' => 'applications/project/engine/PhabricatorProjectEditEngine.php', 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', + 'PhabricatorProjectFerretEngine' => 'applications/project/search/PhabricatorProjectFerretEngine.php', + 'PhabricatorProjectFilterTransaction' => 'applications/project/xaction/PhabricatorProjectFilterTransaction.php', 'PhabricatorProjectFulltextEngine' => 'applications/project/search/PhabricatorProjectFulltextEngine.php', 'PhabricatorProjectHeraldAction' => 'applications/project/herald/PhabricatorProjectHeraldAction.php', 'PhabricatorProjectHeraldAdapter' => 'applications/project/herald/PhabricatorProjectHeraldAdapter.php', 'PhabricatorProjectHeraldFieldGroup' => 'applications/project/herald/PhabricatorProjectHeraldFieldGroup.php', 'PhabricatorProjectHovercardEngineExtension' => 'applications/project/engineextension/PhabricatorProjectHovercardEngineExtension.php', 'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php', - 'PhabricatorProjectIconsConfigOptionType' => 'applications/project/config/PhabricatorProjectIconsConfigOptionType.php', + 'PhabricatorProjectIconTransaction' => 'applications/project/xaction/PhabricatorProjectIconTransaction.php', + 'PhabricatorProjectIconsConfigType' => 'applications/project/config/PhabricatorProjectIconsConfigType.php', + 'PhabricatorProjectImageTransaction' => 'applications/project/xaction/PhabricatorProjectImageTransaction.php', 'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php', 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', 'PhabricatorProjectListView' => 'applications/project/view/PhabricatorProjectListView.php', 'PhabricatorProjectLockController' => 'applications/project/controller/PhabricatorProjectLockController.php', + 'PhabricatorProjectLockTransaction' => 'applications/project/xaction/PhabricatorProjectLockTransaction.php', 'PhabricatorProjectLogicalAncestorDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalAncestorDatasource.php', 'PhabricatorProjectLogicalDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalDatasource.php', + 'PhabricatorProjectLogicalOnlyDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalOnlyDatasource.php', 'PhabricatorProjectLogicalOrNotDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php', 'PhabricatorProjectLogicalUserDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalUserDatasource.php', 'PhabricatorProjectLogicalViewerDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php', @@ -3591,13 +3706,17 @@ phutil_register_library_map(array( 'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php', 'PhabricatorProjectMembersViewController' => 'applications/project/controller/PhabricatorProjectMembersViewController.php', 'PhabricatorProjectMenuItemController' => 'applications/project/controller/PhabricatorProjectMenuItemController.php', + 'PhabricatorProjectMilestoneTransaction' => 'applications/project/xaction/PhabricatorProjectMilestoneTransaction.php', 'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php', 'PhabricatorProjectNameContextFreeGrammar' => 'applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php', + 'PhabricatorProjectNameTransaction' => 'applications/project/xaction/PhabricatorProjectNameTransaction.php', 'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php', 'PhabricatorProjectObjectHasProjectEdgeType' => 'applications/project/edge/PhabricatorProjectObjectHasProjectEdgeType.php', 'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php', 'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php', 'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php', + 'PhabricatorProjectParentTransaction' => 'applications/project/xaction/PhabricatorProjectParentTransaction.php', + 'PhabricatorProjectPictureProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php', 'PhabricatorProjectPointsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php', 'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php', 'PhabricatorProjectProfileMenuEngine' => 'applications/project/engine/PhabricatorProjectProfileMenuEngine.php', @@ -3613,8 +3732,11 @@ phutil_register_library_map(array( 'PhabricatorProjectSilenceController' => 'applications/project/controller/PhabricatorProjectSilenceController.php', 'PhabricatorProjectSilencedEdgeType' => 'applications/project/edge/PhabricatorProjectSilencedEdgeType.php', 'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php', + 'PhabricatorProjectSlugsTransaction' => 'applications/project/xaction/PhabricatorProjectSlugsTransaction.php', + 'PhabricatorProjectSortTransaction' => 'applications/project/xaction/PhabricatorProjectSortTransaction.php', 'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php', 'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php', + 'PhabricatorProjectStatusTransaction' => 'applications/project/xaction/PhabricatorProjectStatusTransaction.php', 'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php', 'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php', 'PhabricatorProjectSubprojectsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectSubprojectsProfileMenuItem.php', @@ -3622,6 +3744,8 @@ phutil_register_library_map(array( 'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php', 'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php', 'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php', + 'PhabricatorProjectTransactionType' => 'applications/project/xaction/PhabricatorProjectTransactionType.php', + 'PhabricatorProjectTypeTransaction' => 'applications/project/xaction/PhabricatorProjectTypeTransaction.php', 'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php', 'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php', 'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php', @@ -3630,7 +3754,9 @@ phutil_register_library_map(array( 'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php', 'PhabricatorProjectWatcherListView' => 'applications/project/view/PhabricatorProjectWatcherListView.php', 'PhabricatorProjectWorkboardBackgroundColor' => 'applications/project/constants/PhabricatorProjectWorkboardBackgroundColor.php', + 'PhabricatorProjectWorkboardBackgroundTransaction' => 'applications/project/xaction/PhabricatorProjectWorkboardBackgroundTransaction.php', 'PhabricatorProjectWorkboardProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectWorkboardProfileMenuItem.php', + 'PhabricatorProjectWorkboardTransaction' => 'applications/project/xaction/PhabricatorProjectWorkboardTransaction.php', 'PhabricatorProjectsAncestorsSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsAncestorsSearchEngineAttachment.php', 'PhabricatorProjectsCurtainExtension' => 'applications/project/engineextension/PhabricatorProjectsCurtainExtension.php', 'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php', @@ -3653,9 +3779,11 @@ phutil_register_library_map(array( 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', 'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php', + 'PhabricatorRegexListConfigType' => 'applications/config/type/PhabricatorRegexListConfigType.php', 'PhabricatorRegistrationProfile' => 'applications/people/storage/PhabricatorRegistrationProfile.php', 'PhabricatorReleephApplication' => 'applications/releeph/application/PhabricatorReleephApplication.php', 'PhabricatorReleephApplicationConfigOptions' => 'applications/releeph/config/PhabricatorReleephApplicationConfigOptions.php', + 'PhabricatorRemarkupCachePurger' => 'applications/cache/purger/PhabricatorRemarkupCachePurger.php', 'PhabricatorRemarkupControl' => 'view/form/control/PhabricatorRemarkupControl.php', 'PhabricatorRemarkupCowsayBlockInterpreter' => 'infrastructure/markup/interpreter/PhabricatorRemarkupCowsayBlockInterpreter.php', 'PhabricatorRemarkupCustomBlockRule' => 'infrastructure/markup/rule/PhabricatorRemarkupCustomBlockRule.php', @@ -3680,9 +3808,11 @@ phutil_register_library_map(array( 'PhabricatorRepositoryCommitTestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryCommitTestCase.php', 'PhabricatorRepositoryConfigOptions' => 'applications/repository/config/PhabricatorRepositoryConfigOptions.php', 'PhabricatorRepositoryDAO' => 'applications/repository/storage/PhabricatorRepositoryDAO.php', + 'PhabricatorRepositoryDestructibleCodex' => 'applications/repository/codex/PhabricatorRepositoryDestructibleCodex.php', 'PhabricatorRepositoryDiscoveryEngine' => 'applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php', 'PhabricatorRepositoryEditor' => 'applications/repository/editor/PhabricatorRepositoryEditor.php', 'PhabricatorRepositoryEngine' => 'applications/repository/engine/PhabricatorRepositoryEngine.php', + 'PhabricatorRepositoryFerretEngine' => 'applications/repository/search/PhabricatorRepositoryFerretEngine.php', 'PhabricatorRepositoryFulltextEngine' => 'applications/repository/search/PhabricatorRepositoryFulltextEngine.php', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryGitCommitChangeParserWorker.php', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryGitCommitMessageParserWorker.php', @@ -3756,7 +3886,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryURITransaction' => 'applications/repository/storage/PhabricatorRepositoryURITransaction.php', 'PhabricatorRepositoryURITransactionQuery' => 'applications/repository/query/PhabricatorRepositoryURITransactionQuery.php', 'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php', - 'PhabricatorRepositoryVersion' => 'applications/repository/constants/PhabricatorRepositoryVersion.php', 'PhabricatorRepositoryWorkingCopyVersion' => 'applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php', 'PhabricatorRequestExceptionHandler' => 'aphront/handler/PhabricatorRequestExceptionHandler.php', 'PhabricatorResourceSite' => 'aphront/site/PhabricatorResourceSite.php', @@ -3800,6 +3929,7 @@ phutil_register_library_map(array( 'PhabricatorSearchDatasourceField' => 'applications/search/field/PhabricatorSearchDatasourceField.php', 'PhabricatorSearchDateControlField' => 'applications/search/field/PhabricatorSearchDateControlField.php', 'PhabricatorSearchDateField' => 'applications/search/field/PhabricatorSearchDateField.php', + 'PhabricatorSearchDefaultController' => 'applications/search/controller/PhabricatorSearchDefaultController.php', 'PhabricatorSearchDeleteController' => 'applications/search/controller/PhabricatorSearchDeleteController.php', 'PhabricatorSearchDocument' => 'applications/search/storage/document/PhabricatorSearchDocument.php', 'PhabricatorSearchDocumentField' => 'applications/search/storage/document/PhabricatorSearchDocumentField.php', @@ -3846,6 +3976,7 @@ phutil_register_library_map(array( 'PhabricatorSelectSetting' => 'applications/settings/setting/PhabricatorSelectSetting.php', 'PhabricatorSendGridConfigOptions' => 'applications/config/option/PhabricatorSendGridConfigOptions.php', 'PhabricatorSessionsSettingsPanel' => 'applications/settings/panel/PhabricatorSessionsSettingsPanel.php', + 'PhabricatorSetConfigType' => 'applications/config/type/PhabricatorSetConfigType.php', 'PhabricatorSetting' => 'applications/settings/setting/PhabricatorSetting.php', 'PhabricatorSettingsAccountPanelGroup' => 'applications/settings/panelgroup/PhabricatorSettingsAccountPanelGroup.php', 'PhabricatorSettingsAddEmailAction' => 'applications/settings/action/PhabricatorSettingsAddEmailAction.php', @@ -3878,10 +4009,12 @@ phutil_register_library_map(array( 'PhabricatorSlowvoteApplication' => 'applications/slowvote/application/PhabricatorSlowvoteApplication.php', 'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/PhabricatorSlowvoteChoice.php', 'PhabricatorSlowvoteCloseController' => 'applications/slowvote/controller/PhabricatorSlowvoteCloseController.php', + 'PhabricatorSlowvoteCloseTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteCloseTransaction.php', 'PhabricatorSlowvoteCommentController' => 'applications/slowvote/controller/PhabricatorSlowvoteCommentController.php', 'PhabricatorSlowvoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteController.php', 'PhabricatorSlowvoteDAO' => 'applications/slowvote/storage/PhabricatorSlowvoteDAO.php', 'PhabricatorSlowvoteDefaultViewCapability' => 'applications/slowvote/capability/PhabricatorSlowvoteDefaultViewCapability.php', + 'PhabricatorSlowvoteDescriptionTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteDescriptionTransaction.php', 'PhabricatorSlowvoteEditController' => 'applications/slowvote/controller/PhabricatorSlowvoteEditController.php', 'PhabricatorSlowvoteEditor' => 'applications/slowvote/editor/PhabricatorSlowvoteEditor.php', 'PhabricatorSlowvoteListController' => 'applications/slowvote/controller/PhabricatorSlowvoteListController.php', @@ -3891,16 +4024,19 @@ phutil_register_library_map(array( 'PhabricatorSlowvotePollController' => 'applications/slowvote/controller/PhabricatorSlowvotePollController.php', 'PhabricatorSlowvotePollPHIDType' => 'applications/slowvote/phid/PhabricatorSlowvotePollPHIDType.php', 'PhabricatorSlowvoteQuery' => 'applications/slowvote/query/PhabricatorSlowvoteQuery.php', + 'PhabricatorSlowvoteQuestionTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteQuestionTransaction.php', 'PhabricatorSlowvoteReplyHandler' => 'applications/slowvote/mail/PhabricatorSlowvoteReplyHandler.php', + 'PhabricatorSlowvoteResponsesTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteResponsesTransaction.php', 'PhabricatorSlowvoteSchemaSpec' => 'applications/slowvote/storage/PhabricatorSlowvoteSchemaSpec.php', 'PhabricatorSlowvoteSearchEngine' => 'applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php', + 'PhabricatorSlowvoteShuffleTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteShuffleTransaction.php', 'PhabricatorSlowvoteTransaction' => 'applications/slowvote/storage/PhabricatorSlowvoteTransaction.php', 'PhabricatorSlowvoteTransactionComment' => 'applications/slowvote/storage/PhabricatorSlowvoteTransactionComment.php', 'PhabricatorSlowvoteTransactionQuery' => 'applications/slowvote/query/PhabricatorSlowvoteTransactionQuery.php', + 'PhabricatorSlowvoteTransactionType' => 'applications/slowvote/xaction/PhabricatorSlowvoteTransactionType.php', 'PhabricatorSlowvoteVoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteVoteController.php', 'PhabricatorSlug' => 'infrastructure/util/PhabricatorSlug.php', 'PhabricatorSlugTestCase' => 'infrastructure/util/__tests__/PhabricatorSlugTestCase.php', - 'PhabricatorSortTableUIExample' => 'applications/uiexample/examples/PhabricatorSortTableUIExample.php', 'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php', 'PhabricatorSpaceEditField' => 'applications/transactions/editfield/PhabricatorSpaceEditField.php', 'PhabricatorSpacesApplication' => 'applications/spaces/application/PhabricatorSpacesApplication.php', @@ -3914,13 +4050,18 @@ phutil_register_library_map(array( 'PhabricatorSpacesInterface' => 'applications/spaces/interface/PhabricatorSpacesInterface.php', 'PhabricatorSpacesListController' => 'applications/spaces/controller/PhabricatorSpacesListController.php', 'PhabricatorSpacesNamespace' => 'applications/spaces/storage/PhabricatorSpacesNamespace.php', + 'PhabricatorSpacesNamespaceArchiveTransaction' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceArchiveTransaction.php', 'PhabricatorSpacesNamespaceDatasource' => 'applications/spaces/typeahead/PhabricatorSpacesNamespaceDatasource.php', + 'PhabricatorSpacesNamespaceDefaultTransaction' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceDefaultTransaction.php', + 'PhabricatorSpacesNamespaceDescriptionTransaction' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceDescriptionTransaction.php', 'PhabricatorSpacesNamespaceEditor' => 'applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php', + 'PhabricatorSpacesNamespaceNameTransaction' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceNameTransaction.php', 'PhabricatorSpacesNamespacePHIDType' => 'applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php', 'PhabricatorSpacesNamespaceQuery' => 'applications/spaces/query/PhabricatorSpacesNamespaceQuery.php', 'PhabricatorSpacesNamespaceSearchEngine' => 'applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php', 'PhabricatorSpacesNamespaceTransaction' => 'applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php', 'PhabricatorSpacesNamespaceTransactionQuery' => 'applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php', + 'PhabricatorSpacesNamespaceTransactionType' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceTransactionType.php', 'PhabricatorSpacesNoAccessController' => 'applications/spaces/controller/PhabricatorSpacesNoAccessController.php', 'PhabricatorSpacesRemarkupRule' => 'applications/spaces/remarkup/PhabricatorSpacesRemarkupRule.php', 'PhabricatorSpacesSchemaSpec' => 'applications/spaces/storage/PhabricatorSpacesSchemaSpec.php', @@ -3952,6 +4093,7 @@ phutil_register_library_map(array( 'PhabricatorStorageFixtureScopeGuard' => 'infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php', 'PhabricatorStorageManagementAPI' => 'infrastructure/storage/management/PhabricatorStorageManagementAPI.php', 'PhabricatorStorageManagementAdjustWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php', + 'PhabricatorStorageManagementAnalyzeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementAnalyzeWorkflow.php', 'PhabricatorStorageManagementDatabasesWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php', 'PhabricatorStorageManagementDestroyWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php', 'PhabricatorStorageManagementDumpWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php', @@ -3967,6 +4109,8 @@ phutil_register_library_map(array( 'PhabricatorStoragePatch' => 'infrastructure/storage/management/PhabricatorStoragePatch.php', 'PhabricatorStorageSchemaSpec' => 'infrastructure/storage/schema/PhabricatorStorageSchemaSpec.php', 'PhabricatorStorageSetupCheck' => 'applications/config/check/PhabricatorStorageSetupCheck.php', + 'PhabricatorStringConfigType' => 'applications/config/type/PhabricatorStringConfigType.php', + 'PhabricatorStringListConfigType' => 'applications/config/type/PhabricatorStringListConfigType.php', 'PhabricatorStringListEditField' => 'applications/transactions/editfield/PhabricatorStringListEditField.php', 'PhabricatorStringSetting' => 'applications/settings/setting/PhabricatorStringSetting.php', 'PhabricatorSubmitEditField' => 'applications/transactions/editfield/PhabricatorSubmitEditField.php', @@ -4027,7 +4171,9 @@ phutil_register_library_map(array( 'PhabricatorTestStorageEngine' => 'applications/files/engine/PhabricatorTestStorageEngine.php', 'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php', 'PhabricatorTextAreaEditField' => 'applications/transactions/editfield/PhabricatorTextAreaEditField.php', + 'PhabricatorTextConfigType' => 'applications/config/type/PhabricatorTextConfigType.php', 'PhabricatorTextEditField' => 'applications/transactions/editfield/PhabricatorTextEditField.php', + 'PhabricatorTextListConfigType' => 'applications/config/type/PhabricatorTextListConfigType.php', 'PhabricatorTime' => 'infrastructure/time/PhabricatorTime.php', 'PhabricatorTimeFormatSetting' => 'applications/settings/setting/PhabricatorTimeFormatSetting.php', 'PhabricatorTimeGuard' => 'infrastructure/time/PhabricatorTimeGuard.php', @@ -4059,7 +4205,6 @@ phutil_register_library_map(array( 'PhabricatorTokensCurtainExtension' => 'applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php', 'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php', 'PhabricatorTokensToken' => 'applications/tokens/storage/PhabricatorTokensToken.php', - 'PhabricatorTooltipUIExample' => 'applications/uiexample/examples/PhabricatorTooltipUIExample.php', 'PhabricatorTransactionChange' => 'applications/transactions/data/PhabricatorTransactionChange.php', 'PhabricatorTransactionRemarkupChange' => 'applications/transactions/data/PhabricatorTransactionRemarkupChange.php', 'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php', @@ -4102,6 +4247,7 @@ phutil_register_library_map(array( 'PhabricatorUserBadgesCacheType' => 'applications/people/cache/PhabricatorUserBadgesCacheType.php', 'PhabricatorUserBlurbField' => 'applications/people/customfield/PhabricatorUserBlurbField.php', 'PhabricatorUserCache' => 'applications/people/storage/PhabricatorUserCache.php', + 'PhabricatorUserCachePurger' => 'applications/cache/purger/PhabricatorUserCachePurger.php', 'PhabricatorUserCacheType' => 'applications/people/cache/PhabricatorUserCacheType.php', 'PhabricatorUserCardView' => 'applications/people/view/PhabricatorUserCardView.php', 'PhabricatorUserConfigOptions' => 'applications/people/config/PhabricatorUserConfigOptions.php', @@ -4115,6 +4261,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', + 'PhabricatorUserFerretEngine' => 'applications/people/search/PhabricatorUserFerretEngine.php', 'PhabricatorUserFulltextEngine' => 'applications/people/search/PhabricatorUserFulltextEngine.php', 'PhabricatorUserIconField' => 'applications/people/customfield/PhabricatorUserIconField.php', 'PhabricatorUserLog' => 'applications/people/storage/PhabricatorUserLog.php', @@ -4152,6 +4299,7 @@ phutil_register_library_map(array( 'PhabricatorWebContentSource' => 'infrastructure/contentsource/PhabricatorWebContentSource.php', 'PhabricatorWebServerSetupCheck' => 'applications/config/check/PhabricatorWebServerSetupCheck.php', 'PhabricatorWeekStartDaySetting' => 'applications/settings/setting/PhabricatorWeekStartDaySetting.php', + 'PhabricatorWildConfigType' => 'applications/config/type/PhabricatorWildConfigType.php', 'PhabricatorWordPressAuthProvider' => 'applications/auth/provider/PhabricatorWordPressAuthProvider.php', 'PhabricatorWorker' => 'infrastructure/daemon/workers/PhabricatorWorker.php', 'PhabricatorWorkerActiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php', @@ -4227,24 +4375,35 @@ phutil_register_library_map(array( 'PhameBlogController' => 'applications/phame/controller/blog/PhameBlogController.php', 'PhameBlogCreateCapability' => 'applications/phame/capability/PhameBlogCreateCapability.php', 'PhameBlogDatasource' => 'applications/phame/typeahead/PhameBlogDatasource.php', + 'PhameBlogDescriptionTransaction' => 'applications/phame/xaction/PhameBlogDescriptionTransaction.php', 'PhameBlogEditConduitAPIMethod' => 'applications/phame/conduit/PhameBlogEditConduitAPIMethod.php', 'PhameBlogEditController' => 'applications/phame/controller/blog/PhameBlogEditController.php', 'PhameBlogEditEngine' => 'applications/phame/editor/PhameBlogEditEngine.php', 'PhameBlogEditor' => 'applications/phame/editor/PhameBlogEditor.php', 'PhameBlogFeedController' => 'applications/phame/controller/blog/PhameBlogFeedController.php', + 'PhameBlogFerretEngine' => 'applications/phame/search/PhameBlogFerretEngine.php', + 'PhameBlogFullDomainTransaction' => 'applications/phame/xaction/PhameBlogFullDomainTransaction.php', 'PhameBlogFulltextEngine' => 'applications/phame/search/PhameBlogFulltextEngine.php', + 'PhameBlogHeaderImageTransaction' => 'applications/phame/xaction/PhameBlogHeaderImageTransaction.php', 'PhameBlogHeaderPictureController' => 'applications/phame/controller/blog/PhameBlogHeaderPictureController.php', 'PhameBlogListController' => 'applications/phame/controller/blog/PhameBlogListController.php', 'PhameBlogListView' => 'applications/phame/view/PhameBlogListView.php', 'PhameBlogManageController' => 'applications/phame/controller/blog/PhameBlogManageController.php', + 'PhameBlogNameTransaction' => 'applications/phame/xaction/PhameBlogNameTransaction.php', + 'PhameBlogParentDomainTransaction' => 'applications/phame/xaction/PhameBlogParentDomainTransaction.php', + 'PhameBlogParentSiteTransaction' => 'applications/phame/xaction/PhameBlogParentSiteTransaction.php', + 'PhameBlogProfileImageTransaction' => 'applications/phame/xaction/PhameBlogProfileImageTransaction.php', 'PhameBlogProfilePictureController' => 'applications/phame/controller/blog/PhameBlogProfilePictureController.php', 'PhameBlogQuery' => 'applications/phame/query/PhameBlogQuery.php', 'PhameBlogReplyHandler' => 'applications/phame/mail/PhameBlogReplyHandler.php', 'PhameBlogSearchConduitAPIMethod' => 'applications/phame/conduit/PhameBlogSearchConduitAPIMethod.php', 'PhameBlogSearchEngine' => 'applications/phame/query/PhameBlogSearchEngine.php', 'PhameBlogSite' => 'applications/phame/site/PhameBlogSite.php', + 'PhameBlogStatusTransaction' => 'applications/phame/xaction/PhameBlogStatusTransaction.php', + 'PhameBlogSubtitleTransaction' => 'applications/phame/xaction/PhameBlogSubtitleTransaction.php', 'PhameBlogTransaction' => 'applications/phame/storage/PhameBlogTransaction.php', 'PhameBlogTransactionQuery' => 'applications/phame/query/PhameBlogTransactionQuery.php', + 'PhameBlogTransactionType' => 'applications/phame/xaction/PhameBlogTransactionType.php', 'PhameBlogViewController' => 'applications/phame/controller/blog/PhameBlogViewController.php', 'PhameConstants' => 'applications/phame/constants/PhameConstants.php', 'PhameController' => 'applications/phame/controller/PhameController.php', @@ -4256,12 +4415,16 @@ phutil_register_library_map(array( 'PhameNextPostView' => 'applications/phame/view/PhameNextPostView.php', 'PhamePost' => 'applications/phame/storage/PhamePost.php', 'PhamePostArchiveController' => 'applications/phame/controller/post/PhamePostArchiveController.php', + 'PhamePostBlogTransaction' => 'applications/phame/xaction/PhamePostBlogTransaction.php', + 'PhamePostBodyTransaction' => 'applications/phame/xaction/PhamePostBodyTransaction.php', 'PhamePostController' => 'applications/phame/controller/post/PhamePostController.php', 'PhamePostEditConduitAPIMethod' => 'applications/phame/conduit/PhamePostEditConduitAPIMethod.php', 'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php', 'PhamePostEditEngine' => 'applications/phame/editor/PhamePostEditEngine.php', 'PhamePostEditor' => 'applications/phame/editor/PhamePostEditor.php', + 'PhamePostFerretEngine' => 'applications/phame/search/PhamePostFerretEngine.php', 'PhamePostFulltextEngine' => 'applications/phame/search/PhamePostFulltextEngine.php', + 'PhamePostHeaderImageTransaction' => 'applications/phame/xaction/PhamePostHeaderImageTransaction.php', 'PhamePostHeaderPictureController' => 'applications/phame/controller/post/PhamePostHeaderPictureController.php', 'PhamePostHistoryController' => 'applications/phame/controller/post/PhamePostHistoryController.php', 'PhamePostListController' => 'applications/phame/controller/post/PhamePostListController.php', @@ -4274,10 +4437,14 @@ phutil_register_library_map(array( 'PhamePostReplyHandler' => 'applications/phame/mail/PhamePostReplyHandler.php', 'PhamePostSearchConduitAPIMethod' => 'applications/phame/conduit/PhamePostSearchConduitAPIMethod.php', 'PhamePostSearchEngine' => 'applications/phame/query/PhamePostSearchEngine.php', + 'PhamePostSubtitleTransaction' => 'applications/phame/xaction/PhamePostSubtitleTransaction.php', + 'PhamePostTitleTransaction' => 'applications/phame/xaction/PhamePostTitleTransaction.php', 'PhamePostTransaction' => 'applications/phame/storage/PhamePostTransaction.php', 'PhamePostTransactionComment' => 'applications/phame/storage/PhamePostTransactionComment.php', 'PhamePostTransactionQuery' => 'applications/phame/query/PhamePostTransactionQuery.php', + 'PhamePostTransactionType' => 'applications/phame/xaction/PhamePostTransactionType.php', 'PhamePostViewController' => 'applications/phame/controller/post/PhamePostViewController.php', + 'PhamePostVisibilityTransaction' => 'applications/phame/xaction/PhamePostVisibilityTransaction.php', 'PhameSchemaSpec' => 'applications/phame/storage/PhameSchemaSpec.php', 'PhameSite' => 'applications/phame/site/PhameSite.php', 'PhluxController' => 'applications/phlux/controller/PhluxController.php', @@ -4296,8 +4463,14 @@ phutil_register_library_map(array( 'PholioDefaultEditCapability' => 'applications/pholio/capability/PholioDefaultEditCapability.php', 'PholioDefaultViewCapability' => 'applications/pholio/capability/PholioDefaultViewCapability.php', 'PholioImage' => 'applications/pholio/storage/PholioImage.php', + 'PholioImageDescriptionTransaction' => 'applications/pholio/xaction/PholioImageDescriptionTransaction.php', + 'PholioImageFileTransaction' => 'applications/pholio/xaction/PholioImageFileTransaction.php', + 'PholioImageNameTransaction' => 'applications/pholio/xaction/PholioImageNameTransaction.php', 'PholioImagePHIDType' => 'applications/pholio/phid/PholioImagePHIDType.php', 'PholioImageQuery' => 'applications/pholio/query/PholioImageQuery.php', + 'PholioImageReplaceTransaction' => 'applications/pholio/xaction/PholioImageReplaceTransaction.php', + 'PholioImageSequenceTransaction' => 'applications/pholio/xaction/PholioImageSequenceTransaction.php', + 'PholioImageTransactionType' => 'applications/pholio/xaction/PholioImageTransactionType.php', 'PholioImageUploadController' => 'applications/pholio/controller/PholioImageUploadController.php', 'PholioInlineController' => 'applications/pholio/controller/PholioInlineController.php', 'PholioInlineListController' => 'applications/pholio/controller/PholioInlineListController.php', @@ -4306,24 +4479,30 @@ phutil_register_library_map(array( 'PholioMockAuthorHeraldField' => 'applications/pholio/herald/PholioMockAuthorHeraldField.php', 'PholioMockCommentController' => 'applications/pholio/controller/PholioMockCommentController.php', 'PholioMockDescriptionHeraldField' => 'applications/pholio/herald/PholioMockDescriptionHeraldField.php', + 'PholioMockDescriptionTransaction' => 'applications/pholio/xaction/PholioMockDescriptionTransaction.php', 'PholioMockEditController' => 'applications/pholio/controller/PholioMockEditController.php', 'PholioMockEditor' => 'applications/pholio/editor/PholioMockEditor.php', 'PholioMockEmbedView' => 'applications/pholio/view/PholioMockEmbedView.php', + 'PholioMockFerretEngine' => 'applications/pholio/search/PholioMockFerretEngine.php', 'PholioMockFulltextEngine' => 'applications/pholio/search/PholioMockFulltextEngine.php', 'PholioMockHasTaskEdgeType' => 'applications/pholio/edge/PholioMockHasTaskEdgeType.php', 'PholioMockHasTaskRelationship' => 'applications/pholio/relationships/PholioMockHasTaskRelationship.php', 'PholioMockHeraldField' => 'applications/pholio/herald/PholioMockHeraldField.php', 'PholioMockHeraldFieldGroup' => 'applications/pholio/herald/PholioMockHeraldFieldGroup.php', 'PholioMockImagesView' => 'applications/pholio/view/PholioMockImagesView.php', + 'PholioMockInlineTransaction' => 'applications/pholio/xaction/PholioMockInlineTransaction.php', 'PholioMockListController' => 'applications/pholio/controller/PholioMockListController.php', 'PholioMockMailReceiver' => 'applications/pholio/mail/PholioMockMailReceiver.php', 'PholioMockNameHeraldField' => 'applications/pholio/herald/PholioMockNameHeraldField.php', + 'PholioMockNameTransaction' => 'applications/pholio/xaction/PholioMockNameTransaction.php', 'PholioMockPHIDType' => 'applications/pholio/phid/PholioMockPHIDType.php', 'PholioMockQuery' => 'applications/pholio/query/PholioMockQuery.php', 'PholioMockRelationship' => 'applications/pholio/relationships/PholioMockRelationship.php', 'PholioMockRelationshipSource' => 'applications/search/relationship/PholioMockRelationshipSource.php', 'PholioMockSearchEngine' => 'applications/pholio/query/PholioMockSearchEngine.php', + 'PholioMockStatusTransaction' => 'applications/pholio/xaction/PholioMockStatusTransaction.php', 'PholioMockThumbGridView' => 'applications/pholio/view/PholioMockThumbGridView.php', + 'PholioMockTransactionType' => 'applications/pholio/xaction/PholioMockTransactionType.php', 'PholioMockViewController' => 'applications/pholio/controller/PholioMockViewController.php', 'PholioRemarkupRule' => 'applications/pholio/remarkup/PholioRemarkupRule.php', 'PholioReplyHandler' => 'applications/pholio/mail/PholioReplyHandler.php', @@ -4331,6 +4510,7 @@ phutil_register_library_map(array( 'PholioTransaction' => 'applications/pholio/storage/PholioTransaction.php', 'PholioTransactionComment' => 'applications/pholio/storage/PholioTransactionComment.php', 'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php', + 'PholioTransactionType' => 'applications/pholio/xaction/PholioTransactionType.php', 'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php', 'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php', 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', @@ -4521,16 +4701,23 @@ phutil_register_library_map(array( 'PhrictionDocument' => 'applications/phriction/storage/PhrictionDocument.php', 'PhrictionDocumentAuthorHeraldField' => 'applications/phriction/herald/PhrictionDocumentAuthorHeraldField.php', 'PhrictionDocumentContentHeraldField' => 'applications/phriction/herald/PhrictionDocumentContentHeraldField.php', + 'PhrictionDocumentContentTransaction' => 'applications/phriction/xaction/PhrictionDocumentContentTransaction.php', 'PhrictionDocumentController' => 'applications/phriction/controller/PhrictionDocumentController.php', + 'PhrictionDocumentDeleteTransaction' => 'applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php', + 'PhrictionDocumentFerretEngine' => 'applications/phriction/search/PhrictionDocumentFerretEngine.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', + 'PhrictionDocumentMoveAwayTransaction' => 'applications/phriction/xaction/PhrictionDocumentMoveAwayTransaction.php', + 'PhrictionDocumentMoveToTransaction' => 'applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php', 'PhrictionDocumentPHIDType' => 'applications/phriction/phid/PhrictionDocumentPHIDType.php', 'PhrictionDocumentPathHeraldField' => 'applications/phriction/herald/PhrictionDocumentPathHeraldField.php', 'PhrictionDocumentQuery' => 'applications/phriction/query/PhrictionDocumentQuery.php', 'PhrictionDocumentStatus' => 'applications/phriction/constants/PhrictionDocumentStatus.php', 'PhrictionDocumentTitleHeraldField' => 'applications/phriction/herald/PhrictionDocumentTitleHeraldField.php', + 'PhrictionDocumentTitleTransaction' => 'applications/phriction/xaction/PhrictionDocumentTitleTransaction.php', + 'PhrictionDocumentTransactionType' => 'applications/phriction/xaction/PhrictionDocumentTransactionType.php', 'PhrictionEditConduitAPIMethod' => 'applications/phriction/conduit/PhrictionEditConduitAPIMethod.php', 'PhrictionEditController' => 'applications/phriction/controller/PhrictionEditController.php', 'PhrictionHistoryConduitAPIMethod' => 'applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php', @@ -4552,18 +4739,22 @@ phutil_register_library_map(array( 'PonderAddAnswerView' => 'applications/ponder/view/PonderAddAnswerView.php', 'PonderAnswer' => 'applications/ponder/storage/PonderAnswer.php', 'PonderAnswerCommentController' => 'applications/ponder/controller/PonderAnswerCommentController.php', + 'PonderAnswerContentTransaction' => 'applications/ponder/xaction/PonderAnswerContentTransaction.php', 'PonderAnswerEditController' => 'applications/ponder/controller/PonderAnswerEditController.php', 'PonderAnswerEditor' => 'applications/ponder/editor/PonderAnswerEditor.php', 'PonderAnswerHistoryController' => 'applications/ponder/controller/PonderAnswerHistoryController.php', 'PonderAnswerMailReceiver' => 'applications/ponder/mail/PonderAnswerMailReceiver.php', 'PonderAnswerPHIDType' => 'applications/ponder/phid/PonderAnswerPHIDType.php', 'PonderAnswerQuery' => 'applications/ponder/query/PonderAnswerQuery.php', + 'PonderAnswerQuestionIDTransaction' => 'applications/ponder/xaction/PonderAnswerQuestionIDTransaction.php', 'PonderAnswerReplyHandler' => 'applications/ponder/mail/PonderAnswerReplyHandler.php', 'PonderAnswerSaveController' => 'applications/ponder/controller/PonderAnswerSaveController.php', 'PonderAnswerStatus' => 'applications/ponder/constants/PonderAnswerStatus.php', + 'PonderAnswerStatusTransaction' => 'applications/ponder/xaction/PonderAnswerStatusTransaction.php', 'PonderAnswerTransaction' => 'applications/ponder/storage/PonderAnswerTransaction.php', 'PonderAnswerTransactionComment' => 'applications/ponder/storage/PonderAnswerTransactionComment.php', 'PonderAnswerTransactionQuery' => 'applications/ponder/query/PonderAnswerTransactionQuery.php', + 'PonderAnswerTransactionType' => 'applications/ponder/xaction/PonderAnswerTransactionType.php', 'PonderAnswerView' => 'applications/ponder/view/PonderAnswerView.php', 'PonderConstants' => 'applications/ponder/constants/PonderConstants.php', 'PonderController' => 'applications/ponder/controller/PonderController.php', @@ -4573,9 +4764,13 @@ phutil_register_library_map(array( 'PonderFooterView' => 'applications/ponder/view/PonderFooterView.php', 'PonderModerateCapability' => 'applications/ponder/capability/PonderModerateCapability.php', 'PonderQuestion' => 'applications/ponder/storage/PonderQuestion.php', + 'PonderQuestionAnswerTransaction' => 'applications/ponder/xaction/PonderQuestionAnswerTransaction.php', + 'PonderQuestionAnswerWikiTransaction' => 'applications/ponder/xaction/PonderQuestionAnswerWikiTransaction.php', 'PonderQuestionCommentController' => 'applications/ponder/controller/PonderQuestionCommentController.php', + 'PonderQuestionContentTransaction' => 'applications/ponder/xaction/PonderQuestionContentTransaction.php', 'PonderQuestionCreateMailReceiver' => 'applications/ponder/mail/PonderQuestionCreateMailReceiver.php', 'PonderQuestionEditController' => 'applications/ponder/controller/PonderQuestionEditController.php', + 'PonderQuestionEditEngine' => 'applications/ponder/editor/PonderQuestionEditEngine.php', 'PonderQuestionEditor' => 'applications/ponder/editor/PonderQuestionEditor.php', 'PonderQuestionFulltextEngine' => 'applications/ponder/search/PonderQuestionFulltextEngine.php', 'PonderQuestionHistoryController' => 'applications/ponder/controller/PonderQuestionHistoryController.php', @@ -4587,9 +4782,12 @@ phutil_register_library_map(array( 'PonderQuestionSearchEngine' => 'applications/ponder/query/PonderQuestionSearchEngine.php', 'PonderQuestionStatus' => 'applications/ponder/constants/PonderQuestionStatus.php', 'PonderQuestionStatusController' => 'applications/ponder/controller/PonderQuestionStatusController.php', + 'PonderQuestionStatusTransaction' => 'applications/ponder/xaction/PonderQuestionStatusTransaction.php', + 'PonderQuestionTitleTransaction' => 'applications/ponder/xaction/PonderQuestionTitleTransaction.php', 'PonderQuestionTransaction' => 'applications/ponder/storage/PonderQuestionTransaction.php', 'PonderQuestionTransactionComment' => 'applications/ponder/storage/PonderQuestionTransactionComment.php', 'PonderQuestionTransactionQuery' => 'applications/ponder/query/PonderQuestionTransactionQuery.php', + 'PonderQuestionTransactionType' => 'applications/ponder/xaction/PonderQuestionTransactionType.php', 'PonderQuestionViewController' => 'applications/ponder/controller/PonderQuestionViewController.php', 'PonderRemarkupRule' => 'applications/ponder/remarkup/PonderRemarkupRule.php', 'PonderSchemaSpec' => 'applications/ponder/storage/PonderSchemaSpec.php', @@ -4711,6 +4909,7 @@ phutil_register_library_map(array( 'TokenGiveConduitAPIMethod' => 'applications/tokens/conduit/TokenGiveConduitAPIMethod.php', 'TokenGivenConduitAPIMethod' => 'applications/tokens/conduit/TokenGivenConduitAPIMethod.php', 'TokenQueryConduitAPIMethod' => 'applications/tokens/conduit/TokenQueryConduitAPIMethod.php', + 'TransactionSearchConduitAPIMethod' => 'applications/transactions/conduit/TransactionSearchConduitAPIMethod.php', 'UserConduitAPIMethod' => 'applications/people/conduit/UserConduitAPIMethod.php', 'UserDisableConduitAPIMethod' => 'applications/people/conduit/UserDisableConduitAPIMethod.php', 'UserEnableConduitAPIMethod' => 'applications/people/conduit/UserEnableConduitAPIMethod.php', @@ -5007,6 +5206,7 @@ phutil_register_library_map(array( 'CalendarTimeUtil' => 'Phobject', 'CalendarTimeUtilTestCase' => 'PhabricatorTestCase', 'CelerityAPI' => 'Phobject', + 'CelerityDarkModePostprocessor' => 'CelerityPostprocessor', 'CelerityDefaultPostprocessor' => 'CelerityPostprocessor', 'CelerityHighContrastPostprocessor' => 'CelerityPostprocessor', 'CelerityLargeFontPostprocessor' => 'CelerityPostprocessor', @@ -5079,13 +5279,14 @@ phutil_register_library_map(array( 'ConpherenceCreateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceDAO' => 'PhabricatorLiskDAO', 'ConpherenceDurableColumnView' => 'AphrontTagView', + 'ConpherenceEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', + 'ConpherenceEditEngine' => 'PhabricatorEditEngine', 'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor', 'ConpherenceFulltextQuery' => 'PhabricatorOffsetPagedQuery', 'ConpherenceIndex' => 'ConpherenceDAO', 'ConpherenceLayoutView' => 'AphrontTagView', 'ConpherenceListController' => 'ConpherenceController', 'ConpherenceMenuItemView' => 'AphrontTagView', - 'ConpherenceNewRoomController' => 'ConpherenceController', 'ConpherenceNotificationPanelController' => 'ConpherenceController', 'ConpherenceParticipant' => 'ConpherenceDAO', 'ConpherenceParticipantController' => 'ConpherenceController', @@ -5095,6 +5296,7 @@ phutil_register_library_map(array( 'ConpherenceQueryThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceQueryTransactionConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler', + 'ConpherenceRoomEditController' => 'ConpherenceController', 'ConpherenceRoomListController' => 'ConpherenceController', 'ConpherenceRoomPictureController' => 'ConpherenceController', 'ConpherenceRoomPreferencesController' => 'ConpherenceController', @@ -5245,11 +5447,9 @@ phutil_register_library_map(array( 'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'DifferentialDiffViewController' => 'DifferentialController', 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher', - 'DifferentialDraft' => 'DifferentialDAO', 'DifferentialExactUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DifferentialFieldParseException' => 'Exception', 'DifferentialFieldValidationException' => 'Exception', - 'DifferentialFindConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetAllDiffsConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetCommitPathsConduitAPIMethod' => 'DifferentialConduitAPIMethod', @@ -5258,13 +5458,10 @@ phutil_register_library_map(array( 'DifferentialGetRevisionCommentsConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetWorkingCopy' => 'Phobject', - 'DifferentialGitHubLandingStrategy' => 'DifferentialLandingStrategy', 'DifferentialGitSVNIDCommitMessageField' => 'DifferentialCommitMessageField', 'DifferentialHarbormasterField' => 'DifferentialCustomField', 'DifferentialHiddenComment' => 'DifferentialDAO', 'DifferentialHostField' => 'DifferentialCustomField', - 'DifferentialHostedGitLandingStrategy' => 'DifferentialLandingStrategy', - 'DifferentialHostedMercurialLandingStrategy' => 'DifferentialLandingStrategy', 'DifferentialHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'DifferentialHunk' => array( 'DifferentialDAO', @@ -5283,9 +5480,8 @@ phutil_register_library_map(array( 'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery', 'DifferentialJIRAIssuesCommitMessageField' => 'DifferentialCommitMessageCustomField', 'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField', - 'DifferentialLandingActionMenuEventListener' => 'PhabricatorEventListener', - 'DifferentialLandingStrategy' => 'Phobject', 'DifferentialLegacyHunk' => 'DifferentialHunk', + 'DifferentialLegacyQuery' => 'Phobject', 'DifferentialLineAdjustmentMap' => 'Phobject', 'DifferentialLintField' => 'DifferentialHarbormasterField', 'DifferentialLintStatus' => 'Phobject', @@ -5341,6 +5537,7 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', 'PhabricatorProjectInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', 'PhabricatorConduitResultInterface', 'PhabricatorDraftInterface', ), @@ -5352,6 +5549,7 @@ phutil_register_library_map(array( 'DifferentialRevisionAuthorProjectsHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionCloseDetailsController' => 'DifferentialController', 'DifferentialRevisionCloseTransaction' => 'DifferentialRevisionActionTransaction', + 'DifferentialRevisionClosedStatusDatasource' => 'PhabricatorTypeaheadDatasource', 'DifferentialRevisionCommandeerTransaction' => 'DifferentialRevisionActionTransaction', 'DifferentialRevisionContentAddedHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionContentHeraldField' => 'DifferentialRevisionHeraldField', @@ -5363,6 +5561,7 @@ phutil_register_library_map(array( 'DifferentialRevisionEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionEditEngine' => 'PhabricatorEditEngine', + 'DifferentialRevisionFerretEngine' => 'PhabricatorFerretEngine', 'DifferentialRevisionFulltextEngine' => 'PhabricatorFulltextEngine', 'DifferentialRevisionGraph' => 'PhabricatorObjectGraph', 'DifferentialRevisionHasChildRelationship' => 'DifferentialRevisionRelationship', @@ -5375,10 +5574,12 @@ phutil_register_library_map(array( 'DifferentialRevisionHeraldField' => 'HeraldField', 'DifferentialRevisionHeraldFieldGroup' => 'HeraldFieldGroup', 'DifferentialRevisionIDCommitMessageField' => 'DifferentialCommitMessageField', - 'DifferentialRevisionLandController' => 'DifferentialController', + 'DifferentialRevisionInlineTransaction' => 'PhabricatorModularTransactionType', + 'DifferentialRevisionInlinesController' => 'DifferentialController', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionListView' => 'AphrontView', 'DifferentialRevisionMailReceiver' => 'PhabricatorObjectMailReceiver', + 'DifferentialRevisionOpenStatusDatasource' => 'PhabricatorTypeaheadDatasource', 'DifferentialRevisionOperationController' => 'DifferentialController', 'DifferentialRevisionPHIDType' => 'PhabricatorPHIDType', 'DifferentialRevisionPackageHeraldField' => 'DifferentialRevisionHeraldField', @@ -5403,6 +5604,9 @@ phutil_register_library_map(array( 'DifferentialRevisionSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'DifferentialRevisionSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DifferentialRevisionStatus' => 'Phobject', + 'DifferentialRevisionStatusDatasource' => 'PhabricatorTypeaheadDatasource', + 'DifferentialRevisionStatusFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', + 'DifferentialRevisionStatusTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialRevisionSummaryHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionSummaryTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialRevisionTestPlanTransaction' => 'DifferentialRevisionTransactionType', @@ -5440,6 +5644,7 @@ phutil_register_library_map(array( 'DiffusionBlameConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBlameQuery' => 'DiffusionQuery', 'DiffusionBlockHeraldAction' => 'HeraldAction', + 'DiffusionBranchListView' => 'DiffusionView', 'DiffusionBranchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBranchTableController' => 'DiffusionController', 'DiffusionBranchTableView' => 'DiffusionView', @@ -5451,6 +5656,7 @@ phutil_register_library_map(array( 'DiffusionCachedResolveRefsQuery' => 'DiffusionLowLevelQuery', 'DiffusionChangeController' => 'DiffusionController', 'DiffusionChangeHeraldFieldGroup' => 'HeraldFieldGroup', + 'DiffusionCloneController' => 'DiffusionController', 'DiffusionCloneURIView' => 'AphrontView', 'DiffusionCommandEngine' => 'Phobject', 'DiffusionCommandEngineTestCase' => 'PhabricatorTestCase', @@ -5475,6 +5681,7 @@ phutil_register_library_map(array( 'DiffusionCommitEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'DiffusionCommitEditController' => 'DiffusionController', 'DiffusionCommitEditEngine' => 'PhabricatorEditEngine', + 'DiffusionCommitFerretEngine' => 'PhabricatorFerretEngine', 'DiffusionCommitFulltextEngine' => 'PhabricatorFulltextEngine', 'DiffusionCommitHasPackageEdgeType' => 'PhabricatorEdgeType', 'DiffusionCommitHasRevisionEdgeType' => 'PhabricatorEdgeType', @@ -5488,6 +5695,7 @@ phutil_register_library_map(array( 'DiffusionCommitHookEngine' => 'Phobject', 'DiffusionCommitHookRejectException' => 'Exception', 'DiffusionCommitListController' => 'DiffusionController', + 'DiffusionCommitListView' => 'AphrontView', 'DiffusionCommitMergeHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitMessageHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitPackageAuditHeraldField' => 'DiffusionCommitHeraldField', @@ -5557,9 +5765,12 @@ phutil_register_library_map(array( 'DiffusionRepositoryClusterEngineLogInterface', ), 'DiffusionGitUploadPackSSHWorkflow' => 'DiffusionGitSSHWorkflow', + 'DiffusionGraphController' => 'DiffusionController', 'DiffusionHistoryController' => 'DiffusionController', + 'DiffusionHistoryListView' => 'DiffusionHistoryView', 'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', - 'DiffusionHistoryTableView' => 'DiffusionView', + 'DiffusionHistoryTableView' => 'DiffusionHistoryView', + 'DiffusionHistoryView' => 'DiffusionView', 'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController', 'DiffusionInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController', @@ -5576,7 +5787,6 @@ phutil_register_library_map(array( 'DiffusionLowLevelGitRefQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelMercurialBranchesQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelMercurialPathsQuery' => 'DiffusionLowLevelQuery', - 'DiffusionLowLevelMercurialPathsQueryTests' => 'PhabricatorTestCase', 'DiffusionLowLevelParentsQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelQuery' => 'Phobject', 'DiffusionLowLevelResolveRefsQuery' => 'DiffusionLowLevelQuery', @@ -5601,6 +5811,7 @@ phutil_register_library_map(array( 'DiffusionPathQueryTestCase' => 'PhabricatorTestCase', 'DiffusionPathTreeController' => 'DiffusionController', 'DiffusionPathValidateController' => 'DiffusionController', + 'DiffusionPatternSearchView' => 'DiffusionView', 'DiffusionPhpExternalSymbolsSource' => 'DiffusionExternalSymbolsSource', 'DiffusionPreCommitContentAffectedFilesHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentAuthorHeraldField' => 'DiffusionPreCommitContentHeraldField', @@ -5661,7 +5872,6 @@ phutil_register_library_map(array( 'DiffusionRepositoryController' => 'DiffusionController', 'DiffusionRepositoryDatasource' => 'PhabricatorTypeaheadDatasource', 'DiffusionRepositoryDefaultController' => 'DiffusionController', - 'DiffusionRepositoryDocumentationManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryEditActivateController' => 'DiffusionRepositoryManageController', 'DiffusionRepositoryEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'DiffusionRepositoryEditController' => 'DiffusionRepositoryManageController', @@ -5677,11 +5887,11 @@ phutil_register_library_map(array( 'DiffusionRepositoryManagementPanel' => 'Phobject', 'DiffusionRepositoryPath' => 'Phobject', 'DiffusionRepositoryPoliciesManagementPanel' => 'DiffusionRepositoryManagementPanel', + 'DiffusionRepositoryProfilePictureController' => 'DiffusionController', 'DiffusionRepositoryRef' => 'Phobject', 'DiffusionRepositoryRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'DiffusionRepositorySearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'DiffusionRepositoryStagingManagementPanel' => 'DiffusionRepositoryManagementPanel', - 'DiffusionRepositoryStatusManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryStorageManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositorySubversionManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositorySymbolsManagementPanel' => 'DiffusionRepositoryManagementPanel', @@ -5716,6 +5926,7 @@ phutil_register_library_map(array( 'DiffusionSymbolQuery' => 'PhabricatorOffsetPagedQuery', 'DiffusionTagListController' => 'DiffusionController', 'DiffusionTagListView' => 'DiffusionView', + 'DiffusionTagTableView' => 'DiffusionView', 'DiffusionTaggedRepositoriesFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionURIEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', @@ -6002,9 +6213,12 @@ phutil_register_library_map(array( 'FundBackerPHIDType' => 'PhabricatorPHIDType', 'FundBackerProduct' => 'PhortuneProductImplementation', 'FundBackerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'FundBackerRefundTransaction' => 'FundBackerTransactionType', 'FundBackerSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'FundBackerTransaction' => 'PhabricatorApplicationTransaction', + 'FundBackerStatusTransaction' => 'FundBackerTransactionType', + 'FundBackerTransaction' => 'PhabricatorModularTransaction', 'FundBackerTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'FundBackerTransactionType' => 'PhabricatorModularTransactionType', 'FundController' => 'PhabricatorController', 'FundCreateInitiativesCapability' => 'PhabricatorPolicyCapability', 'FundDAO' => 'PhabricatorLiskDAO', @@ -6020,22 +6234,32 @@ phutil_register_library_map(array( 'PhabricatorTokenReceiverInterface', 'PhabricatorDestructibleInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'FundInitiativeBackController' => 'FundController', + 'FundInitiativeBackerTransaction' => 'FundInitiativeTransactionType', 'FundInitiativeCloseController' => 'FundController', - 'FundInitiativeCommentController' => 'FundController', + 'FundInitiativeDescriptionTransaction' => 'FundInitiativeTransactionType', 'FundInitiativeEditController' => 'FundController', + 'FundInitiativeEditEngine' => 'PhabricatorEditEngine', 'FundInitiativeEditor' => 'PhabricatorApplicationTransactionEditor', + 'FundInitiativeFerretEngine' => 'PhabricatorFerretEngine', 'FundInitiativeFulltextEngine' => 'PhabricatorFulltextEngine', 'FundInitiativeListController' => 'FundController', + 'FundInitiativeMerchantTransaction' => 'FundInitiativeTransactionType', + 'FundInitiativeNameTransaction' => 'FundInitiativeTransactionType', 'FundInitiativePHIDType' => 'PhabricatorPHIDType', 'FundInitiativeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'FundInitiativeRefundTransaction' => 'FundInitiativeTransactionType', 'FundInitiativeRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'FundInitiativeReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', + 'FundInitiativeRisksTransaction' => 'FundInitiativeTransactionType', 'FundInitiativeSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'FundInitiativeTransaction' => 'PhabricatorApplicationTransaction', + 'FundInitiativeStatusTransaction' => 'FundInitiativeTransactionType', + 'FundInitiativeTransaction' => 'PhabricatorModularTransaction', 'FundInitiativeTransactionComment' => 'PhabricatorApplicationTransactionComment', 'FundInitiativeTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'FundInitiativeTransactionType' => 'PhabricatorModularTransactionType', 'FundInitiativeViewController' => 'FundController', 'FundSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'HarbormasterArcLintBuildStepImplementation' => 'HarbormasterBuildStepImplementation', @@ -6321,10 +6545,6 @@ phutil_register_library_map(array( 'HeraldTranscriptTestCase' => 'PhabricatorTestCase', 'HeraldUtilityActionGroup' => 'HeraldActionGroup', 'Javelin' => 'Phobject', - 'JavelinReactorUIExample' => 'PhabricatorUIExample', - 'JavelinUIExample' => 'PhabricatorUIExample', - 'JavelinViewExampleServerView' => 'AphrontView', - 'JavelinViewUIExample' => 'PhabricatorUIExample', 'LegalpadController' => 'PhabricatorController', 'LegalpadCreateDocumentsCapability' => 'PhabricatorPolicyCapability', 'LegalpadDAO' => 'PhabricatorLiskDAO', @@ -6341,15 +6561,17 @@ phutil_register_library_map(array( 'LegalpadDAO', 'PhabricatorMarkupInterface', ), - 'LegalpadDocumentCommentController' => 'LegalpadController', 'LegalpadDocumentDatasource' => 'PhabricatorTypeaheadDatasource', 'LegalpadDocumentDoneController' => 'LegalpadController', 'LegalpadDocumentEditController' => 'LegalpadController', + 'LegalpadDocumentEditEngine' => 'PhabricatorEditEngine', 'LegalpadDocumentEditor' => 'PhabricatorApplicationTransactionEditor', 'LegalpadDocumentListController' => 'LegalpadController', 'LegalpadDocumentManageController' => 'LegalpadController', + 'LegalpadDocumentPreambleTransaction' => 'LegalpadDocumentTransactionType', 'LegalpadDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'LegalpadDocumentRemarkupRule' => 'PhabricatorObjectRemarkupRule', + 'LegalpadDocumentRequireSignatureTransaction' => 'LegalpadDocumentTransactionType', 'LegalpadDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine', 'LegalpadDocumentSignController' => 'LegalpadController', 'LegalpadDocumentSignature' => array( @@ -6360,15 +6582,19 @@ phutil_register_library_map(array( 'LegalpadDocumentSignatureListController' => 'LegalpadController', 'LegalpadDocumentSignatureQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'LegalpadDocumentSignatureSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'LegalpadDocumentSignatureTypeTransaction' => 'LegalpadDocumentTransactionType', 'LegalpadDocumentSignatureVerificationController' => 'LegalpadController', 'LegalpadDocumentSignatureViewController' => 'LegalpadController', + 'LegalpadDocumentTextTransaction' => 'LegalpadDocumentTransactionType', + 'LegalpadDocumentTitleTransaction' => 'LegalpadDocumentTransactionType', + 'LegalpadDocumentTransactionType' => 'PhabricatorModularTransactionType', 'LegalpadMailReceiver' => 'PhabricatorObjectMailReceiver', 'LegalpadObjectNeedsSignatureEdgeType' => 'PhabricatorEdgeType', 'LegalpadReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'LegalpadRequireSignatureHeraldAction' => 'HeraldAction', 'LegalpadSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'LegalpadSignatureNeededByObjectEdgeType' => 'PhabricatorEdgeType', - 'LegalpadTransaction' => 'PhabricatorApplicationTransaction', + 'LegalpadTransaction' => 'PhabricatorModularTransaction', 'LegalpadTransactionComment' => 'PhabricatorApplicationTransactionComment', 'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'LegalpadTransactionView' => 'PhabricatorApplicationTransactionView', @@ -6385,6 +6611,7 @@ phutil_register_library_map(array( 'LiskRawMigrationIterator' => 'PhutilBufferedIterator', 'MacroConduitAPIMethod' => 'ConduitAPIMethod', 'MacroCreateMemeConduitAPIMethod' => 'MacroConduitAPIMethod', + 'MacroEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'MacroEmojiExample' => 'PhabricatorUIExample', 'MacroQueryConduitAPIMethod' => 'MacroConduitAPIMethod', 'ManiphestAssignEmailCommand' => 'ManiphestEmailCommand', @@ -6427,9 +6654,10 @@ phutil_register_library_map(array( 'ManiphestHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'ManiphestInfoConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestNameIndex' => 'ManiphestDAO', - 'ManiphestPointsConfigOptionType' => 'PhabricatorConfigJSONOptionType', - 'ManiphestPriorityConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'ManiphestPointsConfigType' => 'PhabricatorJSONConfigType', + 'ManiphestPrioritiesConfigType' => 'PhabricatorJSONConfigType', 'ManiphestPriorityEmailCommand' => 'ManiphestEmailCommand', + 'ManiphestPrioritySearchConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestProjectNameFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 'ManiphestQueryConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestQueryStatusesConduitAPIMethod' => 'ManiphestConduitAPIMethod', @@ -6439,10 +6667,11 @@ phutil_register_library_map(array( 'ManiphestSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'ManiphestSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'ManiphestSearchController' => 'PhabricatorApplicationSearchController', - 'ManiphestStatusConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'ManiphestStatusEmailCommand' => 'ManiphestEmailCommand', + 'ManiphestStatusSearchConduitAPIMethod' => 'ManiphestConduitAPIMethod', + 'ManiphestStatusesConfigType' => 'PhabricatorJSONConfigType', 'ManiphestSubpriorityController' => 'ManiphestController', - 'ManiphestSubtypesConfigOptionsType' => 'PhabricatorConfigJSONOptionType', + 'ManiphestSubtypesConfigType' => 'PhabricatorJSONConfigType', 'ManiphestTask' => array( 'ManiphestDAO', 'PhabricatorSubscribableInterface', @@ -6459,6 +6688,7 @@ phutil_register_library_map(array( 'PhabricatorSpacesInterface', 'PhabricatorConduitResultInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', 'DoorkeeperBridgedObjectInterface', 'PhabricatorEditEngineSubtypeInterface', 'PhabricatorEditEngineLockableInterface', @@ -6467,17 +6697,22 @@ phutil_register_library_map(array( 'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction', 'ManiphestTaskAssignSelfHeraldAction' => 'ManiphestTaskAssignHeraldAction', 'ManiphestTaskAssigneeHeraldField' => 'ManiphestTaskHeraldField', + 'ManiphestTaskAttachTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskAuthorHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskAuthorPolicyRule' => 'PhabricatorPolicyRule', 'ManiphestTaskCloseAsDuplicateRelationship' => 'ManiphestTaskRelationship', 'ManiphestTaskClosedStatusDatasource' => 'PhabricatorTypeaheadDatasource', + 'ManiphestTaskCoverImageTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskDependedOnByTaskEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskDependsOnTaskEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskDescriptionHeraldField' => 'ManiphestTaskHeraldField', + 'ManiphestTaskDescriptionTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskDetailController' => 'ManiphestController', + 'ManiphestTaskEdgeTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskEditBulkJobType' => 'PhabricatorWorkerBulkJobType', 'ManiphestTaskEditController' => 'ManiphestController', 'ManiphestTaskEditEngineLock' => 'PhabricatorEditEngineLock', + 'ManiphestTaskFerretEngine' => 'PhabricatorFerretEngine', 'ManiphestTaskFulltextEngine' => 'PhabricatorFulltextEngine', 'ManiphestTaskGraph' => 'PhabricatorObjectGraph', 'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType', @@ -6497,14 +6732,20 @@ phutil_register_library_map(array( 'ManiphestTaskListView' => 'ManiphestView', 'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver', 'ManiphestTaskMergeInRelationship' => 'ManiphestTaskRelationship', + 'ManiphestTaskMergedFromTransaction' => 'ManiphestTaskTransactionType', + 'ManiphestTaskMergedIntoTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskOpenStatusDatasource' => 'PhabricatorTypeaheadDatasource', + 'ManiphestTaskOwnerTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskPHIDResolver' => 'PhabricatorPHIDResolver', 'ManiphestTaskPHIDType' => 'PhabricatorPHIDType', + 'ManiphestTaskParentTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskPoints' => 'Phobject', + 'ManiphestTaskPointsTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskPriority' => 'ManiphestConstants', 'ManiphestTaskPriorityDatasource' => 'PhabricatorTypeaheadDatasource', 'ManiphestTaskPriorityHeraldAction' => 'HeraldAction', 'ManiphestTaskPriorityHeraldField' => 'ManiphestTaskHeraldField', + 'ManiphestTaskPriorityTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'ManiphestTaskRelationship' => 'PhabricatorObjectRelationship', 'ManiphestTaskRelationshipSource' => 'PhabricatorObjectRelationshipSource', @@ -6516,10 +6757,15 @@ phutil_register_library_map(array( 'ManiphestTaskStatusHeraldAction' => 'HeraldAction', 'ManiphestTaskStatusHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskStatusTestCase' => 'PhabricatorTestCase', + 'ManiphestTaskStatusTransaction' => 'ManiphestTaskTransactionType', + 'ManiphestTaskSubpriorityTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskSubtypeDatasource' => 'PhabricatorTypeaheadDatasource', 'ManiphestTaskTestCase' => 'PhabricatorTestCase', 'ManiphestTaskTitleHeraldField' => 'ManiphestTaskHeraldField', - 'ManiphestTransaction' => 'PhabricatorApplicationTransaction', + 'ManiphestTaskTitleTransaction' => 'ManiphestTaskTransactionType', + 'ManiphestTaskTransactionType' => 'PhabricatorModularTransactionType', + 'ManiphestTaskUnblockTransaction' => 'ManiphestTaskTransactionType', + 'ManiphestTransaction' => 'PhabricatorModularTransaction', 'ManiphestTransactionComment' => 'PhabricatorApplicationTransactionComment', 'ManiphestTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'ManiphestTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -6541,11 +6787,13 @@ phutil_register_library_map(array( 'MultimeterLabel' => 'MultimeterDimension', 'MultimeterSampleController' => 'MultimeterController', 'MultimeterViewer' => 'MultimeterDimension', + 'NuanceCommandImplementation' => 'Phobject', 'NuanceConduitAPIMethod' => 'ConduitAPIMethod', 'NuanceConsoleController' => 'NuanceController', 'NuanceContentSource' => 'PhabricatorContentSource', 'NuanceController' => 'PhabricatorController', 'NuanceDAO' => 'PhabricatorLiskDAO', + 'NuanceFormItemType' => 'NuanceItemType', 'NuanceGitHubEventItemType' => 'NuanceItemType', 'NuanceGitHubImportCursor' => 'NuanceImportCursor', 'NuanceGitHubIssuesImportCursor' => 'NuanceGitHubImportCursor', @@ -6571,16 +6819,25 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'NuanceItemCommandQuery' => 'NuanceQuery', + 'NuanceItemCommandSpec' => 'Phobject', + 'NuanceItemCommandTransaction' => 'NuanceItemTransactionType', 'NuanceItemController' => 'NuanceController', 'NuanceItemEditor' => 'PhabricatorApplicationTransactionEditor', 'NuanceItemListController' => 'NuanceItemController', 'NuanceItemManageController' => 'NuanceController', + 'NuanceItemOwnerTransaction' => 'NuanceItemTransactionType', 'NuanceItemPHIDType' => 'PhabricatorPHIDType', + 'NuanceItemPropertyTransaction' => 'NuanceItemTransactionType', 'NuanceItemQuery' => 'NuanceQuery', + 'NuanceItemQueueTransaction' => 'NuanceItemTransactionType', + 'NuanceItemRequestorTransaction' => 'NuanceItemTransactionType', 'NuanceItemSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'NuanceItemSourceTransaction' => 'NuanceItemTransactionType', + 'NuanceItemStatusTransaction' => 'NuanceItemTransactionType', 'NuanceItemTransaction' => 'NuanceTransaction', 'NuanceItemTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceItemTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'NuanceItemTransactionType' => 'PhabricatorModularTransactionType', 'NuanceItemType' => 'Phobject', 'NuanceItemUpdateWorker' => 'NuanceWorker', 'NuanceItemViewController' => 'NuanceController', @@ -6600,13 +6857,16 @@ phutil_register_library_map(array( 'NuanceQueueEditEngine' => 'PhabricatorEditEngine', 'NuanceQueueEditor' => 'PhabricatorApplicationTransactionEditor', 'NuanceQueueListController' => 'NuanceQueueController', + 'NuanceQueueNameTransaction' => 'NuanceQueueTransactionType', 'NuanceQueuePHIDType' => 'PhabricatorPHIDType', 'NuanceQueueQuery' => 'NuanceQuery', 'NuanceQueueSearchEngine' => 'PhabricatorApplicationSearchEngine', 'NuanceQueueTransaction' => 'NuanceTransaction', 'NuanceQueueTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceQueueTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'NuanceQueueTransactionType' => 'PhabricatorModularTransactionType', 'NuanceQueueViewController' => 'NuanceQueueController', + 'NuanceQueueWorkController' => 'NuanceQueueController', 'NuanceSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'NuanceSource' => array( 'NuanceDAO', @@ -6617,6 +6877,7 @@ phutil_register_library_map(array( 'NuanceSourceActionController' => 'NuanceController', 'NuanceSourceController' => 'NuanceController', 'NuanceSourceDefaultEditCapability' => 'PhabricatorPolicyCapability', + 'NuanceSourceDefaultQueueTransaction' => 'NuanceSourceTransactionType', 'NuanceSourceDefaultViewCapability' => 'PhabricatorPolicyCapability', 'NuanceSourceDefinition' => 'Phobject', 'NuanceSourceDefinitionTestCase' => 'PhabricatorTestCase', @@ -6626,14 +6887,17 @@ phutil_register_library_map(array( 'NuanceSourceListController' => 'NuanceSourceController', 'NuanceSourceManageCapability' => 'PhabricatorPolicyCapability', 'NuanceSourceNameNgrams' => 'PhabricatorSearchNgrams', + 'NuanceSourceNameTransaction' => 'NuanceSourceTransactionType', 'NuanceSourcePHIDType' => 'PhabricatorPHIDType', 'NuanceSourceQuery' => 'NuanceQuery', 'NuanceSourceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'NuanceSourceTransaction' => 'NuanceTransaction', 'NuanceSourceTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceSourceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'NuanceSourceTransactionType' => 'PhabricatorModularTransactionType', 'NuanceSourceViewController' => 'NuanceSourceController', - 'NuanceTransaction' => 'PhabricatorApplicationTransaction', + 'NuanceTransaction' => 'PhabricatorModularTransaction', + 'NuanceTrashCommand' => 'NuanceCommandImplementation', 'NuanceWorker' => 'PhabricatorWorker', 'OwnersConduitAPIMethod' => 'ConduitAPIMethod', 'OwnersEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', @@ -6652,6 +6916,7 @@ phutil_register_library_map(array( 'PHUIBadgeExample' => 'PhabricatorUIExample', 'PHUIBadgeMiniView' => 'AphrontTagView', 'PHUIBadgeView' => 'AphrontTagView', + 'PHUIBigInfoExample' => 'PhabricatorUIExample', 'PHUIBigInfoView' => 'AphrontTagView', 'PHUIBoxExample' => 'PhabricatorUIExample', 'PHUIBoxView' => 'AphrontTagView', @@ -6680,6 +6945,7 @@ phutil_register_library_map(array( 'PHUIDiffInlineCommentTableScaffold' => 'AphrontView', 'PHUIDiffInlineCommentUndoView' => 'PHUIDiffInlineCommentView', 'PHUIDiffInlineCommentView' => 'AphrontView', + 'PHUIDiffInlineThreader' => 'Phobject', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 'PHUIDiffRevealIconView' => 'AphrontView', 'PHUIDiffTableOfContentsItemView' => 'AphrontView', @@ -6710,11 +6976,11 @@ phutil_register_library_map(array( 'PHUIImageMaskExample' => 'PhabricatorUIExample', 'PHUIImageMaskView' => 'AphrontTagView', 'PHUIInfoExample' => 'PhabricatorUIExample', - 'PHUIInfoPanelExample' => 'PhabricatorUIExample', - 'PHUIInfoPanelView' => 'AphrontView', 'PHUIInfoView' => 'AphrontTagView', 'PHUIInvisibleCharacterTestCase' => 'PhabricatorTestCase', 'PHUIInvisibleCharacterView' => 'AphrontView', + 'PHUILeftRightExample' => 'PhabricatorUIExample', + 'PHUILeftRightView' => 'AphrontTagView', 'PHUIListExample' => 'PhabricatorUIExample', 'PHUIListItemView' => 'AphrontTagView', 'PHUIListView' => 'AphrontTagView', @@ -6749,6 +7015,7 @@ phutil_register_library_map(array( 'PHUIUserAvailabilityView' => 'AphrontTagView', 'PHUIWorkboardView' => 'AphrontTagView', 'PHUIWorkpanelView' => 'AphrontTagView', + 'PHUIXComponentsExample' => 'PhabricatorUIExample', 'PassphraseAbstractKey' => 'Phobject', 'PassphraseConduitAPIMethod' => 'ConduitAPIMethod', 'PassphraseController' => 'PhabricatorController', @@ -6761,26 +7028,37 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', 'PhabricatorSpacesInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'PassphraseCredentialAuthorPolicyRule' => 'PhabricatorPolicyRule', 'PassphraseCredentialConduitController' => 'PassphraseController', + 'PassphraseCredentialConduitTransaction' => 'PassphraseCredentialTransactionType', 'PassphraseCredentialControl' => 'AphrontFormControl', 'PassphraseCredentialCreateController' => 'PassphraseController', + 'PassphraseCredentialDescriptionTransaction' => 'PassphraseCredentialTransactionType', 'PassphraseCredentialDestroyController' => 'PassphraseController', + 'PassphraseCredentialDestroyTransaction' => 'PassphraseCredentialTransactionType', 'PassphraseCredentialEditController' => 'PassphraseController', + 'PassphraseCredentialFerretEngine' => 'PhabricatorFerretEngine', 'PassphraseCredentialFulltextEngine' => 'PhabricatorFulltextEngine', 'PassphraseCredentialListController' => 'PassphraseController', 'PassphraseCredentialLockController' => 'PassphraseController', + 'PassphraseCredentialLockTransaction' => 'PassphraseCredentialTransactionType', + 'PassphraseCredentialLookedAtTransaction' => 'PassphraseCredentialTransactionType', + 'PassphraseCredentialNameTransaction' => 'PassphraseCredentialTransactionType', 'PassphraseCredentialPHIDType' => 'PhabricatorPHIDType', 'PassphraseCredentialPublicController' => 'PassphraseController', 'PassphraseCredentialQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PassphraseCredentialRevealController' => 'PassphraseController', 'PassphraseCredentialSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PassphraseCredentialTransaction' => 'PhabricatorApplicationTransaction', + 'PassphraseCredentialSecretIDTransaction' => 'PassphraseCredentialTransactionType', + 'PassphraseCredentialTransaction' => 'PhabricatorModularTransaction', 'PassphraseCredentialTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PassphraseCredentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PassphraseCredentialTransactionType' => 'PhabricatorModularTransactionType', 'PassphraseCredentialType' => 'Phobject', 'PassphraseCredentialTypeTestCase' => 'PhabricatorTestCase', + 'PassphraseCredentialUsernameTransaction' => 'PassphraseCredentialTransactionType', 'PassphraseCredentialViewController' => 'PassphraseController', 'PassphraseDAO' => 'PhabricatorLiskDAO', 'PassphraseDefaultEditCapability' => 'PhabricatorPolicyCapability', @@ -6857,9 +7135,12 @@ phutil_register_library_map(array( 'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationDocumentation' => 'PhabricatorApplication', 'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController', + 'PhabricatorApplicationEditEngine' => 'PhabricatorEditEngine', 'PhabricatorApplicationEditHTTPParameterHelpView' => 'AphrontView', + 'PhabricatorApplicationEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController', + 'PhabricatorApplicationPolicyChangeTransaction' => 'PhabricatorApplicationTransactionType', 'PhabricatorApplicationProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorApplicationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorApplicationSchemaSpec' => 'PhabricatorConfigSchemaSpec', @@ -7130,7 +7411,6 @@ phutil_register_library_map(array( 'PhabricatorBadgesTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorBadgesTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorBadgesViewController' => 'PhabricatorBadgesProfileController', - 'PhabricatorBarePageUIExample' => 'PhabricatorUIExample', 'PhabricatorBarePageView' => 'AphrontPageView', 'PhabricatorBaseURISetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorBcryptPasswordHasher' => 'PhabricatorPasswordHasher', @@ -7140,13 +7420,14 @@ phutil_register_library_map(array( 'PhabricatorBoardLayoutEngine' => 'Phobject', 'PhabricatorBoardRenderingEngine' => 'Phobject', 'PhabricatorBoardResponseEngine' => 'Phobject', + 'PhabricatorBoolConfigType' => 'PhabricatorTextConfigType', 'PhabricatorBoolEditField' => 'PhabricatorEditField', 'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation', 'PhabricatorBuildbotController' => 'PhabricatorController', 'PhabricatorBuiltinDraftEngine' => 'PhabricatorDraftEngine', + 'PhabricatorBuiltinFileCachePurger' => 'PhabricatorCachePurger', 'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList', 'PhabricatorBulkContentSource' => 'PhabricatorContentSource', - 'PhabricatorBusyUIExample' => 'PhabricatorUIExample', 'PhabricatorCacheDAO' => 'PhabricatorLiskDAO', 'PhabricatorCacheEngine' => 'Phobject', 'PhabricatorCacheEngineExtension' => 'Phobject', @@ -7154,6 +7435,7 @@ phutil_register_library_map(array( 'PhabricatorCacheManagementPurgeWorkflow' => 'PhabricatorCacheManagementWorkflow', 'PhabricatorCacheManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorCacheMarkupGarbageCollector' => 'PhabricatorGarbageCollector', + 'PhabricatorCachePurger' => 'Phobject', 'PhabricatorCacheSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorCacheSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorCacheSpec' => 'Phobject', @@ -7179,6 +7461,7 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface', 'PhabricatorSpacesInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', 'PhabricatorConduitResultInterface', ), 'PhabricatorCalendarEventAcceptTransaction' => 'PhabricatorCalendarEventReplyTransaction', @@ -7199,6 +7482,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventEmailCommand' => 'MetaMTAEmailTransactionCommand', 'PhabricatorCalendarEventEndDateTransaction' => 'PhabricatorCalendarEventDateTransaction', 'PhabricatorCalendarEventExportController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarEventFerretEngine' => 'PhabricatorFerretEngine', 'PhabricatorCalendarEventForkTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventFrequencyTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventFulltextEngine' => 'PhabricatorFulltextEngine', @@ -7339,6 +7623,7 @@ phutil_register_library_map(array( 'PhabricatorCelerityApplication' => 'PhabricatorApplication', 'PhabricatorCelerityTestCase' => 'PhabricatorTestCase', 'PhabricatorChangeParserTestCase' => 'PhabricatorWorkingCopyTestCase', + 'PhabricatorChangesetCachePurger' => 'PhabricatorCachePurger', 'PhabricatorChangesetResponse' => 'AphrontProxyResponse', 'PhabricatorChatLogApplication' => 'PhabricatorApplication', 'PhabricatorChatLogChannel' => array( @@ -7356,14 +7641,15 @@ phutil_register_library_map(array( ), 'PhabricatorChatLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorChunkedFileStorageEngine' => 'PhabricatorFileStorageEngine', + 'PhabricatorClassConfigType' => 'PhabricatorTextConfigType', 'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions', - 'PhabricatorClusterDatabasesConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorClusterDatabasesConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorClusterException' => 'Exception', 'PhabricatorClusterExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorClusterImpossibleWriteException' => 'PhabricatorClusterException', 'PhabricatorClusterImproperWriteException' => 'PhabricatorClusterException', 'PhabricatorClusterNoHostForRoleException' => 'Exception', - 'PhabricatorClusterSearchConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorClusterSearchConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorClusterServiceHealthRecord' => 'Phobject', 'PhabricatorClusterStrandedException' => 'PhabricatorClusterException', 'PhabricatorColumnsEditField' => 'PhabricatorPHIDListEditField', @@ -7470,7 +7756,6 @@ phutil_register_library_map(array( ), 'PhabricatorConfigOptionType' => 'Phobject', 'PhabricatorConfigPHIDModule' => 'PhabricatorConfigModule', - 'PhabricatorConfigPageView' => 'AphrontTagView', 'PhabricatorConfigProxySource' => 'PhabricatorConfigSource', 'PhabricatorConfigPurgeCacheController' => 'PhabricatorConfigController', 'PhabricatorConfigRegexOptionType' => 'PhabricatorConfigJSONOptionType', @@ -7488,6 +7773,7 @@ phutil_register_library_map(array( 'PhabricatorConfigTableSchema' => 'PhabricatorConfigStorageSchema', 'PhabricatorConfigTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorConfigType' => 'Phobject', 'PhabricatorConfigValidationException' => 'Exception', 'PhabricatorConfigVersionController' => 'PhabricatorConfigController', 'PhabricatorConpherenceApplication' => 'PhabricatorApplication', @@ -7496,6 +7782,8 @@ phutil_register_library_map(array( 'PhabricatorConpherenceNotificationsSetting' => 'PhabricatorSelectSetting', 'PhabricatorConpherencePreferencesSettingsPanel' => 'PhabricatorEditEngineSettingsPanel', 'PhabricatorConpherenceProfileMenuItem' => 'PhabricatorProfileMenuItem', + 'PhabricatorConpherenceRoomContextFreeGrammar' => 'PhutilContextFreeGrammar', + 'PhabricatorConpherenceRoomTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorConpherenceSoundSetting' => 'PhabricatorSelectSetting', 'PhabricatorConpherenceThreadPHIDType' => 'PhabricatorPHIDType', 'PhabricatorConpherenceWidgetVisibleSetting' => 'PhabricatorInternalSetting', @@ -7571,6 +7859,7 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldStorageQuery' => 'Phobject', 'PhabricatorCustomFieldStringIndexStorage' => 'PhabricatorCustomFieldIndexStorage', 'PhabricatorCustomLogoConfigType' => 'PhabricatorConfigOptionType', + 'PhabricatorCustomUIFooterConfigType' => 'PhabricatorConfigJSONOptionType', 'PhabricatorDaemon' => 'PhutilDaemon', 'PhabricatorDaemonBulkJobController' => 'PhabricatorDaemonController', 'PhabricatorDaemonBulkJobListController' => 'PhabricatorDaemonBulkJobController', @@ -7705,8 +7994,7 @@ phutil_register_library_map(array( 'PhabricatorDebugController' => 'PhabricatorController', 'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorDefaultSyntaxStyle' => 'PhabricatorSyntaxStyle', - 'PhabricatorDesktopNotificationsSetting' => 'PhabricatorInternalSetting', - 'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel', + 'PhabricatorDestructibleCodex' => 'Phobject', 'PhabricatorDestructionEngine' => 'Phobject', 'PhabricatorDestructionEngineExtension' => 'Phobject', 'PhabricatorDestructionEngineExtensionModule' => 'PhabricatorConfigModule', @@ -7828,6 +8116,7 @@ phutil_register_library_map(array( 'PhabricatorEmojiRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorEmojiTranslation' => 'PhutilTranslation', 'PhabricatorEmptyQueryException' => 'Exception', + 'PhabricatorEnumConfigType' => 'PhabricatorTextConfigType', 'PhabricatorEnv' => 'Phobject', 'PhabricatorEnvTestCase' => 'PhabricatorTestCase', 'PhabricatorEpochEditField' => 'PhabricatorEditField', @@ -7895,6 +8184,12 @@ phutil_register_library_map(array( 'PhabricatorFeedStoryNotification' => 'PhabricatorFeedDAO', 'PhabricatorFeedStoryPublisher' => 'Phobject', 'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO', + 'PhabricatorFerretEngine' => 'Phobject', + 'PhabricatorFerretEngineTestCase' => 'PhabricatorTestCase', + 'PhabricatorFerretFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', + 'PhabricatorFerretFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine', + 'PhabricatorFerretMetadata' => 'Phobject', + 'PhabricatorFerretSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'PhabricatorFile' => array( 'PhabricatorFileDAO', 'PhabricatorApplicationTransactionInterface', @@ -7999,7 +8294,6 @@ phutil_register_library_map(array( 'PhabricatorFilesManagementGenerateKeyWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementIntegrityWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementMigrateWorkflow' => 'PhabricatorFilesManagementWorkflow', - 'PhabricatorFilesManagementPurgeWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementRebuildWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorFilesOnDiskBuiltinFile' => 'PhabricatorFilesBuiltinFile', @@ -8038,6 +8332,7 @@ phutil_register_library_map(array( 'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow', 'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow', 'PhabricatorGarbageCollectorManagementWorkflow' => 'PhabricatorManagementWorkflow', + 'PhabricatorGeneralCachePurger' => 'PhabricatorCachePurger', 'PhabricatorGestureUIExample' => 'PhabricatorUIExample', 'PhabricatorGitGraphStream' => 'PhabricatorRepositoryGraphStream', 'PhabricatorGitHubAuthProvider' => 'PhabricatorOAuth2AuthProvider', @@ -8111,6 +8406,7 @@ phutil_register_library_map(array( 'PhabricatorInlineCommentPreviewController' => 'PhabricatorController', 'PhabricatorInlineSummaryView' => 'AphrontView', 'PhabricatorInstructionsEditField' => 'PhabricatorEditField', + 'PhabricatorIntConfigType' => 'PhabricatorTextConfigType', 'PhabricatorInternalSetting' => 'PhabricatorSetting', 'PhabricatorInternationalizationManagementExtractWorkflow' => 'PhabricatorInternationalizationManagementWorkflow', 'PhabricatorInternationalizationManagementWorkflow' => 'PhabricatorManagementWorkflow', @@ -8119,6 +8415,7 @@ phutil_register_library_map(array( 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'PhabricatorTestCase', 'PhabricatorIteratorFileUploadSource' => 'PhabricatorFileUploadSource', 'PhabricatorJIRAAuthProvider' => 'PhabricatorOAuth1AuthProvider', + 'PhabricatorJSONConfigType' => 'PhabricatorTextConfigType', 'PhabricatorJavelinLinter' => 'ArcanistLinter', 'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorJumpNavHandler' => 'Phobject', @@ -8144,7 +8441,6 @@ phutil_register_library_map(array( 'PhabricatorLiskFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 'PhabricatorLiskSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'PhabricatorLiskSerializer' => 'Phobject', - 'PhabricatorListFilterUIExample' => 'PhabricatorUIExample', 'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorLocalTimeTestCase' => 'PhabricatorTestCase', 'PhabricatorLocaleScopeGuard' => 'Phobject', @@ -8153,26 +8449,32 @@ phutil_register_library_map(array( 'PhabricatorLogoutController' => 'PhabricatorAuthController', 'PhabricatorLunarPhasePolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorMacroApplication' => 'PhabricatorApplication', + 'PhabricatorMacroAudioBehaviorTransaction' => 'PhabricatorMacroTransactionType', 'PhabricatorMacroAudioController' => 'PhabricatorMacroController', - 'PhabricatorMacroCommentController' => 'PhabricatorMacroController', + 'PhabricatorMacroAudioTransaction' => 'PhabricatorMacroTransactionType', 'PhabricatorMacroConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorMacroController' => 'PhabricatorController', 'PhabricatorMacroDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorMacroDisableController' => 'PhabricatorMacroController', - 'PhabricatorMacroEditController' => 'PhabricatorMacroController', + 'PhabricatorMacroDisabledTransaction' => 'PhabricatorMacroTransactionType', + 'PhabricatorMacroEditController' => 'PhameBlogController', + 'PhabricatorMacroEditEngine' => 'PhabricatorEditEngine', 'PhabricatorMacroEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorMacroFileTransaction' => 'PhabricatorMacroTransactionType', 'PhabricatorMacroListController' => 'PhabricatorMacroController', 'PhabricatorMacroMacroPHIDType' => 'PhabricatorPHIDType', 'PhabricatorMacroMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorMacroManageCapability' => 'PhabricatorPolicyCapability', 'PhabricatorMacroMemeController' => 'PhabricatorMacroController', 'PhabricatorMacroMemeDialogController' => 'PhabricatorMacroController', + 'PhabricatorMacroNameTransaction' => 'PhabricatorMacroTransactionType', 'PhabricatorMacroQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorMacroReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorMacroSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhabricatorMacroTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorMacroTransaction' => 'PhabricatorModularTransaction', 'PhabricatorMacroTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorMacroTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorMacroViewController' => 'PhabricatorMacroController', 'PhabricatorMailEmailHeraldField' => 'HeraldField', 'PhabricatorMailEmailHeraldFieldGroup' => 'HeraldFieldGroup', @@ -8298,6 +8600,11 @@ phutil_register_library_map(array( 'PhabricatorSearchDAO', 'PhabricatorPolicyInterface', ), + 'PhabricatorNamedQueryConfig' => array( + 'PhabricatorSearchDAO', + 'PhabricatorPolicyInterface', + ), + 'PhabricatorNamedQueryConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorNamedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorNavigationRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorNeverTriggerClock' => 'PhabricatorTriggerClock', @@ -8315,12 +8622,14 @@ phutil_register_library_map(array( 'PhabricatorNotificationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorNotificationSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorNotificationServerRef' => 'Phobject', - 'PhabricatorNotificationServersConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorNotificationServersConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorNotificationStatusView' => 'AphrontTagView', 'PhabricatorNotificationTestController' => 'PhabricatorNotificationController', 'PhabricatorNotificationTestFeedStory' => 'PhabricatorFeedStory', 'PhabricatorNotificationUIExample' => 'PhabricatorUIExample', 'PhabricatorNotificationsApplication' => 'PhabricatorApplication', + 'PhabricatorNotificationsSetting' => 'PhabricatorInternalSetting', + 'PhabricatorNotificationsSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorNuanceApplication' => 'PhabricatorApplication', 'PhabricatorOAuth1AuthProvider' => 'PhabricatorOAuthAuthProvider', 'PhabricatorOAuth1SecretTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', @@ -8427,6 +8736,7 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', 'PhabricatorConduitResultInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', 'PhabricatorNgramsInterface', ), 'PhabricatorOwnersPackageAuditingTransaction' => 'PhabricatorOwnersPackageTransactionType', @@ -8436,6 +8746,7 @@ phutil_register_library_map(array( 'PhabricatorOwnersPackageDescriptionTransaction' => 'PhabricatorOwnersPackageTransactionType', 'PhabricatorOwnersPackageDominionTransaction' => 'PhabricatorOwnersPackageTransactionType', 'PhabricatorOwnersPackageEditEngine' => 'PhabricatorEditEngine', + 'PhabricatorOwnersPackageFerretEngine' => 'PhabricatorFerretEngine', 'PhabricatorOwnersPackageFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorOwnersPackageFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorOwnersPackageNameNgrams' => 'PhabricatorSearchNgrams', @@ -8662,10 +8973,12 @@ phutil_register_library_map(array( 'PhabricatorPeopleProfileManageController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileMenuEngine' => 'PhabricatorProfileMenuEngine', 'PhabricatorPeopleProfilePictureController' => 'PhabricatorPeopleProfileController', + 'PhabricatorPeopleProfileRevisionsController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileTasksController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileViewController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPeopleRenameController' => 'PhabricatorPeopleController', + 'PhabricatorPeopleRevisionsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorPeopleSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorPeopleTasksProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorPeopleTestDataGenerator' => 'PhabricatorTestDataGenerator', @@ -8811,6 +9124,7 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldInterface', 'PhabricatorDestructibleInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', 'PhabricatorConduitResultInterface', 'PhabricatorColumnProxyInterface', ), @@ -8824,8 +9138,10 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardManageController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController', + 'PhabricatorProjectBuiltinsExample' => 'PhabricatorUIExample', 'PhabricatorProjectCardView' => 'AphrontTagView', - 'PhabricatorProjectColorsConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorProjectColorTransaction' => 'PhabricatorProjectTransactionType', + 'PhabricatorProjectColorsConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorProjectColumn' => array( 'PhabricatorProjectDAO', 'PhabricatorApplicationTransactionInterface', @@ -8868,18 +9184,24 @@ phutil_register_library_map(array( 'PhabricatorProjectEditController' => 'PhabricatorProjectController', 'PhabricatorProjectEditEngine' => 'PhabricatorEditEngine', 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', + 'PhabricatorProjectFerretEngine' => 'PhabricatorFerretEngine', + 'PhabricatorProjectFilterTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorProjectHeraldAction' => 'HeraldAction', 'PhabricatorProjectHeraldAdapter' => 'HeraldAdapter', 'PhabricatorProjectHeraldFieldGroup' => 'HeraldFieldGroup', 'PhabricatorProjectHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'PhabricatorProjectIconSet' => 'PhabricatorIconSet', - 'PhabricatorProjectIconsConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorProjectIconTransaction' => 'PhabricatorProjectTransactionType', + 'PhabricatorProjectIconsConfigType' => 'PhabricatorJSONConfigType', + 'PhabricatorProjectImageTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectListView' => 'AphrontView', 'PhabricatorProjectLockController' => 'PhabricatorProjectController', + 'PhabricatorProjectLockTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectLogicalAncestorDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalDatasource' => 'PhabricatorTypeaheadCompositeDatasource', + 'PhabricatorProjectLogicalOnlyDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorProjectLogicalOrNotDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalViewerDatasource' => 'PhabricatorTypeaheadDatasource', @@ -8895,13 +9217,17 @@ phutil_register_library_map(array( 'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController', 'PhabricatorProjectMembersViewController' => 'PhabricatorProjectController', 'PhabricatorProjectMenuItemController' => 'PhabricatorProjectController', + 'PhabricatorProjectMilestoneTransaction' => 'PhabricatorProjectTypeTransaction', 'PhabricatorProjectMoveController' => 'PhabricatorProjectController', 'PhabricatorProjectNameContextFreeGrammar' => 'PhutilContextFreeGrammar', + 'PhabricatorProjectNameTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorProjectObjectHasProjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver', + 'PhabricatorProjectParentTransaction' => 'PhabricatorProjectTypeTransaction', + 'PhabricatorProjectPictureProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorProjectPointsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController', 'PhabricatorProjectProfileMenuEngine' => 'PhabricatorProfileMenuEngine', @@ -8917,18 +9243,23 @@ phutil_register_library_map(array( 'PhabricatorProjectSilenceController' => 'PhabricatorProjectController', 'PhabricatorProjectSilencedEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectSlug' => 'PhabricatorProjectDAO', + 'PhabricatorProjectSlugsTransaction' => 'PhabricatorProjectTransactionType', + 'PhabricatorProjectSortTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectStandardCustomField' => array( 'PhabricatorProjectCustomField', 'PhabricatorStandardCustomFieldInterface', ), 'PhabricatorProjectStatus' => 'Phobject', + 'PhabricatorProjectStatusTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectSubprojectWarningController' => 'PhabricatorProjectController', 'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController', 'PhabricatorProjectSubprojectsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator', - 'PhabricatorProjectTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorProjectTransaction' => 'PhabricatorModularTransaction', 'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorProjectTransactionType' => 'PhabricatorModularTransactionType', + 'PhabricatorProjectTypeTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener', 'PhabricatorProjectUpdateController' => 'PhabricatorProjectController', 'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', @@ -8937,7 +9268,9 @@ phutil_register_library_map(array( 'PhabricatorProjectWatchController' => 'PhabricatorProjectController', 'PhabricatorProjectWatcherListView' => 'PhabricatorProjectUserListView', 'PhabricatorProjectWorkboardBackgroundColor' => 'Phobject', + 'PhabricatorProjectWorkboardBackgroundTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectWorkboardProfileMenuItem' => 'PhabricatorProfileMenuItem', + 'PhabricatorProjectWorkboardTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectsAncestorsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'PhabricatorProjectsCurtainExtension' => 'PHUICurtainExtension', 'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension', @@ -8963,9 +9296,11 @@ phutil_register_library_map(array( 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController', + 'PhabricatorRegexListConfigType' => 'PhabricatorTextListConfigType', 'PhabricatorRegistrationProfile' => 'Phobject', 'PhabricatorReleephApplication' => 'PhabricatorApplication', 'PhabricatorReleephApplicationConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorRemarkupCachePurger' => 'PhabricatorCachePurger', 'PhabricatorRemarkupControl' => 'AphrontFormTextAreaControl', 'PhabricatorRemarkupCowsayBlockInterpreter' => 'PhutilRemarkupBlockInterpreter', 'PhabricatorRemarkupCustomBlockRule' => 'PhutilRemarkupBlockRule', @@ -8981,10 +9316,12 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface', 'PhabricatorMarkupInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorDestructibleCodexInterface', 'PhabricatorProjectInterface', 'PhabricatorSpacesInterface', 'PhabricatorConduitResultInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'PhabricatorRepositoryAuditRequest' => array( 'PhabricatorRepositoryDAO', @@ -9005,6 +9342,7 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', 'PhabricatorConduitResultInterface', 'PhabricatorDraftInterface', ), @@ -9023,9 +9361,11 @@ phutil_register_library_map(array( 'PhabricatorRepositoryCommitTestCase' => 'PhabricatorTestCase', 'PhabricatorRepositoryConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO', + 'PhabricatorRepositoryDestructibleCodex' => 'PhabricatorDestructibleCodex', 'PhabricatorRepositoryDiscoveryEngine' => 'PhabricatorRepositoryEngine', 'PhabricatorRepositoryEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorRepositoryEngine' => 'Phobject', + 'PhabricatorRepositoryFerretEngine' => 'PhabricatorFerretEngine', 'PhabricatorRepositoryFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', @@ -9124,7 +9464,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoryURITransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorRepositoryURITransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO', - 'PhabricatorRepositoryVersion' => 'Phobject', 'PhabricatorRepositoryWorkingCopyVersion' => 'PhabricatorRepositoryDAO', 'PhabricatorRequestExceptionHandler' => 'AphrontRequestExceptionHandler', 'PhabricatorResourceSite' => 'PhabricatorSite', @@ -9170,6 +9509,7 @@ phutil_register_library_map(array( 'PhabricatorSearchDatasourceField' => 'PhabricatorSearchTokenizerField', 'PhabricatorSearchDateControlField' => 'PhabricatorSearchField', 'PhabricatorSearchDateField' => 'PhabricatorSearchField', + 'PhabricatorSearchDefaultController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchDeleteController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO', @@ -9216,6 +9556,7 @@ phutil_register_library_map(array( 'PhabricatorSelectSetting' => 'PhabricatorSetting', 'PhabricatorSendGridConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorSessionsSettingsPanel' => 'PhabricatorSettingsPanel', + 'PhabricatorSetConfigType' => 'PhabricatorTextConfigType', 'PhabricatorSetting' => 'Phobject', 'PhabricatorSettingsAccountPanelGroup' => 'PhabricatorSettingsPanelGroup', 'PhabricatorSettingsAddEmailAction' => 'PhabricatorSystemAction', @@ -9248,10 +9589,12 @@ phutil_register_library_map(array( 'PhabricatorSlowvoteApplication' => 'PhabricatorApplication', 'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO', 'PhabricatorSlowvoteCloseController' => 'PhabricatorSlowvoteController', + 'PhabricatorSlowvoteCloseTransaction' => 'PhabricatorSlowvoteTransactionType', 'PhabricatorSlowvoteCommentController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvoteController' => 'PhabricatorController', 'PhabricatorSlowvoteDAO' => 'PhabricatorLiskDAO', 'PhabricatorSlowvoteDefaultViewCapability' => 'PhabricatorPolicyCapability', + 'PhabricatorSlowvoteDescriptionTransaction' => 'PhabricatorSlowvoteTransactionType', 'PhabricatorSlowvoteEditController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvoteEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorSlowvoteListController' => 'PhabricatorSlowvoteController', @@ -9271,16 +9614,19 @@ phutil_register_library_map(array( 'PhabricatorSlowvotePollController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvotePollPHIDType' => 'PhabricatorPHIDType', 'PhabricatorSlowvoteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorSlowvoteQuestionTransaction' => 'PhabricatorSlowvoteTransactionType', 'PhabricatorSlowvoteReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', + 'PhabricatorSlowvoteResponsesTransaction' => 'PhabricatorSlowvoteTransactionType', 'PhabricatorSlowvoteSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorSlowvoteSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhabricatorSlowvoteTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorSlowvoteShuffleTransaction' => 'PhabricatorSlowvoteTransactionType', + 'PhabricatorSlowvoteTransaction' => 'PhabricatorModularTransaction', 'PhabricatorSlowvoteTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorSlowvoteTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorSlowvoteTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorSlowvoteVoteController' => 'PhabricatorSlowvoteController', 'PhabricatorSlug' => 'Phobject', 'PhabricatorSlugTestCase' => 'PhabricatorTestCase', - 'PhabricatorSortTableUIExample' => 'PhabricatorUIExample', 'PhabricatorSourceCodeView' => 'AphrontView', 'PhabricatorSpaceEditField' => 'PhabricatorEditField', 'PhabricatorSpacesApplication' => 'PhabricatorApplication', @@ -9299,13 +9645,18 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', ), + 'PhabricatorSpacesNamespaceArchiveTransaction' => 'PhabricatorSpacesNamespaceTransactionType', 'PhabricatorSpacesNamespaceDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorSpacesNamespaceDefaultTransaction' => 'PhabricatorSpacesNamespaceTransactionType', + 'PhabricatorSpacesNamespaceDescriptionTransaction' => 'PhabricatorSpacesNamespaceTransactionType', 'PhabricatorSpacesNamespaceEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorSpacesNamespaceNameTransaction' => 'PhabricatorSpacesNamespaceTransactionType', 'PhabricatorSpacesNamespacePHIDType' => 'PhabricatorPHIDType', 'PhabricatorSpacesNamespaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorSpacesNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhabricatorSpacesNamespaceTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorSpacesNamespaceTransaction' => 'PhabricatorModularTransaction', 'PhabricatorSpacesNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorSpacesNamespaceTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorSpacesNoAccessController' => 'PhabricatorSpacesController', 'PhabricatorSpacesRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorSpacesSchemaSpec' => 'PhabricatorConfigSchemaSpec', @@ -9339,6 +9690,7 @@ phutil_register_library_map(array( 'PhabricatorStorageFixtureScopeGuard' => 'Phobject', 'PhabricatorStorageManagementAPI' => 'Phobject', 'PhabricatorStorageManagementAdjustWorkflow' => 'PhabricatorStorageManagementWorkflow', + 'PhabricatorStorageManagementAnalyzeWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementDatabasesWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementDestroyWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementDumpWorkflow' => 'PhabricatorStorageManagementWorkflow', @@ -9354,6 +9706,8 @@ phutil_register_library_map(array( 'PhabricatorStoragePatch' => 'Phobject', 'PhabricatorStorageSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorStorageSetupCheck' => 'PhabricatorSetupCheck', + 'PhabricatorStringConfigType' => 'PhabricatorTextConfigType', + 'PhabricatorStringListConfigType' => 'PhabricatorTextListConfigType', 'PhabricatorStringListEditField' => 'PhabricatorEditField', 'PhabricatorStringSetting' => 'PhabricatorSetting', 'PhabricatorSubmitEditField' => 'PhabricatorEditField', @@ -9413,7 +9767,9 @@ phutil_register_library_map(array( 'PhabricatorTestStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorTestWorker' => 'PhabricatorWorker', 'PhabricatorTextAreaEditField' => 'PhabricatorEditField', + 'PhabricatorTextConfigType' => 'PhabricatorConfigType', 'PhabricatorTextEditField' => 'PhabricatorEditField', + 'PhabricatorTextListConfigType' => 'PhabricatorTextConfigType', 'PhabricatorTime' => 'Phobject', 'PhabricatorTimeFormatSetting' => 'PhabricatorSelectSetting', 'PhabricatorTimeGuard' => 'Phobject', @@ -9456,7 +9812,6 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface', 'PhabricatorConduitResultInterface', ), - 'PhabricatorTooltipUIExample' => 'PhabricatorUIExample', 'PhabricatorTransactionChange' => 'Phobject', 'PhabricatorTransactionRemarkupChange' => 'PhabricatorTransactionChange', 'PhabricatorTransactions' => 'Phobject', @@ -9505,11 +9860,13 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', 'PhabricatorConduitResultInterface', ), 'PhabricatorUserBadgesCacheType' => 'PhabricatorUserCacheType', 'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField', 'PhabricatorUserCache' => 'PhabricatorUserDAO', + 'PhabricatorUserCachePurger' => 'PhabricatorCachePurger', 'PhabricatorUserCacheType' => 'Phobject', 'PhabricatorUserCardView' => 'AphrontTagView', 'PhabricatorUserConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -9526,6 +9883,7 @@ phutil_register_library_map(array( 'PhabricatorUserEditorTestCase' => 'PhabricatorTestCase', 'PhabricatorUserEmail' => 'PhabricatorUserDAO', 'PhabricatorUserEmailTestCase' => 'PhabricatorTestCase', + 'PhabricatorUserFerretEngine' => 'PhabricatorFerretEngine', 'PhabricatorUserFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorUserIconField' => 'PhabricatorUserCustomField', 'PhabricatorUserLog' => array( @@ -9571,6 +9929,7 @@ phutil_register_library_map(array( 'PhabricatorWebContentSource' => 'PhabricatorContentSource', 'PhabricatorWebServerSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorWeekStartDaySetting' => 'PhabricatorSelectSetting', + 'PhabricatorWildConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorWordPressAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorWorker' => 'Phobject', 'PhabricatorWorkerActiveTask' => 'PhabricatorWorkerTask', @@ -9664,30 +10023,42 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorConduitResultInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'PhameBlog404Controller' => 'PhameLiveController', 'PhameBlogArchiveController' => 'PhameBlogController', 'PhameBlogController' => 'PhameController', 'PhameBlogCreateCapability' => 'PhabricatorPolicyCapability', 'PhameBlogDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhameBlogDescriptionTransaction' => 'PhameBlogTransactionType', 'PhameBlogEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhameBlogEditController' => 'PhameBlogController', 'PhameBlogEditEngine' => 'PhabricatorEditEngine', 'PhameBlogEditor' => 'PhabricatorApplicationTransactionEditor', 'PhameBlogFeedController' => 'PhameBlogController', + 'PhameBlogFerretEngine' => 'PhabricatorFerretEngine', + 'PhameBlogFullDomainTransaction' => 'PhameBlogTransactionType', 'PhameBlogFulltextEngine' => 'PhabricatorFulltextEngine', + 'PhameBlogHeaderImageTransaction' => 'PhameBlogTransactionType', 'PhameBlogHeaderPictureController' => 'PhameBlogController', 'PhameBlogListController' => 'PhameBlogController', 'PhameBlogListView' => 'AphrontTagView', 'PhameBlogManageController' => 'PhameBlogController', + 'PhameBlogNameTransaction' => 'PhameBlogTransactionType', + 'PhameBlogParentDomainTransaction' => 'PhameBlogTransactionType', + 'PhameBlogParentSiteTransaction' => 'PhameBlogTransactionType', + 'PhameBlogProfileImageTransaction' => 'PhameBlogTransactionType', 'PhameBlogProfilePictureController' => 'PhameBlogController', 'PhameBlogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhameBlogReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhameBlogSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhameBlogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhameBlogSite' => 'PhameSite', - 'PhameBlogTransaction' => 'PhabricatorApplicationTransaction', + 'PhameBlogStatusTransaction' => 'PhameBlogTransactionType', + 'PhameBlogSubtitleTransaction' => 'PhameBlogTransactionType', + 'PhameBlogTransaction' => 'PhabricatorModularTransaction', 'PhameBlogTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhameBlogTransactionType' => 'PhabricatorModularTransactionType', 'PhameBlogViewController' => 'PhameLiveController', 'PhameConstants' => 'Phobject', 'PhameController' => 'PhabricatorController', @@ -9709,14 +10080,19 @@ phutil_register_library_map(array( 'PhabricatorTokenReceiverInterface', 'PhabricatorConduitResultInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'PhamePostArchiveController' => 'PhamePostController', + 'PhamePostBlogTransaction' => 'PhamePostTransactionType', + 'PhamePostBodyTransaction' => 'PhamePostTransactionType', 'PhamePostController' => 'PhameController', 'PhamePostEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhamePostEditController' => 'PhamePostController', 'PhamePostEditEngine' => 'PhabricatorEditEngine', 'PhamePostEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhamePostFerretEngine' => 'PhabricatorFerretEngine', 'PhamePostFulltextEngine' => 'PhabricatorFulltextEngine', + 'PhamePostHeaderImageTransaction' => 'PhamePostTransactionType', 'PhamePostHeaderPictureController' => 'PhamePostController', 'PhamePostHistoryController' => 'PhamePostController', 'PhamePostListController' => 'PhamePostController', @@ -9729,10 +10105,14 @@ phutil_register_library_map(array( 'PhamePostReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhamePostSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhamePostSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhamePostTransaction' => 'PhabricatorApplicationTransaction', + 'PhamePostSubtitleTransaction' => 'PhamePostTransactionType', + 'PhamePostTitleTransaction' => 'PhamePostTransactionType', + 'PhamePostTransaction' => 'PhabricatorModularTransaction', 'PhamePostTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhamePostTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhamePostTransactionType' => 'PhabricatorModularTransactionType', 'PhamePostViewController' => 'PhameLiveController', + 'PhamePostVisibilityTransaction' => 'PhamePostTransactionType', 'PhameSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhameSite' => 'PhabricatorSite', 'PhluxController' => 'PhabricatorController', @@ -9760,8 +10140,14 @@ phutil_register_library_map(array( 'PhabricatorMarkupInterface', 'PhabricatorPolicyInterface', ), + 'PholioImageDescriptionTransaction' => 'PholioImageTransactionType', + 'PholioImageFileTransaction' => 'PholioImageTransactionType', + 'PholioImageNameTransaction' => 'PholioImageTransactionType', 'PholioImagePHIDType' => 'PhabricatorPHIDType', 'PholioImageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PholioImageReplaceTransaction' => 'PholioImageTransactionType', + 'PholioImageSequenceTransaction' => 'PholioImageTransactionType', + 'PholioImageTransactionType' => 'PholioTransactionType', 'PholioImageUploadController' => 'PholioController', 'PholioInlineController' => 'PholioController', 'PholioInlineListController' => 'PholioController', @@ -9778,36 +10164,44 @@ phutil_register_library_map(array( 'PhabricatorSpacesInterface', 'PhabricatorMentionableInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'PholioMockArchiveController' => 'PholioController', 'PholioMockAuthorHeraldField' => 'PholioMockHeraldField', 'PholioMockCommentController' => 'PholioController', 'PholioMockDescriptionHeraldField' => 'PholioMockHeraldField', + 'PholioMockDescriptionTransaction' => 'PholioMockTransactionType', 'PholioMockEditController' => 'PholioController', 'PholioMockEditor' => 'PhabricatorApplicationTransactionEditor', 'PholioMockEmbedView' => 'AphrontView', + 'PholioMockFerretEngine' => 'PhabricatorFerretEngine', 'PholioMockFulltextEngine' => 'PhabricatorFulltextEngine', 'PholioMockHasTaskEdgeType' => 'PhabricatorEdgeType', 'PholioMockHasTaskRelationship' => 'PholioMockRelationship', 'PholioMockHeraldField' => 'HeraldField', 'PholioMockHeraldFieldGroup' => 'HeraldFieldGroup', 'PholioMockImagesView' => 'AphrontView', + 'PholioMockInlineTransaction' => 'PholioMockTransactionType', 'PholioMockListController' => 'PholioController', 'PholioMockMailReceiver' => 'PhabricatorObjectMailReceiver', 'PholioMockNameHeraldField' => 'PholioMockHeraldField', + 'PholioMockNameTransaction' => 'PholioMockTransactionType', 'PholioMockPHIDType' => 'PhabricatorPHIDType', 'PholioMockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PholioMockRelationship' => 'PhabricatorObjectRelationship', 'PholioMockRelationshipSource' => 'PhabricatorObjectRelationshipSource', 'PholioMockSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PholioMockStatusTransaction' => 'PholioMockTransactionType', 'PholioMockThumbGridView' => 'AphrontView', + 'PholioMockTransactionType' => 'PholioTransactionType', 'PholioMockViewController' => 'PholioController', 'PholioRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PholioReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PholioSchemaSpec' => 'PhabricatorConfigSchemaSpec', - 'PholioTransaction' => 'PhabricatorApplicationTransaction', + 'PholioTransaction' => 'PhabricatorModularTransaction', 'PholioTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PholioTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PholioTransactionType' => 'PhabricatorModularTransactionType', 'PholioTransactionView' => 'PhabricatorApplicationTransactionView', 'PholioUploadedImageView' => 'AphrontView', 'PhortuneAccount' => array( @@ -10051,21 +10445,29 @@ phutil_register_library_map(array( 'PhabricatorTokenReceiverInterface', 'PhabricatorDestructibleInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', 'PhabricatorProjectInterface', 'PhabricatorApplicationTransactionInterface', ), 'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField', + 'PhrictionDocumentContentTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentController' => 'PhrictionController', + 'PhrictionDocumentDeleteTransaction' => 'PhrictionDocumentTransactionType', + 'PhrictionDocumentFerretEngine' => 'PhabricatorFerretEngine', 'PhrictionDocumentFulltextEngine' => 'PhabricatorFulltextEngine', 'PhrictionDocumentHeraldAdapter' => 'HeraldAdapter', 'PhrictionDocumentHeraldField' => 'HeraldField', 'PhrictionDocumentHeraldFieldGroup' => 'HeraldFieldGroup', + 'PhrictionDocumentMoveAwayTransaction' => 'PhrictionDocumentTransactionType', + 'PhrictionDocumentMoveToTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentPHIDType' => 'PhabricatorPHIDType', 'PhrictionDocumentPathHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhrictionDocumentStatus' => 'PhrictionConstants', 'PhrictionDocumentTitleHeraldField' => 'PhrictionDocumentHeraldField', + 'PhrictionDocumentTitleTransaction' => 'PhrictionDocumentTransactionType', + 'PhrictionDocumentTransactionType' => 'PhabricatorModularTransactionType', 'PhrictionEditConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionEditController' => 'PhrictionController', 'PhrictionHistoryConduitAPIMethod' => 'PhrictionConduitAPIMethod', @@ -10079,7 +10481,7 @@ phutil_register_library_map(array( 'PhrictionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhrictionSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhrictionSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhrictionTransaction' => 'PhabricatorApplicationTransaction', + 'PhrictionTransaction' => 'PhabricatorModularTransaction', 'PhrictionTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhrictionTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhrictionTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -10095,18 +10497,22 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', ), 'PonderAnswerCommentController' => 'PonderController', + 'PonderAnswerContentTransaction' => 'PonderAnswerTransactionType', 'PonderAnswerEditController' => 'PonderController', 'PonderAnswerEditor' => 'PonderEditor', 'PonderAnswerHistoryController' => 'PonderController', 'PonderAnswerMailReceiver' => 'PhabricatorObjectMailReceiver', 'PonderAnswerPHIDType' => 'PhabricatorPHIDType', 'PonderAnswerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PonderAnswerQuestionIDTransaction' => 'PonderAnswerTransactionType', 'PonderAnswerReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PonderAnswerSaveController' => 'PonderController', 'PonderAnswerStatus' => 'PonderConstants', - 'PonderAnswerTransaction' => 'PhabricatorApplicationTransaction', + 'PonderAnswerStatusTransaction' => 'PonderAnswerTransactionType', + 'PonderAnswerTransaction' => 'PhabricatorModularTransaction', 'PonderAnswerTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PonderAnswerTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PonderAnswerTransactionType' => 'PhabricatorModularTransactionType', 'PonderAnswerView' => 'AphrontTagView', 'PonderConstants' => 'Phobject', 'PonderController' => 'PhabricatorController', @@ -10128,9 +10534,13 @@ phutil_register_library_map(array( 'PhabricatorSpacesInterface', 'PhabricatorFulltextInterface', ), + 'PonderQuestionAnswerTransaction' => 'PonderQuestionTransactionType', + 'PonderQuestionAnswerWikiTransaction' => 'PonderQuestionTransactionType', 'PonderQuestionCommentController' => 'PonderController', + 'PonderQuestionContentTransaction' => 'PonderQuestionTransactionType', 'PonderQuestionCreateMailReceiver' => 'PhabricatorMailReceiver', 'PonderQuestionEditController' => 'PonderController', + 'PonderQuestionEditEngine' => 'PhabricatorEditEngine', 'PonderQuestionEditor' => 'PonderEditor', 'PonderQuestionFulltextEngine' => 'PhabricatorFulltextEngine', 'PonderQuestionHistoryController' => 'PonderController', @@ -10142,9 +10552,12 @@ phutil_register_library_map(array( 'PonderQuestionSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PonderQuestionStatus' => 'PonderConstants', 'PonderQuestionStatusController' => 'PonderController', - 'PonderQuestionTransaction' => 'PhabricatorApplicationTransaction', + 'PonderQuestionStatusTransaction' => 'PonderQuestionTransactionType', + 'PonderQuestionTitleTransaction' => 'PonderQuestionTransactionType', + 'PonderQuestionTransaction' => 'PhabricatorModularTransaction', 'PonderQuestionTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PonderQuestionTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PonderQuestionTransactionType' => 'PhabricatorModularTransactionType', 'PonderQuestionViewController' => 'PonderController', 'PonderRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PonderSchemaSpec' => 'PhabricatorConfigSchemaSpec', @@ -10282,6 +10695,7 @@ phutil_register_library_map(array( 'TokenGiveConduitAPIMethod' => 'TokenConduitAPIMethod', 'TokenGivenConduitAPIMethod' => 'TokenConduitAPIMethod', 'TokenQueryConduitAPIMethod' => 'TokenConduitAPIMethod', + 'TransactionSearchConduitAPIMethod' => 'ConduitAPIMethod', 'UserConduitAPIMethod' => 'ConduitAPIMethod', 'UserDisableConduitAPIMethod' => 'UserConduitAPIMethod', 'UserEnableConduitAPIMethod' => 'UserConduitAPIMethod', diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php index b89feaebb5..c0cd259992 100644 --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -270,7 +270,10 @@ abstract class AphrontApplicationConfiguration extends Phobject { } } catch (Exception $ex) { $original_exception = $ex; - $response = $this->handleException($ex); + $response = $this->handleThrowable($ex); + } catch (Throwable $ex) { + $original_exception = $ex; + $response = $this->handleThrowable($ex); } try { @@ -663,24 +666,24 @@ abstract class AphrontApplicationConfiguration extends Phobject { * This method delegates exception handling to available subclasses of * @{class:AphrontRequestExceptionHandler}. * - * @param Exception Exception which needs to be handled. + * @param Throwable Exception which needs to be handled. * @return wild Response or response producer, or null if no available * handler can produce a response. * @task exception */ - private function handleException(Exception $ex) { + private function handleThrowable($throwable) { $handlers = AphrontRequestExceptionHandler::getAllHandlers(); $request = $this->getRequest(); foreach ($handlers as $handler) { - if ($handler->canHandleRequestException($request, $ex)) { - $response = $handler->handleRequestException($request, $ex); + if ($handler->canHandleRequestThrowable($request, $throwable)) { + $response = $handler->handleRequestThrowable($request, $throwable); $this->validateErrorHandlerResponse($handler, $response); return $response; } } - throw $ex; + throw $throwable; } private static function newSelfCheckResponse() { diff --git a/src/aphront/handler/AphrontRequestExceptionHandler.php b/src/aphront/handler/AphrontRequestExceptionHandler.php index 694071e1ba..e626f53cc6 100644 --- a/src/aphront/handler/AphrontRequestExceptionHandler.php +++ b/src/aphront/handler/AphrontRequestExceptionHandler.php @@ -12,19 +12,13 @@ abstract class AphrontRequestExceptionHandler extends Phobject { abstract public function getRequestExceptionHandlerPriority(); - public function shouldLogException( + abstract public function canHandleRequestThrowable( AphrontRequest $request, - Exception $ex) { - return null; - } + $throwable); - abstract public function canHandleRequestException( + abstract public function handleRequestThrowable( AphrontRequest $request, - Exception $ex); - - abstract public function handleRequestException( - AphrontRequest $request, - Exception $ex); + $throwable); final public static function getAllHandlers() { return id(new PhutilClassMapQuery()) diff --git a/src/aphront/handler/PhabricatorAjaxRequestExceptionHandler.php b/src/aphront/handler/PhabricatorAjaxRequestExceptionHandler.php index 06023e02de..d519c83fb5 100644 --- a/src/aphront/handler/PhabricatorAjaxRequestExceptionHandler.php +++ b/src/aphront/handler/PhabricatorAjaxRequestExceptionHandler.php @@ -11,27 +11,28 @@ final class PhabricatorAjaxRequestExceptionHandler return pht('Responds to requests made by AJAX clients.'); } - public function canHandleRequestException( + public function canHandleRequestThrowable( AphrontRequest $request, - Exception $ex) { + $throwable) { // For non-workflow requests, return a Ajax response. return ($request->isAjax() && !$request->isWorkflow()); } - public function handleRequestException( + public function handleRequestThrowable( AphrontRequest $request, - Exception $ex) { + $throwable) { // Log these; they don't get shown on the client and can be difficult // to debug. - phlog($ex); + phlog($throwable); $response = new AphrontAjaxResponse(); $response->setError( array( - 'code' => get_class($ex), - 'info' => $ex->getMessage(), + 'code' => get_class($throwable), + 'info' => $throwable->getMessage(), )); + return $response; } diff --git a/src/aphront/handler/PhabricatorConduitRequestExceptionHandler.php b/src/aphront/handler/PhabricatorConduitRequestExceptionHandler.php index 367f607f83..db8a5d5ecc 100644 --- a/src/aphront/handler/PhabricatorConduitRequestExceptionHandler.php +++ b/src/aphront/handler/PhabricatorConduitRequestExceptionHandler.php @@ -11,19 +11,19 @@ final class PhabricatorConduitRequestExceptionHandler return pht('Responds to requests made by Conduit clients.'); } - public function canHandleRequestException( + public function canHandleRequestThrowable( AphrontRequest $request, - Exception $ex) { + $throwable) { return $request->isConduit(); } - public function handleRequestException( + public function handleRequestThrowable( AphrontRequest $request, - Exception $ex) { + $throwable) { $response = id(new ConduitAPIResponse()) - ->setErrorCode(get_class($ex)) - ->setErrorInfo($ex->getMessage()); + ->setErrorCode(get_class($throwable)) + ->setErrorInfo($throwable->getMessage()); return id(new AphrontJSONResponse()) ->setAddJSONShield(false) diff --git a/src/aphront/handler/PhabricatorDefaultRequestExceptionHandler.php b/src/aphront/handler/PhabricatorDefaultRequestExceptionHandler.php index d330cd5f2c..b13100b05d 100644 --- a/src/aphront/handler/PhabricatorDefaultRequestExceptionHandler.php +++ b/src/aphront/handler/PhabricatorDefaultRequestExceptionHandler.php @@ -11,9 +11,9 @@ final class PhabricatorDefaultRequestExceptionHandler return pht('Handles all other exceptions.'); } - public function canHandleRequestException( + public function canHandleRequestThrowable( AphrontRequest $request, - Exception $ex) { + $throwable) { if (!$this->isPhabricatorSite($request)) { return false; @@ -22,9 +22,9 @@ final class PhabricatorDefaultRequestExceptionHandler return true; } - public function handleRequestException( + public function handleRequestThrowable( AphrontRequest $request, - Exception $ex) { + $throwable) { $viewer = $this->getViewer($request); @@ -33,18 +33,18 @@ final class PhabricatorDefaultRequestExceptionHandler // the internet. These include requests with bad CSRF tokens and // questionable "Host" headers. $should_log = true; - if ($ex instanceof AphrontMalformedRequestException) { - $should_log = !$ex->getIsUnlogged(); + if ($throwable instanceof AphrontMalformedRequestException) { + $should_log = !$throwable->getIsUnlogged(); } if ($should_log) { - phlog($ex); + phlog($throwable); } - $class = get_class($ex); - $message = $ex->getMessage(); + $class = get_class($throwable); + $message = $throwable->getMessage(); - if ($ex instanceof AphrontSchemaQueryException) { + if ($throwable instanceof AphrontSchemaQueryException) { $message .= "\n\n".pht( "NOTE: This usually indicates that the MySQL schema has not been ". "properly upgraded. Run '%s' to ensure your schema is up to date.", @@ -54,7 +54,7 @@ final class PhabricatorDefaultRequestExceptionHandler if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) { $trace = id(new AphrontStackTraceView()) ->setUser($viewer) - ->setTrace($ex->getTrace()); + ->setTrace($throwable->getTrace()); } else { $trace = null; } diff --git a/src/aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php b/src/aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php index ff4ace2e4b..f8d522711f 100644 --- a/src/aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php +++ b/src/aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php @@ -13,26 +13,26 @@ final class PhabricatorHighSecurityRequestExceptionHandler 'to present MFA credentials to take an action.'); } - public function canHandleRequestException( + public function canHandleRequestThrowable( AphrontRequest $request, - Exception $ex) { + $throwable) { if (!$this->isPhabricatorSite($request)) { return false; } - return ($ex instanceof PhabricatorAuthHighSecurityRequiredException); + return ($throwable instanceof PhabricatorAuthHighSecurityRequiredException); } - public function handleRequestException( + public function handleRequestThrowable( AphrontRequest $request, - Exception $ex) { + $throwable) { $viewer = $this->getViewer($request); $form = id(new PhabricatorAuthSessionEngine())->renderHighSecurityForm( - $ex->getFactors(), - $ex->getFactorValidationResults(), + $throwable->getFactors(), + $throwable->getFactorValidationResults(), $viewer, $request); @@ -61,7 +61,7 @@ final class PhabricatorHighSecurityRequestExceptionHandler 'period of time. When you are finished taking sensitive '. 'actions, you should leave high security.')) ->setSubmitURI($request->getPath()) - ->addCancelButton($ex->getCancelURI()) + ->addCancelButton($throwable->getCancelURI()) ->addSubmitButton(pht('Enter High Security')); $request_parameters = $request->getPassthroughRequestParameters( diff --git a/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php b/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php index 1c84e90fd1..cb1dea3533 100644 --- a/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php +++ b/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php @@ -13,20 +13,20 @@ final class PhabricatorPolicyRequestExceptionHandler 'do something they do not have permission to do.'); } - public function canHandleRequestException( + public function canHandleRequestThrowable( AphrontRequest $request, - Exception $ex) { + $throwable) { if (!$this->isPhabricatorSite($request)) { return false; } - return ($ex instanceof PhabricatorPolicyException); + return ($throwable instanceof PhabricatorPolicyException); } - public function handleRequestException( + public function handleRequestThrowable( AphrontRequest $request, - Exception $ex) { + $throwable) { $viewer = $this->getViewer($request); @@ -52,12 +52,12 @@ final class PhabricatorPolicyRequestExceptionHandler array( 'class' => 'aphront-policy-rejection', ), - $ex->getRejection()), + $throwable->getRejection()), ); $list = null; - if ($ex->getCapabilityName()) { - $list = $ex->getMoreInfo(); + if ($throwable->getCapabilityName()) { + $list = $throwable->getMoreInfo(); foreach ($list as $key => $item) { $list[$key] = $item; } @@ -67,12 +67,14 @@ final class PhabricatorPolicyRequestExceptionHandler array( 'class' => 'aphront-capability-details', ), - pht('Users with the "%s" capability:', $ex->getCapabilityName())); + pht( + 'Users with the "%s" capability:', + $throwable->getCapabilityName())); } $dialog = id(new AphrontDialogView()) - ->setTitle($ex->getTitle()) + ->setTitle($throwable->getTitle()) ->setClass('aphront-access-dialog') ->setUser($viewer) ->appendChild($content); diff --git a/src/aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php b/src/aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php index 3fb549c29f..78dad564b4 100644 --- a/src/aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php +++ b/src/aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php @@ -13,20 +13,20 @@ final class PhabricatorRateLimitRequestExceptionHandler 'does something too frequently.'); } - public function canHandleRequestException( + public function canHandleRequestThrowable( AphrontRequest $request, - Exception $ex) { + $throwable) { if (!$this->isPhabricatorSite($request)) { return false; } - return ($ex instanceof PhabricatorSystemActionRateLimitException); + return ($throwable instanceof PhabricatorSystemActionRateLimitException); } - public function handleRequestException( + public function handleRequestThrowable( AphrontRequest $request, - Exception $ex) { + $throwable) { $viewer = $this->getViewer($request); @@ -34,8 +34,8 @@ final class PhabricatorRateLimitRequestExceptionHandler ->setTitle(pht('Slow Down!')) ->setUser($viewer) ->setErrors(array(pht('You are being rate limited.'))) - ->appendParagraph($ex->getMessage()) - ->appendParagraph($ex->getRateExplanation()) + ->appendParagraph($throwable->getMessage()) + ->appendParagraph($throwable->getRateExplanation()) ->addCancelButton('/', pht('Okaaaaaaaaaaaaaay...')); } diff --git a/src/aphront/httpparametertype/AphrontFileHTTPParameterType.php b/src/aphront/httpparametertype/AphrontFileHTTPParameterType.php index ceff8c1ddb..c2e5f47f8f 100644 --- a/src/aphront/httpparametertype/AphrontFileHTTPParameterType.php +++ b/src/aphront/httpparametertype/AphrontFileHTTPParameterType.php @@ -7,10 +7,17 @@ final class AphrontFileHTTPParameterType return $key.'_raw'; } + private function getDefaultKey($key) { + return $key.'_default'; + } + protected function getParameterExists(AphrontRequest $request, $key) { $file_key = $this->getFileKey($key); + $default_key = $this->getDefaultKey($key); + return $request->getExists($key) || - $request->getFileExists($file_key); + $request->getFileExists($file_key) || + $request->getExists($default_key); } protected function getParameterValue(AphrontRequest $request, $key) { @@ -27,8 +34,9 @@ final class AphrontFileHTTPParameterType // this code around as a fallback if the client-side JS goes awry. $file_key = $this->getFileKey($key); + $default_key = $this->getDefaultKey($key); if (!$request->getFileExists($file_key)) { - return null; + return $request->getStr($default_key); } $viewer = $this->getViewer(); diff --git a/src/applications/almanac/controller/AlmanacController.php b/src/applications/almanac/controller/AlmanacController.php index e4d9921660..d6d7188511 100644 --- a/src/applications/almanac/controller/AlmanacController.php +++ b/src/applications/almanac/controller/AlmanacController.php @@ -84,8 +84,8 @@ abstract class AlmanacController 'a', array( 'class' => ($can_edit - ? 'button grey small' - : 'button grey small disabled'), + ? 'button button-grey small' + : 'button button-grey small disabled'), 'sigil' => 'workflow', 'href' => $delete_uri, ), diff --git a/src/applications/almanac/view/AlmanacBindingTableView.php b/src/applications/almanac/view/AlmanacBindingTableView.php index 06797c912d..746d3de5c2 100644 --- a/src/applications/almanac/view/AlmanacBindingTableView.php +++ b/src/applications/almanac/view/AlmanacBindingTableView.php @@ -77,7 +77,7 @@ final class AlmanacBindingTableView extends AphrontView { phutil_tag( 'a', array( - 'class' => 'small grey button', + 'class' => 'small button button-grey', 'href' => '/almanac/binding/'.$binding->getID().'/', ), pht('Details')), diff --git a/src/applications/almanac/view/AlmanacInterfaceTableView.php b/src/applications/almanac/view/AlmanacInterfaceTableView.php index f937e34f44..1fdcd0bf35 100644 --- a/src/applications/almanac/view/AlmanacInterfaceTableView.php +++ b/src/applications/almanac/view/AlmanacInterfaceTableView.php @@ -30,9 +30,9 @@ final class AlmanacInterfaceTableView extends AphrontView { $can_edit = $this->getCanEdit(); if ($can_edit) { - $button_class = 'small grey button'; + $button_class = 'small button button-grey'; } else { - $button_class = 'small grey button disabled'; + $button_class = 'small button button-grey disabled'; } $handles = $viewer->loadHandles(mpull($interfaces, 'getNetworkPHID')); diff --git a/src/applications/audit/storage/PhabricatorAuditInlineComment.php b/src/applications/audit/storage/PhabricatorAuditInlineComment.php index b330368d1b..2534384f61 100644 --- a/src/applications/audit/storage/PhabricatorAuditInlineComment.php +++ b/src/applications/audit/storage/PhabricatorAuditInlineComment.php @@ -331,6 +331,14 @@ final class PhabricatorAuditInlineComment return $this->isGhost; } + public function getDateModified() { + return $this->proxy->getDateModified(); + } + + public function getDateCreated() { + return $this->proxy->getDateCreated(); + } + /* -( PhabricatorMarkupInterface Implementation )-------------------------- */ diff --git a/src/applications/audit/storage/PhabricatorAuditTransaction.php b/src/applications/audit/storage/PhabricatorAuditTransaction.php index d09bdb76ce..ad96edb3a4 100644 --- a/src/applications/audit/storage/PhabricatorAuditTransaction.php +++ b/src/applications/audit/storage/PhabricatorAuditTransaction.php @@ -49,6 +49,17 @@ final class PhabricatorAuditTransaction return $blocks; } + public function getActionStrength() { + $type = $this->getTransactionType(); + + switch ($type) { + case self::TYPE_COMMIT: + return 3.0; + } + + return parent::getActionStrength(); + } + public function getRequiredHandlePHIDs() { $phids = parent::getRequiredHandlePHIDs(); diff --git a/src/applications/audit/view/PhabricatorAuditListView.php b/src/applications/audit/view/PhabricatorAuditListView.php index e249406724..f9399e66ab 100644 --- a/src/applications/audit/view/PhabricatorAuditListView.php +++ b/src/applications/audit/view/PhabricatorAuditListView.php @@ -2,7 +2,7 @@ final class PhabricatorAuditListView extends AphrontView { - private $commits; + private $commits = array(); private $header; private $showDrafts; private $noDataString; diff --git a/src/applications/auth/controller/PhabricatorAuthFinishController.php b/src/applications/auth/controller/PhabricatorAuthFinishController.php index 387679b44e..4b775a2e4b 100644 --- a/src/applications/auth/controller/PhabricatorAuthFinishController.php +++ b/src/applications/auth/controller/PhabricatorAuthFinishController.php @@ -54,7 +54,7 @@ final class PhabricatorAuthFinishController ->addHiddenInput(AphrontRequest::TYPE_HISEC, true) ->appendParagraph( pht( - 'Welcome, %s. To complete the login process, provide your '. + 'Welcome, %s. To complete the process of logging in, provide your '. 'multi-factor credentials.', phutil_tag('strong', array(), $viewer->getUsername()))) ->appendChild($form->buildLayoutView()) diff --git a/src/applications/auth/controller/PhabricatorAuthLoginController.php b/src/applications/auth/controller/PhabricatorAuthLoginController.php index 3264e61216..39b6318481 100644 --- a/src/applications/auth/controller/PhabricatorAuthLoginController.php +++ b/src/applications/auth/controller/PhabricatorAuthLoginController.php @@ -92,8 +92,8 @@ final class PhabricatorAuthLoginController } else { return $this->renderError( pht( - 'The external account ("%s") you just used to login is already '. - 'associated with another Phabricator user account. Login to the '. + 'The external account ("%s") you just used to log in is already '. + 'associated with another Phabricator user account. Log in to the '. 'other Phabricator account and unlink the external account before '. 'linking it to a new Phabricator account.', $provider->getProviderName())); @@ -214,7 +214,7 @@ final class PhabricatorAuthLoginController if (!$provider) { return $this->renderError( pht( - 'The account you are attempting to login with uses a nonexistent '. + 'The account you are attempting to log in with uses a nonexistent '. 'or disabled authentication provider (with key "%s"). An '. 'administrator may have recently disabled this provider.', $this->providerKey)); @@ -240,14 +240,14 @@ final class PhabricatorAuthLoginController if ($this->getRequest()->getUser()->isLoggedIn()) { $crumbs->addTextCrumb(pht('Link Account'), $provider->getSettingsURI()); } else { - $crumbs->addTextCrumb(pht('Login'), $this->getApplicationURI('start/')); + $crumbs->addTextCrumb(pht('Log In'), $this->getApplicationURI('start/')); } $crumbs->addTextCrumb($provider->getProviderName()); $crumbs->setBorder(true); return $this->newPage() - ->setTitle(pht('Login')) + ->setTitle(pht('Log In')) ->setCrumbs($crumbs) ->appendChild($content); } @@ -257,9 +257,8 @@ final class PhabricatorAuthLoginController $message) { $message = pht( - 'Authentication provider ("%s") encountered an error during login. %s', - $provider->getProviderName(), - $message); + 'Authentication provider ("%s") encountered an error while attempting '. + 'to log in. %s', $provider->getProviderName(), $message); return $this->renderError($message); } diff --git a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php index ebfd07e7ac..534bda3f35 100644 --- a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php +++ b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php @@ -68,7 +68,7 @@ final class PhabricatorAuthOneTimeLoginController if (!$token) { return $this->newDialog() - ->setTitle(pht('Unable to Login')) + ->setTitle(pht('Unable to Log In')) ->setShortTitle(pht('Login Failure')) ->appendParagraph( pht( @@ -170,7 +170,7 @@ final class PhabricatorAuthOneTimeLoginController case PhabricatorAuthSessionEngine::ONETIME_USERNAME: case PhabricatorAuthSessionEngine::ONETIME_RESET: default: - $title = pht('Login to Phabricator'); + $title = pht('Log in to Phabricator'); break; } @@ -193,7 +193,7 @@ final class PhabricatorAuthOneTimeLoginController $dialog = $this->newDialog() ->setTitle($title) - ->addSubmitButton(pht('Login (%s)', $target_user->getUsername())) + ->addSubmitButton(pht('Log In (%s)', $target_user->getUsername())) ->addCancelButton('/'); foreach ($body as $paragraph) { diff --git a/src/applications/auth/controller/PhabricatorAuthRegisterController.php b/src/applications/auth/controller/PhabricatorAuthRegisterController.php index a3ac38ee2f..64d1c8f23a 100644 --- a/src/applications/auth/controller/PhabricatorAuthRegisterController.php +++ b/src/applications/auth/controller/PhabricatorAuthRegisterController.php @@ -491,14 +491,14 @@ final class PhabricatorAuthRegisterController if ($can_edit_username) { $form->appendChild( id(new AphrontFormTextControl()) - ->setLabel(pht('Phabricator Username')) + ->setLabel(pht('Username')) ->setName('username') ->setValue($value_username) ->setError($e_username)); } else { $form->appendChild( id(new AphrontFormMarkupControl()) - ->setLabel(pht('Phabricator Username')) + ->setLabel(pht('Username')) ->setValue($value_username) ->setError($e_username)); } @@ -554,7 +554,7 @@ final class PhabricatorAuthRegisterController } else { $submit ->addCancelButton($this->getApplicationURI('start/')) - ->setValue(pht('Register Phabricator Account')); + ->setValue(pht('Register Account')); } @@ -568,7 +568,7 @@ final class PhabricatorAuthRegisterController } else { $crumbs->addTextCrumb(pht('Register')); $crumbs->addTextCrumb($provider->getProviderName()); - $title = pht('Phabricator Registration'); + $title = pht('Create a New Account'); } $crumbs->setBorder(true); diff --git a/src/applications/auth/controller/PhabricatorAuthSSHKeyEditController.php b/src/applications/auth/controller/PhabricatorAuthSSHKeyEditController.php index 1023b0cd75..6dfc957f48 100644 --- a/src/applications/auth/controller/PhabricatorAuthSSHKeyEditController.php +++ b/src/applications/auth/controller/PhabricatorAuthSSHKeyEditController.php @@ -93,7 +93,7 @@ final class PhabricatorAuthSSHKeyEditController try { $editor->applyTransactions($key, $xactions); - return id(new AphrontRedirectResponse())->setURI($key->getURI()); + return id(new AphrontRedirectResponse())->setURI($cancel_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_name = $ex->getShortMessage($type_name); diff --git a/src/applications/auth/controller/PhabricatorAuthStartController.php b/src/applications/auth/controller/PhabricatorAuthStartController.php index 5cfcb9b9ef..9af8f25bc7 100644 --- a/src/applications/auth/controller/PhabricatorAuthStartController.php +++ b/src/applications/auth/controller/PhabricatorAuthStartController.php @@ -198,7 +198,7 @@ final class PhabricatorAuthStartController $crumbs->addTextCrumb(pht('Login')); $crumbs->setBorder(true); - $title = pht('Login to Phabricator'); + $title = pht('Login'); $view = array( $header, $invite_message, @@ -239,8 +239,8 @@ final class PhabricatorAuthStartController return $this->newDialog() ->setTitle(pht('Login Required')) - ->appendParagraph(pht('You must login to take this action.')) - ->addSubmitButton(pht('Login')) + ->appendParagraph(pht('You must log in to take this action.')) + ->addSubmitButton(pht('Log In')) ->addCancelButton('/'); } diff --git a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php index 2c21f63407..6211e78110 100644 --- a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php +++ b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php @@ -104,7 +104,7 @@ final class PhabricatorAuthUnlinkController pht( 'You can not unlink this account because you have no other '. 'valid login accounts. If you removed it, you would be unable '. - 'to login. Add another authentication method before removing '. + 'to log in. Add another authentication method before removing '. 'this one.')) ->addCancelButton($this->getDoneURI()); diff --git a/src/applications/auth/controller/PhabricatorEmailLoginController.php b/src/applications/auth/controller/PhabricatorEmailLoginController.php index e9cf693514..92accc7494 100644 --- a/src/applications/auth/controller/PhabricatorEmailLoginController.php +++ b/src/applications/auth/controller/PhabricatorEmailLoginController.php @@ -114,7 +114,7 @@ final class PhabricatorEmailLoginController ->setTitle(pht('Check Your Email')) ->setShortTitle(pht('Email Sent')) ->appendParagraph( - pht('An email has been sent with a link you can use to login.')) + pht('An email has been sent with a link you can use to log in.')) ->addCancelButton('/', pht('Done')); } } diff --git a/src/applications/auth/controller/PhabricatorLogoutController.php b/src/applications/auth/controller/PhabricatorLogoutController.php index f4b61f88a3..47592e0a2d 100644 --- a/src/applications/auth/controller/PhabricatorLogoutController.php +++ b/src/applications/auth/controller/PhabricatorLogoutController.php @@ -52,9 +52,9 @@ final class PhabricatorLogoutController if ($viewer->getPHID()) { return $this->newDialog() - ->setTitle(pht('Log out of Phabricator?')) + ->setTitle(pht('Log Out?')) ->appendChild(pht('Are you sure you want to log out?')) - ->addSubmitButton(pht('Logout')) + ->addSubmitButton(pht('Log Out')) ->addCancelButton('/'); } diff --git a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php index 670a95f977..bf3410139d 100644 --- a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php +++ b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php @@ -33,7 +33,7 @@ final class PhabricatorMustVerifyEmailController } $must_verify = pht( - 'You must verify your email address to login. You should have a '. + 'You must verify your email address to log in. You should have a '. 'new email message from Phabricator with verification instructions '. 'in your inbox (%s).', phutil_tag('strong', array(), $email_address)); diff --git a/src/applications/auth/controller/config/PhabricatorAuthListController.php b/src/applications/auth/controller/config/PhabricatorAuthListController.php index ae5808ed78..c5d4f7ad77 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthListController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthListController.php @@ -103,7 +103,7 @@ final class PhabricatorAuthListController $button = id(new PHUIButtonView()) ->setTag('a') - ->setColor(PHUIButtonView::SIMPLE) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) ->setHref($this->getApplicationURI('config/new/')) ->setIcon('fa-plus') ->setDisabled(!$can_manage) diff --git a/src/applications/auth/provider/PhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorAuthProvider.php index 4dd7f4da1b..d655efd790 100644 --- a/src/applications/auth/provider/PhabricatorAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorAuthProvider.php @@ -389,7 +389,7 @@ abstract class PhabricatorAuthProvider extends Phobject { * @param AphrontRequest HTTP request. * @param string Request mode string. * @param map Additional parameters, see above. - * @return wild Login button. + * @return wild Log in button. */ protected function renderStandardLoginButton( AphrontRequest $request, @@ -414,9 +414,9 @@ abstract class PhabricatorAuthProvider extends Phobject { } else if ($mode == 'invite') { $button_text = pht('Register Account'); } else if ($this->shouldAllowRegistration()) { - $button_text = pht('Login or Register'); + $button_text = pht('Log In or Register'); } else { - $button_text = pht('Login'); + $button_text = pht('Log In'); } $icon = id(new PHUIIconView()) diff --git a/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php b/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php index 013cd21736..7c2bf38618 100644 --- a/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php @@ -80,11 +80,11 @@ final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider { $dialog->addCancelButton($this->getSettingsURI()); } else { if ($this->shouldAllowRegistration()) { - $dialog->setTitle(pht('Login or Register with LDAP')); - $dialog->addSubmitButton(pht('Login or Register')); + $dialog->setTitle(pht('Log In or Register with LDAP')); + $dialog->addSubmitButton(pht('Log In or Register')); } else { - $dialog->setTitle(pht('Login with LDAP')); - $dialog->addSubmitButton(pht('Login')); + $dialog->setTitle(pht('Log In with LDAP')); + $dialog->addSubmitButton(pht('Log In')); } if ($mode == 'login') { $dialog->addCancelButton($this->getStartURI()); @@ -315,7 +315,7 @@ final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider { "credentials (which is more complicated, but more powerful).\n\n". "For many installs, direct binding is sufficient. However, you may ". "want to search first if:\n\n". - " - You want users to be able to login with either their username ". + " - You want users to be able to log in with either their username ". " or their email address.\n". " - The login/username is not part of the distinguished name in ". " your LDAP records.\n". @@ -344,16 +344,16 @@ final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider { "`sn`, which will work the same way direct binding works:\n\n". " lang=text,name=Simple Example\n". " sn\n\n". - "A slightly more complex configuration might let the user login with ". + "A slightly more complex configuration might let the user log in with ". "either their login name or email address:\n\n". " lang=text,name=Match Several Attributes\n". " mail\n". " sn\n\n". "If your LDAP directory is more complex, or you want to perform ". "sophisticated filtering, you can use more complex queries. Depending ". - "on your directory structure, this example might allow users to login ". - "with either their email address or username, but only if they're in ". - "specific departments:\n\n". + "on your directory structure, this example might allow users to log ". + "in with either their email address or username, but only if they're ". + "in specific departments:\n\n". " lang=text,name=Complex Example\n". " (&(mail=\${login})(|(departmentNumber=1)(departmentNumber=2)))\n". " (&(sn=\${login})(|(departmentNumber=1)(departmentNumber=2)))\n\n". diff --git a/src/applications/auth/provider/PhabricatorPasswordAuthProvider.php b/src/applications/auth/provider/PhabricatorPasswordAuthProvider.php index 7c8a5450f1..76bcff1827 100644 --- a/src/applications/auth/provider/PhabricatorPasswordAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorPasswordAuthProvider.php @@ -100,7 +100,7 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider { public function getDescriptionForCreate() { return pht( - 'Allow users to login or register using a username and password.'); + 'Allow users to log in or register using a username and password.'); } public function getAdapter() { diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 29f5ed35be..72b577771f 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -57,6 +57,10 @@ abstract class PhabricatorApplication abstract public function getName(); + public function getMenuName() { + return $this->getName(); + } + public function getShortDescription() { return pht('%s Application', $this->getName()); } @@ -284,7 +288,6 @@ abstract class PhabricatorApplication throw new PhutilMethodNotImplementedException(); } - /* -( Fact Integration )--------------------------------------------------- */ @@ -643,7 +646,7 @@ abstract class PhabricatorApplication public function getApplicationTransactionEditor() { - return new PhutilMethodNotImplementedException(pht('Coming Soon!')); + return new PhabricatorApplicationEditor(); } public function getApplicationTransactionObject() { diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 58d8f6cf6f..1ecfdaf557 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -471,7 +471,7 @@ abstract class PhabricatorController extends AphrontController { ->setViewer($this->getViewer()); } - public function newCurtainView($object) { + public function newCurtainView($object = null) { $viewer = $this->getViewer(); $action_id = celerity_generate_unique_node_id(); @@ -483,17 +483,21 @@ abstract class PhabricatorController extends AphrontController { // NOTE: Applications (objects of class PhabricatorApplication) can't // currently be set here, although they don't need any of the extensions // anyway. This should probably work differently than it does, though. - if ($object instanceof PhabricatorLiskDAO) { - $action_list->setObject($object); + if ($object) { + if ($object instanceof PhabricatorLiskDAO) { + $action_list->setObject($object); + } } $curtain = id(new PHUICurtainView()) ->setViewer($viewer) ->setActionList($action_list); - $panels = PHUICurtainExtension::buildExtensionPanels($viewer, $object); - foreach ($panels as $panel) { - $curtain->addPanel($panel); + if ($object) { + $panels = PHUICurtainExtension::buildExtensionPanels($viewer, $object); + foreach ($panels as $panel) { + $curtain->addPanel($panel); + } } return $curtain; diff --git a/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php b/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php index 91ab0cf83d..cfe42c918b 100644 --- a/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php +++ b/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php @@ -6,119 +6,79 @@ final class PhabricatorCacheManagementPurgeWorkflow protected function didConstruct() { $this ->setName('purge') - ->setSynopsis(pht('Drop data from caches. APC-based caches can be '. - 'purged from the web interface.')) + ->setSynopsis(pht('Drop data from readthrough caches.')) ->setArguments( array( array( - 'name' => 'purge-all', - 'help' => pht('Purge all caches.'), + 'name' => 'all', + 'help' => pht('Purge all caches.'), ), array( - 'name' => 'purge-remarkup', - 'help' => pht('Purge the remarkup cache.'), - ), - array( - 'name' => 'purge-changeset', - 'help' => pht('Purge the Differential changeset cache.'), - ), - array( - 'name' => 'purge-general', - 'help' => pht('Purge the general cache.'), - ), - array( - 'name' => 'purge-user', - 'help' => pht('Purge the user cache.'), + 'name' => 'caches', + 'param' => 'keys', + 'help' => pht('Purge a specific set of caches.'), ), )); } public function execute(PhutilArgumentParser $args) { - $console = PhutilConsole::getConsole(); + $all_purgers = PhabricatorCachePurger::getAllPurgers(); - $purge_all = $args->getArg('purge-all'); - - $purge = array( - 'remarkup' => $purge_all || $args->getArg('purge-remarkup'), - 'changeset' => $purge_all || $args->getArg('purge-changeset'), - 'general' => $purge_all || $args->getArg('purge-general'), - 'user' => $purge_all || $args->getArg('purge-user'), - ); - - if (!array_filter($purge)) { - $list = array(); - foreach ($purge as $key => $ignored) { - $list[] = "'--purge-".$key."'"; - } + $is_all = $args->getArg('all'); + $key_list = $args->getArg('caches'); + if ($is_all && strlen($key_list)) { throw new PhutilArgumentUsageException( pht( - "Specify which cache or caches to purge, or use '%s'. Available ". - "caches are: %s. Use '%s' for more information.", - '--purge-all', - implode(', ', $list), - '--help')); + 'Specify either "--all" or "--caches", not both.')); + } else if (!$is_all && !strlen($key_list)) { + throw new PhutilArgumentUsageException( + pht( + 'Select caches to purge with "--all" or "--caches". Available '. + 'caches are: %s.', + implode(', ', array_keys($all_purgers)))); } - if ($purge['remarkup']) { - $console->writeOut(pht('Purging remarkup cache...')); - $this->purgeRemarkupCache(); - $console->writeOut("%s\n", pht('Done.')); + if ($is_all) { + $purgers = $all_purgers; + } else { + $key_list = preg_split('/[\s,]+/', $key_list); + $purgers = array(); + foreach ($key_list as $key) { + if (isset($all_purgers[$key])) { + $purgers[$key] = $all_purgers[$key]; + } else { + throw new PhutilArgumentUsageException( + pht( + 'Cache purger "%s" is not recognized. Available caches '. + 'are: %s.', + $key, + implode(', ', array_keys($all_purgers)))); + } + } + if (!$purgers) { + throw new PhutilArgumentUsageException( + pht( + 'When using "--caches", you must select at least one valid '. + 'cache to purge.')); + } } - if ($purge['changeset']) { - $console->writeOut(pht('Purging changeset cache...')); - $this->purgeChangesetCache(); - $console->writeOut("%s\n", pht('Done.')); + $viewer = $this->getViewer(); + + foreach ($purgers as $key => $purger) { + $purger->setViewer($viewer); + + echo tsprintf( + "%s\n", + pht( + 'Purging "%s" cache...', + $key)); + + $purger->purgeCache(); } - if ($purge['general']) { - $console->writeOut(pht('Purging general cache...')); - $this->purgeGeneralCache(); - $console->writeOut("%s\n", pht('Done.')); - } - - if ($purge['user']) { - $console->writeOut(pht('Purging user cache...')); - $this->purgeUserCache(); - $console->writeOut("%s\n", pht('Done.')); - } - } - - private function purgeRemarkupCache() { - $conn_w = id(new PhabricatorMarkupCache())->establishConnection('w'); - - queryfx( - $conn_w, - 'TRUNCATE TABLE %T', - id(new PhabricatorMarkupCache())->getTableName()); - } - - private function purgeChangesetCache() { - $conn_w = id(new DifferentialChangeset())->establishConnection('w'); - queryfx( - $conn_w, - 'TRUNCATE TABLE %T', - DifferentialChangeset::TABLE_CACHE); - } - - private function purgeGeneralCache() { - $conn_w = id(new PhabricatorMarkupCache())->establishConnection('w'); - - queryfx( - $conn_w, - 'TRUNCATE TABLE %T', - 'cache_general'); - } - - private function purgeUserCache() { - $table = new PhabricatorUserCache(); - $conn_w = $table->establishConnection('w'); - - queryfx( - $conn_w, - 'TRUNCATE TABLE %T', - $table->getTableName()); + return 0; } } diff --git a/src/applications/cache/purger/PhabricatorBuiltinFileCachePurger.php b/src/applications/cache/purger/PhabricatorBuiltinFileCachePurger.php new file mode 100644 index 0000000000..a4bc682c02 --- /dev/null +++ b/src/applications/cache/purger/PhabricatorBuiltinFileCachePurger.php @@ -0,0 +1,22 @@ +getViewer(); + + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withIsBuiltin(true) + ->execute(); + + $engine = new PhabricatorDestructionEngine(); + foreach ($files as $file) { + $engine->destroyObject($file); + } + } + +} diff --git a/src/applications/cache/purger/PhabricatorCachePurger.php b/src/applications/cache/purger/PhabricatorCachePurger.php new file mode 100644 index 0000000000..0115b45d7e --- /dev/null +++ b/src/applications/cache/purger/PhabricatorCachePurger.php @@ -0,0 +1,30 @@ +viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + final public function getPurgerKey() { + return $this->getPhobjectClassConstant('PURGERKEY'); + } + + final public static function getAllPurgers() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getPurgerKey') + ->execute(); + } + +} diff --git a/src/applications/cache/purger/PhabricatorChangesetCachePurger.php b/src/applications/cache/purger/PhabricatorChangesetCachePurger.php new file mode 100644 index 0000000000..fd6ff9940a --- /dev/null +++ b/src/applications/cache/purger/PhabricatorChangesetCachePurger.php @@ -0,0 +1,18 @@ +establishConnection('w'); + + queryfx( + $conn, + 'TRUNCATE TABLE %T', + DifferentialChangeset::TABLE_CACHE); + } + +} diff --git a/src/applications/cache/purger/PhabricatorGeneralCachePurger.php b/src/applications/cache/purger/PhabricatorGeneralCachePurger.php new file mode 100644 index 0000000000..f2dea1e1e5 --- /dev/null +++ b/src/applications/cache/purger/PhabricatorGeneralCachePurger.php @@ -0,0 +1,18 @@ +establishConnection('w'); + + queryfx( + $conn, + 'TRUNCATE TABLE %T', + 'cache_general'); + } + +} diff --git a/src/applications/cache/purger/PhabricatorRemarkupCachePurger.php b/src/applications/cache/purger/PhabricatorRemarkupCachePurger.php new file mode 100644 index 0000000000..2c416fff36 --- /dev/null +++ b/src/applications/cache/purger/PhabricatorRemarkupCachePurger.php @@ -0,0 +1,18 @@ +establishConnection('w'); + + queryfx( + $conn, + 'TRUNCATE TABLE %T', + $table->getTableName()); + } + +} diff --git a/src/applications/cache/purger/PhabricatorUserCachePurger.php b/src/applications/cache/purger/PhabricatorUserCachePurger.php new file mode 100644 index 0000000000..64f631c96b --- /dev/null +++ b/src/applications/cache/purger/PhabricatorUserCachePurger.php @@ -0,0 +1,18 @@ +establishConnection('w'); + + queryfx( + $conn, + 'TRUNCATE TABLE %T', + $table->getTableName()); + } + +} diff --git a/src/applications/cache/spec/PhabricatorDataCacheSpec.php b/src/applications/cache/spec/PhabricatorDataCacheSpec.php index 482af00d00..bf6a495f7f 100644 --- a/src/applications/cache/spec/PhabricatorDataCacheSpec.php +++ b/src/applications/cache/spec/PhabricatorDataCacheSpec.php @@ -50,9 +50,15 @@ final class PhabricatorDataCacheSpec extends PhabricatorCacheSpec { ->setVersion(phpversion('apcu')); if (ini_get('apc.enabled')) { + if (function_exists('apcu_clear_cache')) { + $clear_callback = 'apcu_clear_cache'; + } else { + $clear_callback = 'apc_clear_cache'; + } + $this ->setIsEnabled(true) - ->setClearCacheCallback('apc_clear_cache'); + ->setClearCacheCallback($clear_callback); $this->initAPCCommonSpec(); } else { $this->setIsEnabled(false); diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 91d3a9e336..3ba3aa1e70 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -125,7 +125,7 @@ final class PhabricatorCalendarEventViewController ->setName(pht('Imported')) ->setIcon('fa-download') ->setHref($event->getImportSource()->getURI()) - ->setShade('orange')); + ->setColor(PHUITagView::COLOR_ORANGE)); } foreach ($this->buildRSVPActions($event) as $action) { diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index eee6b46751..4ab13fd360 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -127,6 +127,9 @@ final class PhabricatorCalendarEventEditor // Clear the availability caches for users whose availability is affected // by this edit. + $phids = mpull($object->getInvitees(), 'getInviteePHID'); + $phids = array_fuse($phids); + $invalidate_all = false; $invalidate_phids = array(); foreach ($xactions as $xaction) { @@ -146,16 +149,21 @@ final class PhabricatorCalendarEventEditor $invalidate_phids[$acting_phid] = $acting_phid; break; case PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE: - foreach ($xaction->getNewValue() as $phid => $ignored) { + foreach ($xaction->getOldValue() as $phid) { + // Add the possibly un-invited user to the list of potentially + // affected users if they are't already present. + $phids[$phid] = $phid; + + $invalidate_phids[$phid] = $phid; + } + + foreach ($xaction->getNewValue() as $phid) { $invalidate_phids[$phid] = $phid; } break; } } - $phids = mpull($object->getInvitees(), 'getInviteePHID'); - $phids = array_fuse($phids); - if (!$invalidate_all) { $phids = array_select_keys($phids, $invalidate_phids); } @@ -168,10 +176,9 @@ final class PhabricatorCalendarEventEditor queryfx( $conn_w, 'UPDATE %T SET availabilityCacheTTL = NULL - WHERE phid IN (%Ls) AND availabilityCacheTTL >= %d', + WHERE phid IN (%Ls)', $user->getTableName(), - $phids, - $object->getStartDateTimeEpochForCache()); + $phids); } return $xactions; diff --git a/src/applications/calendar/herald/PhabricatorCalendarEventHeraldAdapter.php b/src/applications/calendar/herald/PhabricatorCalendarEventHeraldAdapter.php index 49d5f38959..1fc3a56317 100644 --- a/src/applications/calendar/herald/PhabricatorCalendarEventHeraldAdapter.php +++ b/src/applications/calendar/herald/PhabricatorCalendarEventHeraldAdapter.php @@ -49,6 +49,13 @@ final class PhabricatorCalendarEventHeraldAdapter extends HeraldAdapter { } } + public function getRepetitionOptions() { + return array( + HeraldRepetitionPolicyConfig::EVERY, + HeraldRepetitionPolicyConfig::FIRST, + ); + } + public function getHeraldName() { return $this->getObject()->getMonogram(); } diff --git a/src/applications/calendar/search/PhabricatorCalendarEventFerretEngine.php b/src/applications/calendar/search/PhabricatorCalendarEventFerretEngine.php new file mode 100644 index 0000000000..70e456e034 --- /dev/null +++ b/src/applications/calendar/search/PhabricatorCalendarEventFerretEngine.php @@ -0,0 +1,18 @@ +setType(PHUITagView::TYPE_SHADE) - ->setShade($color) + ->setColor($color) ->setName($name) ->setDotColor($color); diff --git a/src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php b/src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php new file mode 100644 index 0000000000..39ee4be57a --- /dev/null +++ b/src/applications/celerity/postprocessor/CelerityDarkModePostprocessor.php @@ -0,0 +1,224 @@ + "13px 'Segoe UI', 'Segoe UI Emoji', ". + "'Segoe UI Symbol', 'Lato', 'Helvetica Neue', ". + "Helvetica, Arial, sans-serif", + + 'fontfamily' => "'Segoe UI', 'Segoe UI Emoji', ". + "'Segoe UI Symbol', 'Lato', 'Helvetica Neue', ". + "Helvetica, Arial, sans-serif", + + // Drop Shadow + 'dropshadow' => '0 2px 12px rgba(0, 0, 0, .20)', + 'whitetextshadow' => '0 1px 0 rgba(255, 255, 255, 1)', + + // Anchors + 'anchor' => '#3498db', + + // Base Colors + 'red' => '#c0392b', + 'lightred' => '#7f261c', + 'orange' => '#e67e22', + 'lightorange' => '#f7e2d4', + 'yellow' => '#f1c40f', + 'lightyellow' => '#a4850a', + 'green' => '#139543', + 'lightgreen' => '#0e7032', + 'blue' => '#2980b9', + 'lightblue' => '#1d5981', + 'sky' => '#3498db', + 'lightsky' => '#ddeef9', + 'fire' => '#e62f17', + 'indigo' => '#6e5cb6', + 'lightindigo' => '#eae6f7', + 'pink' => '#da49be', + 'lightpink' => '#fbeaf8', + 'violet' => '#8e44ad', + 'lightviolet' => '#622f78', + 'charcoal' => '#4b4d51', + 'backdrop' => '#c4cde0', + 'hoverwhite' => 'rgba(255,255,255,.6)', + 'hovergrey' => '#c5cbcf', + 'hoverblue' => '#2a425f', + 'hoverborder' => '#dfe1e9', + 'hoverselectedgrey' => '#bbc4ca', + 'hoverselectedblue' => '#e6e9ee', + 'borderinset' => 'inset 0 0 0 1px rgba(55,55,55,.15)', + 'timeline' => '#4e6078', + 'timeline.icon.background' => '#416086', + 'bluepropertybackground' => '#2d435f', + + // Alphas + 'alphawhite' => '255,255,255', + 'alphagrey' => '255,255,255', + 'alphablue' => '255,255,255', + 'alphablack' => '0,0,0', + + // Base Greys + 'lightgreyborder' => 'rgba(255,255,255,.3)', + 'greyborder' => 'rgba(255,255,255,.6)', + 'darkgreyborder' => 'rgba(255,255,255,.9)', + 'lightgreytext' => 'rgba(255,255,255,.3)', + 'greytext' => 'rgba(255,255,255,.6)', + 'darkgreytext' => 'rgba(255,255,255,.9)', + 'lightgreybackground' => '#2a425f', + 'greybackground' => '#304a6d', + 'darkgreybackground' => '#8C98B8', + + // Base Blues + 'thinblueborder' => '#2c405a', + 'lightblueborder' => '#39506d', + 'blueborder' => '#8C98B8', + 'darkblueborder' => '#626E82', + 'lightbluebackground' => 'rgba(255,255,255,.05)', + 'bluebackground' => 'rgba(255,255,255,.1)', + 'lightbluetext' => 'rgba(255,255,255,.3)', + 'bluetext' => 'rgba(255,255,255,.6)', + 'darkbluetext' => 'rgba(255,255,255,.8)', + 'blacktext' => 'rgba(255,255,255,.9)', + + // Base Greens + 'lightgreenborder' => '#bfdac1', + 'greenborder' => '#8cb89c', + 'greentext' => '#3e6d35', + 'lightgreenbackground' => '#e6f2e4', + + // Base Red + 'lightredborder' => '#f4c6c6', + 'redborder' => '#eb9797', + 'redtext' => '#802b2b', + 'lightredbackground' => '#f5e1e1', + + // Base Violet + 'lightvioletborder' => '#cfbddb', + 'violetborder' => '#b589ba', + 'violettext' => '#603c73', + 'lightvioletbackground' => '#e9dfee', + + // Shades are a more muted set of our base colors + // better suited to blending into other UIs. + + // Shade Red + 'sh-lightredborder' => '#efcfcf', + 'sh-redborder' => '#d1abab', + 'sh-redicon' => '#c85a5a', + 'sh-redtext' => '#a53737', + 'sh-redbackground' => '#f7e6e6', + + // Shade Orange + 'sh-lightorangeborder' => '#f8dcc3', + 'sh-orangeborder' => '#dbb99e', + 'sh-orangeicon' => '#e78331', + 'sh-orangetext' => '#ba6016', + 'sh-orangebackground' => '#fbede1', + + // Shade Yellow + 'sh-lightyellowborder' => '#e9dbcd', + 'sh-yellowborder' => '#c9b8a8', + 'sh-yellowicon' => '#9b946e', + 'sh-yellowtext' => '#726f56', + 'sh-yellowbackground' => '#fdf3da', + + // Shade Green + 'sh-lightgreenborder' => '#c6e6c7', + 'sh-greenborder' => '#a0c4a1', + 'sh-greenicon' => '#4ca74e', + 'sh-greentext' => '#326d34', + 'sh-greenbackground' => '#ddefdd', + + // Shade Blue + 'sh-lightblueborder' => '#cfdbe3', + 'sh-blueborder' => '#a7b5bf', + 'sh-blueicon' => '#6b748c', + 'sh-bluetext' => '#464c5c', + 'sh-bluebackground' => '#dee7f8', + + // Shade Indigo + 'sh-lightindigoborder' => '#d1c9ee', + 'sh-indigoborder' => '#bcb4da', + 'sh-indigoicon' => '#8672d4', + 'sh-indigotext' => '#6e5cb6', + 'sh-indigobackground' => '#eae6f7', + + // Shade Violet + 'sh-lightvioletborder' => '#e0d1e7', + 'sh-violetborder' => '#bcabc5', + 'sh-violeticon' => '#9260ad', + 'sh-violettext' => '#69427f', + 'sh-violetbackground' => '#efe8f3', + + // Shade Pink + 'sh-lightpinkborder' => '#f6d5ef', + 'sh-pinkborder' => '#d5aecd', + 'sh-pinkicon' => '#e26fcb', + 'sh-pinktext' => '#da49be', + 'sh-pinkbackground' => '#fbeaf8', + + // Shade Grey + 'sh-lightgreyborder' => '#e3e4e8', + 'sh-greyborder' => '#b2b2b2', + 'sh-greyicon' => '#757575', + 'sh-greytext' => '#555555', + 'sh-greybackground' => '#edeef2', + + // Shade Disabled + 'sh-lightdisabledborder' => '#e5e5e5', + 'sh-disabledborder' => '#cbcbcb', + 'sh-disabledicon' => '#bababa', + 'sh-disabledtext' => '#a6a6a6', + 'sh-disabledbackground' => '#f3f3f3', + + // Diffs + 'diff.background' => '#121b27', + 'new-background' => 'rgba(151, 234, 151, .55)', + 'new-bright' => 'rgba(151, 234, 151, .75)', + 'old-background' => 'rgba(251, 175, 175, .55)', + 'old-bright' => 'rgba(251, 175, 175, .8)', + 'move-background' => '#faca00', + 'copy-background' => '#f1c40f', + + // Usually light yellow + 'gentle.highlight' => '#26c1c9', + 'gentle.highlight.border' => '#21a9b0', + + 'paste.content' => '#222222', + 'paste.border' => '#000000', + 'paste.highlight' => '#121212', + + // Background color for "most" themes. + 'page.background' => '#223246', + 'page.sidenav' => '#1c293b', + 'page.content' => '#26374c', + + 'menu.profile.text' => 'rgba(255,255,255,.8)', + 'menu.profile.text.selected' => 'rgba(255,255,255,1)', + 'menu.profile.icon.disabled' => 'rgba(255,255,255,.4)', + + // Buttons + 'blue.button.color' => '#2980b9', + 'blue.button.gradient' => 'linear-gradient(to bottom, #3498db, #2980b9)', + 'green.button.color' => '#139543', + 'green.button.gradient' => 'linear-gradient(to bottom, #23BB5B, #139543)', + 'grey.button.color' => '#223246', + 'grey.button.gradient' => 'linear-gradient(to bottom, #223246, #223246)', + 'grey.button.hover' => 'linear-gradient(to bottom, #1c293b, #1c293b)', + + ); + } + +} diff --git a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php index 62a4b4fd24..61f6176f15 100644 --- a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php +++ b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php @@ -72,6 +72,8 @@ final class CelerityDefaultPostprocessor 'hoverselectedblue' => '#e6e9ee', 'borderinset' => 'inset 0 0 0 1px rgba(55,55,55,.15)', 'timeline' => '#d5d8e1', + 'timeline.icon.background' => '#E6E9F1', + 'bluepropertybackground' => '#eff3fc', // Alphas 'alphawhite' => '255,255,255', @@ -100,6 +102,7 @@ final class CelerityDefaultPostprocessor 'lightbluetext' => '#8C98B8', 'bluetext' => '#6B748C', 'darkbluetext' => '#464C5C', + 'blacktext' => '#000', // Base Greens 'lightgreenborder' => '#bfdac1', @@ -193,6 +196,7 @@ final class CelerityDefaultPostprocessor 'sh-disabledbackground' => '#f3f3f3', // Diffs + 'diff.background' => '#fff', 'new-background' => 'rgba(151, 234, 151, .3)', 'new-bright' => 'rgba(151, 234, 151, .6)', 'old-background' => 'rgba(251, 175, 175, .3)', @@ -200,9 +204,18 @@ final class CelerityDefaultPostprocessor 'move-background' => '#fdf5d4', 'copy-background' => '#f1c40f', + // Usually light yellow + 'gentle.highlight' => '#fdf3da', + 'gentle.highlight.border' => '#c9b8a8', + + 'paste.content' => '#fffef5', + 'paste.border' => '#e9dbcd', + 'paste.highlight' => '#fdf3da', + // Background color for "most" themes. 'page.background' => '#f3f5f7', 'page.sidenav' => '#eaedf1', + 'page.content' => '#fff', 'menu.profile.text' => 'rgba(255,255,255,.8)', 'menu.profile.text.selected' => 'rgba(255,255,255,1)', @@ -211,6 +224,21 @@ final class CelerityDefaultPostprocessor 'menu.main.height' => '44px', 'menu.profile.width' => '240px', + // Buttons + 'blue.button.color' => '#2980b9', + 'blue.button.gradient' => 'linear-gradient(to bottom, #3498db, #2980b9)', + 'blue.button.hover' => 'linear-gradient(to bottom, #3498db, #1b6ba0)', + 'green.button.color' => '#139543', + 'green.button.gradient' => 'linear-gradient(to bottom, #23BB5B, #139543)', + 'green.button.hover' => 'linear-gradient(to bottom, #23BB5B, #178841)', + 'red.button.color' => '#b33225', + 'red.button.gradient' => 'linear-gradient(to bottom, #d25454, #b33225)', + 'red.button.hover' => 'linear-gradient(to bottom, #d25454, #982115)', + 'grey.button.color' => '#F7F7F9', + 'grey.button.gradient' => 'linear-gradient(to bottom, #ffffff, #f1f0f1)', + 'grey.button.hover' => 'linear-gradient(to bottom, #ffffff, #eeebec)', + + ); } diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php index 991865b564..12127a1e27 100644 --- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php @@ -119,9 +119,11 @@ final class PhabricatorConduitAPIController ->setError((string)$error_code) ->setDuration(1000000 * ($time_end - $time_start)); - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $log->save(); - unset($unguarded); + if (!PhabricatorEnv::isReadOnly()) { + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $log->save(); + unset($unguarded); + } $response = id(new ConduitAPIResponse()) ->setResult($result) diff --git a/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php b/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php index 59161d2814..2075582386 100644 --- a/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php +++ b/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php @@ -60,7 +60,7 @@ final class PhabricatorConduitTokensSettingsPanel javelin_tag( 'a', array( - 'class' => 'button small grey', + 'class' => 'button small button-grey', 'href' => '/conduit/token/terminate/'.$token->getID().'/', 'sigil' => 'workflow', ), @@ -88,18 +88,19 @@ final class PhabricatorConduitTokensSettingsPanel )); $generate_button = id(new PHUIButtonView()) - ->setText(pht('Generate API Token')) + ->setText(pht('Generate Token')) ->setHref('/conduit/token/edit/?objectPHID='.$user->getPHID()) ->setTag('a') ->setWorkflow(true) ->setIcon('fa-plus'); $terminate_button = id(new PHUIButtonView()) - ->setText(pht('Terminate All Tokens')) + ->setText(pht('Terminate Tokens')) ->setHref('/conduit/token/terminate/?objectPHID='.$user->getPHID()) ->setTag('a') ->setWorkflow(true) - ->setIcon('fa-exclamation-triangle'); + ->setIcon('fa-exclamation-triangle') + ->setColor(PHUIButtonView::RED); $header = id(new PHUIHeaderView()) ->setHeader(pht('Active API Tokens')) @@ -108,7 +109,8 @@ final class PhabricatorConduitTokensSettingsPanel $panel = id(new PHUIObjectBoxView()) ->setHeader($header) - ->setTable($table); + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) + ->appendChild($table); return $panel; } diff --git a/src/applications/config/check/PhabricatorBinariesSetupCheck.php b/src/applications/config/check/PhabricatorBinariesSetupCheck.php index 0d577b5297..cf3c87f480 100644 --- a/src/applications/config/check/PhabricatorBinariesSetupCheck.php +++ b/src/applications/config/check/PhabricatorBinariesSetupCheck.php @@ -99,12 +99,12 @@ final class PhabricatorBinariesSetupCheck extends PhabricatorSetupCheck { continue; } - $version = null; + $version = PhutilBinaryAnalyzer::getForBinary($binary) + ->getBinaryVersion(); + switch ($vcs['versionControlSystem']) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $bad_versions = array(); - list($err, $stdout, $stderr) = exec_manual('git --version'); - $version = trim(substr($stdout, strlen('git version '))); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $bad_versions = array( @@ -117,8 +117,6 @@ final class PhabricatorBinariesSetupCheck extends PhabricatorSetupCheck { 'for files added in rN (Subversion issue #2873), fixed in 1.7.2.', 'svn diff -c N'), ); - list($err, $stdout, $stderr) = exec_manual('svn --version --quiet'); - $version = trim($stdout); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $bad_versions = array( @@ -134,7 +132,6 @@ final class PhabricatorBinariesSetupCheck extends PhabricatorSetupCheck { 'in 2.2.1. Pushing fails with this version as well; see %s.', 'T3046#54922'), ); - $version = PhabricatorRepositoryVersion::getMercurialVersion(); break; } diff --git a/src/applications/config/check/PhabricatorElasticsearchSetupCheck.php b/src/applications/config/check/PhabricatorElasticsearchSetupCheck.php index 157db5e141..cd29ecdc78 100644 --- a/src/applications/config/check/PhabricatorElasticsearchSetupCheck.php +++ b/src/applications/config/check/PhabricatorElasticsearchSetupCheck.php @@ -7,6 +7,12 @@ final class PhabricatorElasticsearchSetupCheck extends PhabricatorSetupCheck { } protected function executeChecks() { + // TODO: Avoid fataling if we don't have a master database configured + // but have the MySQL search index configured. See T12965. + if (PhabricatorEnv::isReadOnly()) { + return; + } + $services = PhabricatorSearchService::getAllServices(); foreach ($services as $service) { diff --git a/src/applications/config/check/PhabricatorMySQLSetupCheck.php b/src/applications/config/check/PhabricatorMySQLSetupCheck.php index 8e2f39a504..dad9ba6d7b 100644 --- a/src/applications/config/check/PhabricatorMySQLSetupCheck.php +++ b/src/applications/config/check/PhabricatorMySQLSetupCheck.php @@ -9,7 +9,13 @@ final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck { protected function executeChecks() { $refs = PhabricatorDatabaseRef::getActiveDatabaseRefs(); foreach ($refs as $ref) { - $this->executeRefChecks($ref); + try { + $this->executeRefChecks($ref); + } catch (AphrontConnectionQueryException $ex) { + // If we're unable to connect to a host, just skip the checks for it. + // This can happen if we're restarting during a cluster incident. See + // T12966 for discussion. + } } } diff --git a/src/applications/config/constants/PhabricatorConfigGroupConstants.php b/src/applications/config/constants/PhabricatorConfigGroupConstants.php index d3200abf2b..7dba1ca808 100644 --- a/src/applications/config/constants/PhabricatorConfigGroupConstants.php +++ b/src/applications/config/constants/PhabricatorConfigGroupConstants.php @@ -28,8 +28,17 @@ abstract class PhabricatorConfigGroupConstants public static function getGroupURI($group) { $map = array( self::GROUP_CORE => '/', - self::GROUP_APPLICATION => pht('application/'), - self::GROUP_DEVELOPER => pht('developer/'), + self::GROUP_APPLICATION => 'application/', + self::GROUP_DEVELOPER => 'developer/', + ); + return idx($map, $group, '#'); + } + + public static function getGroupFullURI($group) { + $map = array( + self::GROUP_CORE => '/config/', + self::GROUP_APPLICATION => '/config/application/', + self::GROUP_DEVELOPER => '/config/developer/', ); return idx($map, $group, '#'); } diff --git a/src/applications/config/controller/PhabricatorConfigAllController.php b/src/applications/config/controller/PhabricatorConfigAllController.php index 421e0078cf..3a19eff006 100644 --- a/src/applications/config/controller/PhabricatorConfigAllController.php +++ b/src/applications/config/controller/PhabricatorConfigAllController.php @@ -49,29 +49,29 @@ final class PhabricatorConfigAllController )); $title = pht('Current Settings'); - - $crumbs = $this - ->buildApplicationCrumbs() - ->addTextCrumb($title) - ->setBorder(true); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true); + $header = $this->buildHeaderView($title); $nav = $this->buildSideNavView(); $nav->selectFilter('all/'); - $content = id(new PhabricatorConfigPageView()) + $view = $this->buildConfigBoxView( + pht('All Settings'), + $table); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($title) + ->setBorder(true); + + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent($table); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn($view); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } diff --git a/src/applications/config/controller/PhabricatorConfigApplicationController.php b/src/applications/config/controller/PhabricatorConfigApplicationController.php index 10f639adc7..b4f60982e7 100644 --- a/src/applications/config/controller/PhabricatorConfigApplicationController.php +++ b/src/applications/config/controller/PhabricatorConfigApplicationController.php @@ -11,28 +11,25 @@ final class PhabricatorConfigApplicationController $groups = PhabricatorApplicationConfigOptions::loadAll(); $apps_list = $this->buildConfigOptionsList($groups, 'apps'); + $apps_list = $this->buildConfigBoxView(pht('Applications'), $apps_list); $title = pht('Application Settings'); + $header = $this->buildHeaderView($title); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true); - - $crumbs = $this - ->buildApplicationCrumbs() - ->addTextCrumb(pht('Applications')) - ->setBorder(true); - - $content = id(new PhabricatorConfigPageView()) + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent($apps_list); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn($apps_list); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($title) + ->setBorder(true); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } private function buildConfigOptionsList(array $groups, $type) { diff --git a/src/applications/config/controller/PhabricatorConfigCacheController.php b/src/applications/config/controller/PhabricatorConfigCacheController.php index 91b0be27cf..a23ab1f9c1 100644 --- a/src/applications/config/controller/PhabricatorConfigCacheController.php +++ b/src/applications/config/controller/PhabricatorConfigCacheController.php @@ -9,16 +9,15 @@ final class PhabricatorConfigCacheController $nav = $this->buildSideNavView(); $nav->selectFilter('cache/'); + $purge_button = id(new PHUIButtonView()) + ->setText(pht('Purge Caches')) + ->setHref('/config/cache/purge/') + ->setTag('a') + ->setWorkflow(true) + ->setIcon('fa-exclamation-triangle'); + $title = pht('Cache Status'); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true); - - $crumbs = $this - ->buildApplicationCrumbs() - ->addTextCrumb(pht('Cache Status')) - ->setBorder(true); + $header = $this->buildHeaderView($title, $purge_button); $code_box = $this->renderCodeBox(); $data_box = $this->renderDataBox(); @@ -28,40 +27,27 @@ final class PhabricatorConfigCacheController $data_box, ); - $content = id(new PhabricatorConfigPageView()) + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($title) + ->setBorder(true); + + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent($page); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn($page); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } private function renderCodeBox() { $cache = PhabricatorOpcodeCacheSpec::getActiveCacheSpec(); - $properties = id(new PHUIPropertyListView()); - $this->renderCommonProperties($properties, $cache); - - $purge_button = id(new PHUIButtonView()) - ->setText(pht('Purge Caches')) - ->setHref('/config/cache/purge/') - ->setTag('a') - ->setWorkflow(true) - ->setIcon('fa-exclamation-triangle'); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Opcode Cache')) - ->addActionLink($purge_button); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($properties); + return $this->buildConfigBoxView(pht('Opcode Cache'), $properties); } private function renderDataBox() { @@ -109,11 +95,9 @@ final class PhabricatorConfigCacheController )); } - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Data Cache')) - ->addPropertyList($properties) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); + $properties = $this->buildConfigBoxView(pht('Data Cache'), $properties); + $table = $this->buildConfigBoxView(pht('Cache Storage'), $table); + return array($properties, $table); } private function renderCommonProperties( diff --git a/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php b/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php index 03dfa3f64c..43e5a15b9d 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php @@ -9,34 +9,31 @@ final class PhabricatorConfigClusterDatabasesController $title = pht('Cluster Database Status'); $doc_href = PhabricatorEnv::getDoclink('Cluster: Databases'); + $button = id(new PHUIButtonView()) + ->setIcon('fa-book') + ->setHref($doc_href) + ->setTag('a') + ->setText(pht('Documentation')); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true) - ->addActionLink( - id(new PHUIButtonView()) - ->setIcon('fa-book') - ->setHref($doc_href) - ->setTag('a') - ->setText(pht('Documentation'))); + $header = $this->buildHeaderView($title, $button); - $crumbs = $this - ->buildApplicationCrumbs() + $database_status = $this->buildClusterDatabaseStatus(); + $status = $this->buildConfigBoxView(pht('Status'), $database_status); + + $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); - $database_status = $this->buildClusterDatabaseStatus(); - - $content = id(new PhabricatorConfigPageView()) + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent($database_status); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn($status); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } private function buildClusterDatabaseStatus() { diff --git a/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php b/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php index cc0cc2bf45..443b51a903 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php @@ -9,34 +9,33 @@ final class PhabricatorConfigClusterNotificationsController $title = pht('Cluster Notifications'); $doc_href = PhabricatorEnv::getDoclink('Cluster: Notifications'); + $button = id(new PHUIButtonView()) + ->setIcon('fa-book') + ->setHref($doc_href) + ->setTag('a') + ->setText(pht('Documentation')); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true) - ->addActionLink( - id(new PHUIButtonView()) - ->setIcon('fa-book') - ->setHref($doc_href) - ->setTag('a') - ->setText(pht('Documentation'))); + $header = $this->buildHeaderView($title, $button); - $crumbs = $this - ->buildApplicationCrumbs() + $notification_status = $this->buildClusterNotificationStatus(); + $status = $this->buildConfigBoxView( + pht('Notifications Status'), + $notification_status); + + $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); - $notification_status = $this->buildClusterNotificationStatus(); - - $content = id(new PhabricatorConfigPageView()) + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent($notification_status); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn($status); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } private function buildClusterNotificationStatus() { diff --git a/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php b/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php index 3531c916a4..471c4cedf0 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php @@ -10,39 +10,39 @@ final class PhabricatorConfigClusterRepositoriesController $title = pht('Cluster Repository Status'); $doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories'); + $button = id(new PHUIButtonView()) + ->setIcon('fa-book') + ->setHref($doc_href) + ->setTag('a') + ->setText(pht('Documentation')); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true) - ->addActionLink( - id(new PHUIButtonView()) - ->setIcon('fa-book') - ->setHref($doc_href) - ->setTag('a') - ->setText(pht('Documentation'))); - - $crumbs = $this - ->buildApplicationCrumbs() - ->addTextCrumb(pht('Repository Servers')) - ->setBorder(true); + $header = $this->buildHeaderView($title, $button); $repository_status = $this->buildClusterRepositoryStatus(); - $repository_errors = $this->buildClusterRepositoryErrors(); + $repo_status = $this->buildConfigBoxView( + pht('Repository Status'), $repository_status); - $content = id(new PhabricatorConfigPageView()) + $repository_errors = $this->buildClusterRepositoryErrors(); + $repo_errors = $this->buildConfigBoxView( + pht('Repository Errors'), $repository_errors); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($title) + ->setBorder(true); + + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent( - array( - $repository_status, - $repository_errors, - )); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn(array( + $repo_status, + $repo_errors, + )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } private function buildClusterRepositoryStatus() { diff --git a/src/applications/config/controller/PhabricatorConfigClusterSearchController.php b/src/applications/config/controller/PhabricatorConfigClusterSearchController.php index 4d3ce407ab..cd00ef73a0 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterSearchController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterSearchController.php @@ -10,33 +10,30 @@ final class PhabricatorConfigClusterSearchController $title = pht('Cluster Search'); $doc_href = PhabricatorEnv::getDoclink('Cluster: Search'); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true) - ->addActionLink( - id(new PHUIButtonView()) - ->setIcon('fa-book') - ->setHref($doc_href) - ->setTag('a') - ->setText(pht('Documentation'))); + $button = id(new PHUIButtonView()) + ->setIcon('fa-book') + ->setHref($doc_href) + ->setTag('a') + ->setText(pht('Documentation')); - $crumbs = $this - ->buildApplicationCrumbs($nav) - ->addTextCrumb($title) - ->setBorder(true); + $header = $this->buildHeaderView($title, $button); $search_status = $this->buildClusterSearchStatus(); - $content = id(new PhabricatorConfigPageView()) + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($title) + ->setBorder(true); + + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent($search_status); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn($search_status); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } private function buildClusterSearchStatus() { @@ -105,15 +102,16 @@ final class PhabricatorConfigClusterSearchController ->setNoDataString(pht('No search servers are configured.')) ->setHeaders($head); - $view = id(new PHUIObjectBoxView()) - ->setHeaderText($service->getDisplayName()) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); + $view = $this->buildConfigBoxView(pht('Search Servers'), $table); - if ($stats_view) { - $view->addPropertyList($stats_view); + $stats = null; + if ($stats_view->hasAnyProperties()) { + $stats = $this->buildConfigBoxView( + pht('%s Stats', $service->getDisplayName()), + $stats_view); } - return $view; + + return array($stats, $view); } private function renderIndexStats($stats) { diff --git a/src/applications/config/controller/PhabricatorConfigController.php b/src/applications/config/controller/PhabricatorConfigController.php index 2abf2b3b31..ad5ea6cd27 100644 --- a/src/applications/config/controller/PhabricatorConfigController.php +++ b/src/applications/config/controller/PhabricatorConfigController.php @@ -8,10 +8,10 @@ abstract class PhabricatorConfigController extends PhabricatorController { public function buildSideNavView($filter = null, $for_app = false) { + $guide_href = new PhutilURI('/guides/'); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - $nav->addLabel(pht('Configuration')); $nav->addFilter('/', pht('Core Settings'), null, 'fa-gear'); $nav->addFilter('application/', @@ -46,7 +46,6 @@ abstract class PhabricatorConfigController extends PhabricatorController { pht('Search Servers'), null, 'fa-search'); $nav->addLabel(pht('Modules')); - $modules = PhabricatorConfigModule::getAllModules(); foreach ($modules as $key => $module) { $nav->addFilter('module/'.$key.'/', @@ -60,4 +59,37 @@ abstract class PhabricatorConfigController extends PhabricatorController { return $this->buildSideNavView(null, true)->getMenu(); } + public function buildHeaderView($text, $action = null) { + $viewer = $this->getViewer(); + + $file = PhabricatorFile::loadBuiltin($viewer, 'projects/v3/manage.png'); + $image = $file->getBestURI($file); + $header = id(new PHUIHeaderView()) + ->setHeader($text) + ->setProfileHeader(true) + ->setImage($image); + + if ($action) { + $header->addActionLink($action); + } + + return $header; + } + + public function buildConfigBoxView($title, $content, $action = null) { + $header = id(new PHUIHeaderView()) + ->setHeader($title); + + if ($action) { + $header->addActionItem($action); + } + + $view = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($content) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG); + + return $view; + } + } diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php index 7674d28f51..708a708043 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php @@ -12,10 +12,6 @@ final class PhabricatorConfigDatabaseIssueController $expect = $query->loadExpectedSchemata(); $comp_servers = $query->buildComparisonSchemata($expect, $actual); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Database Issues')); - $crumbs->setBorder(true); - // Collect all open issues. $issues = array(); foreach ($comp_servers as $ref_name => $comp) { @@ -158,24 +154,27 @@ final class PhabricatorConfigDatabaseIssueController } $title = pht('Database Issues'); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true); + $header = $this->buildHeaderView($title); $nav = $this->buildSideNavView(); $nav->selectFilter('dbissue/'); - $content = id(new PhabricatorConfigPageView()) + $view = $this->buildConfigBoxView(pht('Issues'), $table); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($title) + ->setBorder(true); + + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent($table); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn($view); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } } diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php index d49a546387..a67c57e6f9 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php @@ -132,27 +132,24 @@ final class PhabricatorConfigDatabaseStatusController } $doc_link = PhabricatorEnv::getDoclink('Managing Storage Adjustments'); + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-book') + ->setHref($doc_link) + ->setText(pht('Documentation')); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true) - ->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-book') - ->setHref($doc_link) - ->setText(pht('Learn More'))); + $header = $this->buildHeaderView($title, $button); - $content = id(new PhabricatorConfigPageView()) + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent($body); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn($body); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } @@ -221,11 +218,12 @@ final class PhabricatorConfigDatabaseStatusController )); $title = pht('Database Status'); - $properties = $this->buildProperties( array( ), $comp->getIssues()); + $properties = $this->buildConfigBoxView(pht('Properties'), $properties); + $table = $this->buildConfigBoxView(pht('Database'), $table); return $this->buildResponse($title, array($properties, $table)); } @@ -280,7 +278,7 @@ final class PhabricatorConfigDatabaseStatusController null, )); - $title = pht('Database: %s', $database_name); + $title = $database_name; $actual_database = $actual->getDatabase($database_name); if ($actual_database) { @@ -325,6 +323,9 @@ final class PhabricatorConfigDatabaseStatusController ), $database->getIssues()); + $properties = $this->buildConfigBoxView(pht('Properties'), $properties); + $table = $this->buildConfigBoxView(pht('Database'), $table); + return $this->buildResponse($title, array($properties, $table)); } @@ -503,7 +504,7 @@ final class PhabricatorConfigDatabaseStatusController null, )); - $title = pht('Database: %s.%s', $database_name, $table_name); + $title = pht('%s.%s', $database_name, $table_name); if ($actual_table) { $actual_collation = $actual_table->getCollation(); @@ -534,8 +535,14 @@ final class PhabricatorConfigDatabaseStatusController ), $table->getIssues()); + $box_header = pht('%s.%s', $database_name, $table_name); + + $properties = $this->buildConfigBoxView(pht('Properties'), $properties); + $table = $this->buildConfigBoxView(pht('Database'), $table_view); + $keys = $this->buildConfigBoxView(pht('Keys'), $keys_view); + return $this->buildResponse( - $title, array($properties, $table_view, $keys_view)); + $title, array($properties, $table, $keys)); } private function renderColumn( @@ -613,7 +620,7 @@ final class PhabricatorConfigDatabaseStatusController $title = pht( - 'Database Status: %s.%s.%s', + '%s.%s.%s', $database_name, $table_name, $column_name); @@ -671,6 +678,8 @@ final class PhabricatorConfigDatabaseStatusController ), $column->getIssues()); + $properties = $this->buildConfigBoxView(pht('Properties'), $properties); + return $this->buildResponse($title, $properties); } @@ -734,7 +743,7 @@ final class PhabricatorConfigDatabaseStatusController } $title = pht( - 'Database Status: %s.%s (%s)', + '%s.%s (%s)', $database_name, $table_name, $key_name); @@ -764,6 +773,8 @@ final class PhabricatorConfigDatabaseStatusController ), $key->getIssues()); + $properties = $this->buildConfigBoxView(pht('Properties'), $properties); + return $this->buildResponse($title, $properties); } diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 71b4499591..06df0de889 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -7,7 +7,6 @@ final class PhabricatorConfigEditController $viewer = $request->getViewer(); $key = $request->getURIData('key'); - $options = PhabricatorApplicationConfigOptions::loadAllOptions(); if (empty($options[$key])) { $ancient = PhabricatorExtraConfigSetupCheck::getAncientConfig(); @@ -104,44 +103,45 @@ final class PhabricatorConfigEditController $error_view = null; if ($errors) { $error_view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_ERROR) ->setErrors($errors); } + $doc_href = PhabricatorEnv::getDoclink( + 'Configuration Guide: Locked and Hidden Configuration'); + + $doc_link = phutil_tag( + 'a', + array( + 'href' => $doc_href, + 'target' => '_blank', + ), + pht('Learn more about locked and hidden options.')); + $status_items = array(); + $tag = null; if ($option->getHidden()) { + $tag = id(new PHUITagView()) + ->setName(pht('Hidden')) + ->setColor(PHUITagView::COLOR_GREY) + ->setBorder(PHUITagView::BORDER_NONE) + ->setType(PHUITagView::TYPE_SHADE); + $message = pht( 'This configuration is hidden and can not be edited or viewed from '. 'the web interface.'); - - $status_items[] = id(new PHUIStatusItemView()) - ->setIcon('fa-eye-slash red') - ->setTarget(phutil_tag('strong', array(), pht('Configuration Hidden'))) - ->setNote($message); + $status_items[] = id(new PHUIInfoView()) + ->appendChild(array($message, ' ', $doc_link)); } else if ($option->getLocked()) { + $tag = id(new PHUITagView()) + ->setName(pht('Locked')) + ->setColor(PHUITagView::COLOR_RED) + ->setBorder(PHUITagView::BORDER_NONE) + ->setType(PHUITagView::TYPE_SHADE); + $message = $option->getLockedMessage(); - - $status_items[] = id(new PHUIStatusItemView()) - ->setIcon('fa-lock red') - ->setTarget(phutil_tag('strong', array(), pht('Configuration Locked'))) - ->setNote($message); - } - - if ($status_items) { - $doc_href = PhabricatorEnv::getDoclink( - 'Configuration Guide: Locked and Hidden Configuration'); - - $doc_link = phutil_tag( - 'a', - array( - 'href' => $doc_href, - 'target' => '_blank', - ), - pht('Configuration Guide: Locked and Hidden Configuration')); - - $status_items[] = id(new PHUIStatusItemView()) - ->setIcon('fa-book') - ->setTarget(phutil_tag('strong', array(), pht('Learn More'))) - ->setNote($doc_link); + $status_items[] = id(new PHUIInfoView()) + ->appendChild(array($message, ' ', $doc_link)); } if ($option->getHidden() || $option->getLocked()) { @@ -168,18 +168,6 @@ final class PhabricatorConfigEditController ->setUser($viewer) ->addHiddenInput('issue', $request->getStr('issue')); - if ($status_items) { - $status_view = id(new PHUIStatusListView()); - - foreach ($status_items as $status_item) { - $status_view->addItem($status_item); - } - - $form->appendControl( - id(new AphrontFormMarkupControl()) - ->setValue($status_view)); - } - $description = $option->getDescription(); if (strlen($description)) { $description_view = new PHUIRemarkupView($viewer, $description); @@ -213,56 +201,66 @@ final class PhabricatorConfigEditController ->setValue(pht('Save Config Entry'))); } + $current_config = null; if (!$option->getHidden()) { - $form->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Current Configuration')) - ->setValue($this->renderDefaults($option, $config_entry))); + $current_config = $this->renderDefaults($option, $config_entry); + $current_config = $this->buildConfigBoxView( + pht('Current Configuration'), + $current_config); } $examples = $this->renderExamples($option); if ($examples) { - $form->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Examples')) - ->setValue($examples)); + $examples = $this->buildConfigBoxView( + pht('Examples'), + $examples); } - $title = pht('Edit Option: %s', $key); - $header_icon = 'fa-pencil'; - $short = pht('Edit'); + $title = $key; - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Config Option')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - if ($error_view) { - $form_box->setInfoView($error_view); + $box_header = array(); + if ($group) { + $box_header[] = phutil_tag( + 'a', + array( + 'href' => $group_uri, + ), + $group->getName()); + $box_header[] = " \xC2\xBB "; } + $box_header[] = $key; $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Config'), $this->getApplicationURI()); - if ($group) { $crumbs->addTextCrumb($group->getName(), $group_uri); } - $crumbs->addTextCrumb($key, '/config/edit/'.$key); $crumbs->setBorder(true); + $form_box = $this->buildConfigBoxView($box_header, $form, $tag); + $timeline = $this->buildTransactionTimeline( $config_entry, new PhabricatorConfigTransactionQuery()); $timeline->setShouldTerminate(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); + $nav = $this->buildSideNavView(); + $nav->selectFilter($group_uri); + + $header = $this->buildHeaderView($title); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFooter($form_box); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn(array( + $error_view, + $form_box, + $status_items, + $examples, + $current_config, + )); return $this->newPage() ->setTitle($title) @@ -274,6 +272,56 @@ final class PhabricatorConfigEditController PhabricatorConfigOption $option, AphrontRequest $request) { + $type = $option->newOptionType(); + if ($type) { + $is_set = $type->isValuePresentInRequest($option, $request); + if ($is_set) { + $value = $type->readValueFromRequest($option, $request); + + $errors = array(); + try { + $canonical_value = $type->newValueFromRequestValue( + $option, + $value); + $type->validateStoredValue($option, $canonical_value); + $xaction = $type->newTransaction($option, $canonical_value); + } catch (PhabricatorConfigValidationException $ex) { + $errors[] = $ex->getMessage(); + $xaction = null; + } catch (Exception $ex) { + // NOTE: Some older validators throw bare exceptions. Purely in good + // taste, it would be nice to convert these at some point. + $errors[] = $ex->getMessage(); + $xaction = null; + } + + return array( + $errors ? pht('Invalid') : null, + $errors, + $value, + $xaction, + ); + } else { + $delete_xaction = id(new PhabricatorConfigTransaction()) + ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT) + ->setNewValue( + array( + 'deleted' => true, + 'value' => null, + )); + + return array( + null, + array(), + null, + $delete_xaction, + ); + } + } + + // TODO: If we missed on the new `PhabricatorConfigType` map, fall back + // to the old semi-modular, semi-hacky way of doing things. + $xaction = new PhabricatorConfigTransaction(); $xaction->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT); @@ -284,92 +332,10 @@ final class PhabricatorConfigEditController $info = $option->getCustomObject()->readRequest($option, $request); list($e_value, $errors, $set_value, $value) = $info; } else { - $value = $request->getStr('value'); - if (!strlen($value)) { - $value = null; - - $xaction->setNewValue( - array( - 'deleted' => true, - 'value' => null, - )); - - return array($e_value, $errors, $value, $xaction); - } - - $type = $option->getType(); - $set_value = null; - - switch ($type) { - case 'int': - if (preg_match('/^-?[0-9]+$/', trim($value))) { - $set_value = (int)$value; - } else { - $e_value = pht('Invalid'); - $errors[] = pht('Value must be an integer.'); - } - break; - case 'string': - case 'enum': - $set_value = (string)$value; - break; - case 'list': - case 'list': - $set_value = phutil_split_lines( - $request->getStr('value'), - $retain_endings = false); - - foreach ($set_value as $key => $v) { - if (!strlen($v)) { - unset($set_value[$key]); - } - } - $set_value = array_values($set_value); - - break; - case 'set': - $set_value = array_fill_keys($request->getStrList('value'), true); - break; - case 'bool': - switch ($value) { - case 'true': - $set_value = true; - break; - case 'false': - $set_value = false; - break; - default: - $e_value = pht('Invalid'); - $errors[] = pht('Value must be boolean, "true" or "false".'); - break; - } - break; - case 'class': - if (!class_exists($value)) { - $e_value = pht('Invalid'); - $errors[] = pht('Class does not exist.'); - } else { - $base = $option->getBaseClass(); - if (!is_subclass_of($value, $base)) { - $e_value = pht('Invalid'); - $errors[] = pht('Class is not of valid type.'); - } else { - $set_value = $value; - } - } - break; - default: - $json = json_decode($value, true); - if ($json === null && strtolower($value) != 'null') { - $e_value = pht('Invalid'); - $errors[] = pht( - 'The given value must be valid JSON. This means, among '. - 'other things, that you must wrap strings in double-quotes.'); - } else { - $set_value = $json; - } - break; - } + throw new Exception( + pht( + 'Unknown configuration option type "%s".', + $option->getType())); } if (!$errors) { @@ -390,30 +356,22 @@ final class PhabricatorConfigEditController PhabricatorConfigEntry $entry, $value) { + $type = $option->newOptionType(); + if ($type) { + return $type->newDisplayValue($option, $value); + } + if ($option->isCustomType()) { return $option->getCustomObject()->getDisplayValue( $option, $entry, $value); - } else { - $type = $option->getType(); - switch ($type) { - case 'int': - case 'string': - case 'enum': - case 'class': - return $value; - case 'bool': - return $value ? 'true' : 'false'; - case 'list': - case 'list': - return implode("\n", nonempty($value, array())); - case 'set': - return implode("\n", nonempty(array_keys($value), array())); - default: - return PhabricatorConfigJSON::prettyPrintJSON($value); - } } + + throw new Exception( + pht( + 'Unknown configuration option type "%s".', + $option->getType())); } private function renderControls( @@ -421,75 +379,24 @@ final class PhabricatorConfigEditController $display_value, $e_value) { + $type = $option->newOptionType(); + if ($type) { + return $type->newControls( + $option, + $display_value, + $e_value); + } + if ($option->isCustomType()) { $controls = $option->getCustomObject()->renderControls( $option, $display_value, $e_value); } else { - $type = $option->getType(); - switch ($type) { - case 'int': - case 'string': - $control = id(new AphrontFormTextControl()); - break; - case 'bool': - $control = id(new AphrontFormSelectControl()) - ->setOptions( - array( - '' => pht('(Use Default)'), - 'true' => idx($option->getBoolOptions(), 0), - 'false' => idx($option->getBoolOptions(), 1), - )); - break; - case 'enum': - $options = array_mergev( - array( - array('' => pht('(Use Default)')), - $option->getEnumOptions(), - )); - $control = id(new AphrontFormSelectControl()) - ->setOptions($options); - break; - case 'class': - $symbols = id(new PhutilSymbolLoader()) - ->setType('class') - ->setAncestorClass($option->getBaseClass()) - ->setConcreteOnly(true) - ->selectSymbolsWithoutLoading(); - $names = ipull($symbols, 'name', 'name'); - asort($names); - $names = array( - '' => pht('(Use Default)'), - ) + $names; - - $control = id(new AphrontFormSelectControl()) - ->setOptions($names); - break; - case 'list': - case 'list': - $control = id(new AphrontFormTextAreaControl()) - ->setCaption(pht('Separate values with newlines.')); - break; - case 'set': - $control = id(new AphrontFormTextAreaControl()) - ->setCaption(pht('Separate values with newlines or commas.')); - break; - default: - $control = id(new AphrontFormTextAreaControl()) - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) - ->setCustomClass('PhabricatorMonospaced') - ->setCaption(pht('Enter value in JSON.')); - break; - } - - $control - ->setLabel(pht('Database Value')) - ->setError($e_value) - ->setValue($display_value) - ->setName('value'); - - $controls = array($control); + throw new Exception( + pht( + 'Unknown configuration option type "%s".', + $option->getType())); } return $controls; @@ -517,7 +424,7 @@ final class PhabricatorConfigEditController } } - $table[] = phutil_tag('tr', array(), array( + $table[] = phutil_tag('tr', array('class' => 'column-labels'), array( phutil_tag('th', array(), $description), phutil_tag('td', array(), $value), )); @@ -529,6 +436,8 @@ final class PhabricatorConfigEditController 'table', array( 'class' => 'config-option-table', + 'cellspacing' => '0', + 'cellpadding' => '0', ), $table); } @@ -588,6 +497,8 @@ final class PhabricatorConfigEditController 'table', array( 'class' => 'config-option-table', + 'cellspacing' => '0', + 'cellpadding' => '0', ), $table); } diff --git a/src/applications/config/controller/PhabricatorConfigGroupController.php b/src/applications/config/controller/PhabricatorConfigGroupController.php index 6e713f7eab..920b2092ac 100644 --- a/src/applications/config/controller/PhabricatorConfigGroupController.php +++ b/src/applications/config/controller/PhabricatorConfigGroupController.php @@ -13,7 +13,7 @@ final class PhabricatorConfigGroupController return new Aphront404Response(); } - $group_uri = PhabricatorConfigGroupConstants::getGroupURI( + $group_uri = PhabricatorConfigGroupConstants::getGroupFullURI( $options->getGroup()); $group_name = PhabricatorConfigGroupConstants::getGroupShortName( $options->getGroup()); @@ -22,28 +22,28 @@ final class PhabricatorConfigGroupController $nav->selectFilter($group_uri); $title = pht('%s Configuration', $options->getName()); + $header = $this->buildHeaderView($title); $list = $this->buildOptionList($options->getOptions()); + $group_url = phutil_tag('a', array('href' => $group_uri), $group_name); - $crumbs = $this - ->buildApplicationCrumbs() + $box_header = pht("%s \xC2\xBB %s", $group_url, $options->getName()); + $view = $this->buildConfigBoxView($box_header, $list); + + $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($group_name, $this->getApplicationURI($group_uri)) ->addTextCrumb($options->getName()) ->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true); - - $content = id(new PhabricatorConfigPageView()) + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent($list); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn($view); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } private function buildOptionList(array $options) { @@ -77,13 +77,11 @@ final class PhabricatorConfigGroupController ->setHref('/config/edit/'.$option->getKey().'/') ->addAttribute($summary); - $label = pht('Current Value:'); $color = null; $db_value = idx($db_values, $option->getKey()); if ($db_value && !$db_value->getIsDeleted()) { $item->setEffect('visited'); $color = 'violet'; - $label = pht('Customized Value:'); } if ($option->getHidden()) { @@ -91,6 +89,8 @@ final class PhabricatorConfigGroupController $item->setDisabled(true); } else if ($option->getLocked()) { $item->setStatusIcon('fa-lock '.$color, pht('Locked')); + } else if ($color) { + $item->setStatusIcon('fa-pencil '.$color, pht('Editable')); } else { $item->setStatusIcon('fa-pencil-square-o '.$color, pht('Editable')); } @@ -102,14 +102,13 @@ final class PhabricatorConfigGroupController $current_value = phutil_tag( 'div', array( - 'class' => 'config-options-current-value', + 'class' => 'config-options-current-value '.$color, ), array( - phutil_tag('span', array(), $label), - ' '.$current_value, + $current_value, )); - $item->appendChild($current_value); + $item->setSideColumn($current_value); } $list->addItem($item); diff --git a/src/applications/config/controller/PhabricatorConfigHistoryController.php b/src/applications/config/controller/PhabricatorConfigHistoryController.php index eb7fbf607b..238d09234d 100644 --- a/src/applications/config/controller/PhabricatorConfigHistoryController.php +++ b/src/applications/config/controller/PhabricatorConfigHistoryController.php @@ -29,28 +29,25 @@ final class PhabricatorConfigHistoryController $object->willRenderTimeline($timeline, $this->getRequest()); $title = pht('Settings History'); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title); - $crumbs->setBorder(true); + $header = $this->buildHeaderView($title); $nav = $this->buildSideNavView(); $nav->selectFilter('history/'); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($title) + ->setBorder(true); - $content = id(new PhabricatorConfigPageView()) + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent($timeline); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn($timeline); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } } diff --git a/src/applications/config/controller/PhabricatorConfigIssueListController.php b/src/applications/config/controller/PhabricatorConfigIssueListController.php index 869f53c223..770f79a9f2 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueListController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueListController.php @@ -43,34 +43,34 @@ final class PhabricatorConfigIssueListController } $title = pht('Setup Issues'); + $header = $this->buildHeaderView($title); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true); - - $crumbs = $this - ->buildApplicationCrumbs() - ->addTextCrumb(pht('Setup Issues')) - ->setBorder(true); - - $page = array( - $no_issues, + $issue_list = array( $important, $php, $mysql, $other, ); - $content = id(new PhabricatorConfigPageView()) + $issue_list = $this->buildConfigBoxView(pht('Issues'), $issue_list); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($title) + ->setBorder(true); + + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent($page); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn(array( + $no_issues, + $issue_list, + )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } private function buildIssueList(array $issues, $group, $fonticon) { diff --git a/src/applications/config/controller/PhabricatorConfigIssueViewController.php b/src/applications/config/controller/PhabricatorConfigIssueViewController.php index 43946a3e7b..29c9078413 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueViewController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueViewController.php @@ -36,6 +36,8 @@ final class PhabricatorConfigIssueViewController $title = $issue->getShortName(); } + $header = $this->buildHeaderView($title); + $crumbs = $this ->buildApplicationCrumbs() ->setBorder(true) @@ -43,12 +45,16 @@ final class PhabricatorConfigIssueViewController ->addTextCrumb($title, $request->getRequestURI()) ->setBorder(true); + $content = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn($content); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } private function renderIssue(PhabricatorSetupIssue $issue) { diff --git a/src/applications/config/controller/PhabricatorConfigListController.php b/src/applications/config/controller/PhabricatorConfigListController.php index 517fe014a9..1a136ea416 100644 --- a/src/applications/config/controller/PhabricatorConfigListController.php +++ b/src/applications/config/controller/PhabricatorConfigListController.php @@ -11,28 +11,25 @@ final class PhabricatorConfigListController $groups = PhabricatorApplicationConfigOptions::loadAll(); $core_list = $this->buildConfigOptionsList($groups, 'core'); + $core_list = $this->buildConfigBoxView(pht('Core'), $core_list); $title = pht('Core Settings'); + $header = $this->buildHeaderView($title); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true); - - $crumbs = $this - ->buildApplicationCrumbs() - ->addTextCrumb(pht('Core')) + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($title) ->setBorder(true); - $content = id(new PhabricatorConfigPageView()) + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent($core_list); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn($core_list); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } private function buildConfigOptionsList(array $groups, $type) { diff --git a/src/applications/config/controller/PhabricatorConfigModuleController.php b/src/applications/config/controller/PhabricatorConfigModuleController.php index e10d70561b..63cc5b3843 100644 --- a/src/applications/config/controller/PhabricatorConfigModuleController.php +++ b/src/applications/config/controller/PhabricatorConfigModuleController.php @@ -16,27 +16,26 @@ final class PhabricatorConfigModuleController $content = $module->renderModuleStatus($request); $title = $module->getModuleName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title); - $crumbs->setBorder(true); - $nav = $this->buildSideNavView(); $nav->selectFilter('module/'.$key.'/'); + $header = $this->buildHeaderView($title); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true); + $view = $this->buildConfigBoxView($title, $content); - $content = id(new PhabricatorConfigPageView()) + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($title) + ->setBorder(true); + + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent($content); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn($view); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } } diff --git a/src/applications/config/controller/PhabricatorConfigVersionController.php b/src/applications/config/controller/PhabricatorConfigVersionController.php index 8f43192b3b..8a87dec5cc 100644 --- a/src/applications/config/controller/PhabricatorConfigVersionController.php +++ b/src/applications/config/controller/PhabricatorConfigVersionController.php @@ -7,31 +7,30 @@ final class PhabricatorConfigVersionController $viewer = $request->getViewer(); $title = pht('Version Information'); - - $crumbs = $this - ->buildApplicationCrumbs() - ->addTextCrumb($title) - ->setBorder(true); - $versions = $this->renderModuleStatus($viewer); $nav = $this->buildSideNavView(); $nav->selectFilter('version/'); + $header = $this->buildHeaderView($title); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setProfileHeader(true); + $view = $this->buildConfigBoxView( + pht('Installed Versions'), + $versions); - $content = id(new PhabricatorConfigPageView()) + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($title) + ->setBorder(true); + + $content = id(new PHUITwoColumnView()) ->setHeader($header) - ->setContent($versions); + ->setNavigation($nav) + ->setFixed(true) + ->setMainColumn($view); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($nav) - ->appendChild($content) - ->addClass('white-background'); + ->appendChild($content); } @@ -64,6 +63,29 @@ final class PhabricatorConfigVersionController $version_from_file); } + $binaries = PhutilBinaryAnalyzer::getAllBinaries(); + foreach ($binaries as $binary) { + if (!$binary->isBinaryAvailable()) { + $binary_info = pht('Not Available'); + } else { + $version = $binary->getBinaryVersion(); + $path = $binary->getBinaryPath(); + if ($path === null && $version === null) { + $binary_info = pht('-'); + } else if ($path === null) { + $binary_info = $version; + } else if ($version === null) { + $binary_info = pht('- at %s', $path); + } else { + $binary_info = pht('%s at %s', $version, $path); + } + } + + $version_property_list->addProperty( + $binary->getBinaryName(), + $binary_info); + } + return $version_property_list; } diff --git a/src/applications/config/custom/PhabricatorCustomUIFooterConfigType.php b/src/applications/config/custom/PhabricatorCustomUIFooterConfigType.php new file mode 100644 index 0000000000..d9961e7454 --- /dev/null +++ b/src/applications/config/custom/PhabricatorCustomUIFooterConfigType.php @@ -0,0 +1,41 @@ + $item) { + if (!is_array($item)) { + throw new Exception( + pht( + 'Footer item with index "%s" is invalid: each item must be a '. + 'dictionary describing a footer item.', + $idx)); + } + + try { + PhutilTypeSpec::checkMap( + $item, + array( + 'name' => 'string', + 'href' => 'optional string', + )); + } catch (Exception $ex) { + throw new Exception( + pht( + 'Footer item with index "%s" is invalid: %s', + $idx, + $ex->getMessage())); + } + } + } + + +} diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index 4511865785..9f50d2eeed 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -59,84 +59,37 @@ final class PhabricatorConfigManagementSetWorkflow $option = $options[$key]; - $type = $option->getType(); - switch ($type) { - case 'string': - case 'class': - case 'enum': - $value = (string)$value; - break; - case 'int': - if (!ctype_digit($value)) { - throw new PhutilArgumentUsageException( - pht( - "Config key '%s' is of type '%s'. Specify an integer.", - $key, - $type)); - } - $value = (int)$value; - break; - case 'bool': - if ($value == 'true') { - $value = true; - } else if ($value == 'false') { - $value = false; - } else { - throw new PhutilArgumentUsageException( - pht( - "Config key '%s' is of type '%s'. Specify '%s' or '%s'.", - $key, - $type, - 'true', - 'false')); - } - break; - default: - $value = json_decode($value, true); - if (!is_array($value)) { - switch ($type) { - case 'set': - $command = csprintf( - './bin/config set %R %s', - $key, - '{"value1": true, "value2": true}'); - - $message = sprintf( - "%s\n\n %s\n", - pht( - 'Config key "%s" is of type "%s". Specify it in JSON. '. - 'For example:', - $key, - $type), - $command); - break; - default: - if (preg_match('/^listnewOptionType(); + if ($type) { + try { + $value = $type->newValueFromCommandLineValue( + $option, + $value); + $type->validateStoredValue($option, $value); + } catch (PhabricatorConfigValidationException $ex) { + throw new PhutilArgumentUsageException($ex->getMessage()); + } + } else { + // NOTE: For now, this handles both "wild" values and custom types. + $type = $option->getType(); + switch ($type) { + default: + $value = json_decode($value, true); + if (!is_array($value)) { + switch ($type) { + default: $message = pht( 'Config key "%s" is of type "%s". Specify it in JSON.', $key, $type); - } - break; + break; + } + throw new PhutilArgumentUsageException($message); } - throw new PhutilArgumentUsageException($message); - } - break; + break; + } } + $use_database = $args->getArg('database'); if ($option->getLocked() && $use_database) { throw new PhutilArgumentUsageException( diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 34d74e88ab..49cd9eb17e 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -20,133 +20,34 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { return; } + $type = $option->newOptionType(); + if ($type) { + try { + $type->validateStoredValue($option, $value); + $this->didValidateOption($option, $value); + } catch (PhabricatorConfigValidationException $ex) { + throw $ex; + } catch (Exception $ex) { + // If custom validators threw exceptions other than validation + // exceptions, convert them to validation exceptions so we repair the + // configuration and raise an error. + throw new PhabricatorConfigValidationException($ex->getMessage()); + } + + return; + } + if ($option->isCustomType()) { try { return $option->getCustomObject()->validateOption($option, $value); } catch (Exception $ex) { - // If custom validators threw exceptions, convert them to configuation - // validation exceptions so we repair the configuration and raise - // an error. throw new PhabricatorConfigValidationException($ex->getMessage()); } - } - - switch ($option->getType()) { - case 'bool': - if ($value !== true && - $value !== false) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' is of type bool, but value is not true or false.", - $option->getKey())); - } - break; - case 'int': - if (!is_int($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' is of type int, but value is not an integer.", - $option->getKey())); - } - break; - case 'string': - if (!is_string($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' is of type string, but value is not a string.", - $option->getKey())); - } - break; - case 'class': - $symbols = id(new PhutilSymbolLoader()) - ->setType('class') - ->setAncestorClass($option->getBaseClass()) - ->setConcreteOnly(true) - ->selectSymbolsWithoutLoading(); - $names = ipull($symbols, 'name', 'name'); - if (empty($names[$value])) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' value must name a class extending '%s'.", - $option->getKey(), - $option->getBaseClass())); - } - break; - case 'set': - $valid = true; - if (!is_array($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a set, but value is not an array.", - $option->getKey())); - } - foreach ($value as $v) { - if ($v !== true) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a set, but array contains values other ". - "than 'true'.", - $option->getKey())); - } - } - break; - case 'list': - $valid = true; - if (!is_array($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of regular expressions, but value ". - "is not an array.", - $option->getKey())); - } - if ($value && array_keys($value) != range(0, count($value) - 1)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of regular expressions, but the ". - "value is a map with unnatural keys.", - $option->getKey())); - } - foreach ($value as $v) { - $ok = @preg_match($v, ''); - if ($ok === false) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of regular expressions, but the ". - "value '%s' is not a valid regular expression.", - $option->getKey(), - $v)); - } - } - break; - case 'list': - $valid = true; - if (!is_array($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of strings, but value is not ". - "an array.", - $option->getKey())); - } - if ($value && array_keys($value) != range(0, count($value) - 1)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of strings, but the value is a ". - "map with unnatural keys.", - $option->getKey())); - } - foreach ($value as $v) { - if (!is_string($v)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of strings, but it contains one ". - "or more non-strings.", - $option->getKey())); - } - } - break; - case 'wild': - default: - break; + } else { + throw new Exception( + pht( + 'Unknown configuration option type "%s".', + $option->getType())); } $this->didValidateOption($option, $value); diff --git a/src/applications/config/option/PhabricatorClusterConfigOptions.php b/src/applications/config/option/PhabricatorClusterConfigOptions.php index c3636c31e0..ca4153a8b2 100644 --- a/src/applications/config/option/PhabricatorClusterConfigOptions.php +++ b/src/applications/config/option/PhabricatorClusterConfigOptions.php @@ -20,7 +20,7 @@ final class PhabricatorClusterConfigOptions } public function getOptions() { - $databases_type = 'custom:PhabricatorClusterDatabasesConfigOptionType'; + $databases_type = 'cluster.databases'; $databases_help = $this->deformat(pht(<<deformat(pht(<<type; } + public function newOptionType() { + $type_key = $this->getType(); + $type_map = PhabricatorConfigType::getAllTypes(); + return idx($type_map, $type_key); + } + public function isCustomType() { return !strncmp($this->getType(), 'custom:', 7); } diff --git a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php index 556b1b169a..6c846daa57 100644 --- a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php +++ b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php @@ -138,15 +138,19 @@ EODOC , 'metamta.public-replies')); + $adapter_doc_href = PhabricatorEnv::getDoclink( + 'Configuring Outbound Email'); + $adapter_doc_name = pht('Configuring Outbound Email'); $adapter_description = $this->deformat(pht(<<deformat(pht(<<newOption('metamta.user-address-format', 'enum', 'full') ->setEnumOptions( array( - 'short' => 'short', - 'real' => 'real', - 'full' => 'full', + 'short' => pht('Short'), + 'real' => pht('Real'), + 'full' => pht('Full'), )) ->setSummary(pht('Control how Phabricator renders user names in mail.')) ->setDescription($address_description) diff --git a/src/applications/config/option/PhabricatorNotificationConfigOptions.php b/src/applications/config/option/PhabricatorNotificationConfigOptions.php index 34fae7c184..9f06bc3c2d 100644 --- a/src/applications/config/option/PhabricatorNotificationConfigOptions.php +++ b/src/applications/config/option/PhabricatorNotificationConfigOptions.php @@ -20,7 +20,7 @@ final class PhabricatorNotificationConfigOptions } public function getOptions() { - $servers_type = 'custom:PhabricatorNotificationServersConfigOptionType'; + $servers_type = 'cluster.notifications'; $servers_help = $this->deformat(pht(<< 'blindigo', - 'red' => 'red', - 'blue' => 'blue', - 'green' => 'green', - 'indigo' => 'indigo', - 'dark' => 'dark', + 'blindigo' => pht('Blindigo'), + 'red' => pht('Red'), + 'blue' => pht('Blue'), + 'green' => pht('Green'), + 'indigo' => pht('Indigo'), + 'dark' => pht('Dark'), ); $example = <<newOption('ui.header-color', 'enum', 'blindigo') @@ -63,7 +64,7 @@ EOJSON; "Phabricator logo in the site header.\n\n". " - **Wordmark**: Choose new text to display next to the logo. ". "By default, the header displays //Phabricator//.\n\n")), - $this->newOption('ui.footer-items', 'list', array()) + $this->newOption('ui.footer-items', $footer_type, array()) ->setSummary( pht( 'Allows you to add footer links on most pages.')) diff --git a/src/applications/config/schema/PhabricatorConfigCoreSchemaSpec.php b/src/applications/config/schema/PhabricatorConfigCoreSchemaSpec.php index 4219ad2cb4..0d8d743649 100644 --- a/src/applications/config/schema/PhabricatorConfigCoreSchemaSpec.php +++ b/src/applications/config/schema/PhabricatorConfigCoreSchemaSpec.php @@ -42,5 +42,13 @@ final class PhabricatorConfigCoreSchemaSpec )); } + $ferret_objects = id(new PhutilClassMapQuery()) + ->setAncestorClass('PhabricatorFerretInterface') + ->execute(); + + foreach ($ferret_objects as $ferret_object) { + $engine = $ferret_object->newFerretEngine(); + $this->buildFerretIndexSchema($engine); + } } } diff --git a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php index 49a99ab03a..b24adf27cd 100644 --- a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php @@ -55,6 +55,26 @@ abstract class PhabricatorConfigSchemaSpec extends Phobject { $object->getSchemaKeys()); } + protected function buildFerretIndexSchema(PhabricatorFerretEngine $engine) { + $this->buildRawSchema( + $engine->getApplicationName(), + $engine->getDocumentTableName(), + $engine->getDocumentSchemaColumns(), + $engine->getDocumentSchemaKeys()); + + $this->buildRawSchema( + $engine->getApplicationName(), + $engine->getFieldTableName(), + $engine->getFieldSchemaColumns(), + $engine->getFieldSchemaKeys()); + + $this->buildRawSchema( + $engine->getApplicationName(), + $engine->getNgramsTableName(), + $engine->getNgramsSchemaColumns(), + $engine->getNgramsSchemaKeys()); + } + protected function buildRawSchema( $database_name, $table_name, diff --git a/src/applications/config/type/PhabricatorBoolConfigType.php b/src/applications/config/type/PhabricatorBoolConfigType.php new file mode 100644 index 0000000000..840d8b4015 --- /dev/null +++ b/src/applications/config/type/PhabricatorBoolConfigType.php @@ -0,0 +1,62 @@ +newException( + pht( + 'Value for option "%s" of type "%s" must be either '. + '"true" or "false".', + $option->getKey(), + $this->getTypeKey())); + } + + return ($value === 'true'); + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + + if ($value) { + return 'true'; + } else { + return 'false'; + } + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + + if (!is_bool($value)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a boolean.', + $option->getKey(), + $this->getTypeKey())); + } + } + + protected function newControl(PhabricatorConfigOption $option) { + $bool_map = $option->getBoolOptions(); + + $map = array( + '' => pht('(Use Default)'), + ) + array( + 'true' => idx($bool_map, 0), + 'false' => idx($bool_map, 1), + ); + + return id(new AphrontFormSelectControl()) + ->setOptions($map); + } +} diff --git a/src/applications/config/type/PhabricatorClassConfigType.php b/src/applications/config/type/PhabricatorClassConfigType.php new file mode 100644 index 0000000000..0ec5e0eb5e --- /dev/null +++ b/src/applications/config/type/PhabricatorClassConfigType.php @@ -0,0 +1,76 @@ +newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a string.', + $option->getKey(), + $this->getTypeKey())); + } + + $base = $option->getBaseClass(); + $map = $this->getClassOptions($option); + + try { + $ok = class_exists($value); + } catch (Exception $ex) { + $ok = false; + } + + if (!$ok) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not the '. + 'name of a known class. Valid selections are: %s.', + $option->getKey(), + $this->getTypeKey(), + implode(', ', array_keys($map)))); + } + + if (!isset($map[$value])) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the current value ("%s") is not '. + 'a known, concrete subclass of base class "%s". Valid selections '. + 'are: %s.', + $option->getKey(), + $this->getTypeKey(), + $value, + $base, + implode(', ', array_keys($map)))); + } + } + + protected function newControl(PhabricatorConfigOption $option) { + $map = array( + '' => pht('(Use Default)'), + ) + $this->getClassOptions($option); + + return id(new AphrontFormSelectControl()) + ->setOptions($map); + } + + private function getClassOptions(PhabricatorConfigOption $option) { + $symbols = id(new PhutilSymbolLoader()) + ->setType('class') + ->setAncestorClass($option->getBaseClass()) + ->setConcreteOnly(true) + ->selectSymbolsWithoutLoading(); + + $map = ipull($symbols, 'name', 'name'); + asort($map); + + return $map; + } + +} diff --git a/src/applications/config/type/PhabricatorConfigType.php b/src/applications/config/type/PhabricatorConfigType.php new file mode 100644 index 0000000000..2968d00d18 --- /dev/null +++ b/src/applications/config/type/PhabricatorConfigType.php @@ -0,0 +1,111 @@ +getPhobjectClassConstant('TYPEKEY'); + } + + final public static function getAllTypes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getTypeKey') + ->execute(); + } + + public function isValuePresentInRequest( + PhabricatorConfigOption $option, + AphrontRequest $request) { + $http_type = $this->newHTTPParameterType(); + return $http_type->getExists($request, 'value'); + } + + public function readValueFromRequest( + PhabricatorConfigOption $option, + AphrontRequest $request) { + $http_type = $this->newHTTPParameterType(); + return $http_type->getValue($request, 'value'); + } + + abstract protected function newHTTPParameterType(); + + public function newTransaction( + PhabricatorConfigOption $option, + $value) { + + $xaction_value = $this->newTransactionValue($option, $value); + + return id(new PhabricatorConfigTransaction()) + ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT) + ->setNewValue( + array( + 'deleted' => false, + 'value' => $xaction_value, + )); + } + + protected function newTransactionValue( + PhabricatorConfigOption $option, + $value) { + return $value; + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + return $value; + } + + public function newControls( + PhabricatorConfigOption $option, + $value, + $error) { + + $control = $this->newControl($option) + ->setError($error) + ->setLabel(pht('Database Value')) + ->setName('value'); + + $value = $this->newControlValue($option, $value); + $control->setValue($value); + + return array( + $control, + ); + } + + abstract protected function newControl(PhabricatorConfigOption $option); + + protected function newControlValue( + PhabricatorConfigOption $option, + $value) { + return $value; + } + + protected function newException($message) { + return new PhabricatorConfigValidationException($message); + } + + public function newValueFromRequestValue( + PhabricatorConfigOption $option, + $value) { + return $this->newCanonicalValue($option, $value); + } + + public function newValueFromCommandLineValue( + PhabricatorConfigOption $option, + $value) { + return $this->newCanonicalValue($option, $value); + } + + protected function newCanonicalValue( + PhabricatorConfigOption $option, + $value) { + return $value; + } + + abstract public function validateStoredValue( + PhabricatorConfigOption $option, + $value); + +} diff --git a/src/applications/config/type/PhabricatorEnumConfigType.php b/src/applications/config/type/PhabricatorEnumConfigType.php new file mode 100644 index 0000000000..0d1d958b2e --- /dev/null +++ b/src/applications/config/type/PhabricatorEnumConfigType.php @@ -0,0 +1,43 @@ +newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a string.', + $option->getKey(), + $this->getTypeKey())); + } + + $map = $option->getEnumOptions(); + if (!isset($map[$value])) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the current value ("%s") is not '. + 'among the set of valid values: %s.', + $option->getKey(), + $this->getTypeKey(), + $value, + implode(', ', array_keys($map)))); + } + } + + protected function newControl(PhabricatorConfigOption $option) { + $map = array( + '' => pht('(Use Default)'), + ) + $option->getEnumOptions(); + + return id(new AphrontFormSelectControl()) + ->setOptions($map); + } + +} diff --git a/src/applications/config/type/PhabricatorIntConfigType.php b/src/applications/config/type/PhabricatorIntConfigType.php new file mode 100644 index 0000000000..807104f1a5 --- /dev/null +++ b/src/applications/config/type/PhabricatorIntConfigType.php @@ -0,0 +1,36 @@ +newException( + pht( + 'Value for option "%s" must be an integer.', + $option->getKey())); + } + + return (int)$value; + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + + if (!is_int($value)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'an integer.', + $option->getKey(), + $this->getTypeKey())); + } + } + +} diff --git a/src/applications/config/type/PhabricatorJSONConfigType.php b/src/applications/config/type/PhabricatorJSONConfigType.php new file mode 100644 index 0000000000..b1226c6e53 --- /dev/null +++ b/src/applications/config/type/PhabricatorJSONConfigType.php @@ -0,0 +1,38 @@ +newException( + pht( + 'Value for option "%s" (of type "%s") must be specified in JSON, '. + 'but input could not be decoded: %s', + $option->getKey(), + $this->getTypeKey(), + $ex->getMessage())); + } + + return $value; + } + + protected function newControl(PhabricatorConfigOption $option) { + return id(new AphrontFormTextAreaControl()) + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) + ->setCustomClass('PhabricatorMonospaced') + ->setCaption(pht('Enter value in JSON.')); + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + return PhabricatorConfigJSON::prettyPrintJSON($value); + } + +} diff --git a/src/applications/config/type/PhabricatorRegexListConfigType.php b/src/applications/config/type/PhabricatorRegexListConfigType.php new file mode 100644 index 0000000000..b37b440c7e --- /dev/null +++ b/src/applications/config/type/PhabricatorRegexListConfigType.php @@ -0,0 +1,24 @@ +'; + + protected function validateStoredItem( + PhabricatorConfigOption $option, + $value) { + + $ok = @preg_match($value, ''); + if ($ok === false) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s" and must be set to a list of valid '. + 'regular expressions, but "%s" is not a valid regular expression.', + $option->getKey(), + $this->getTypeKey(), + $value)); + } + } + +} diff --git a/src/applications/config/type/PhabricatorSetConfigType.php b/src/applications/config/type/PhabricatorSetConfigType.php new file mode 100644 index 0000000000..805ae50468 --- /dev/null +++ b/src/applications/config/type/PhabricatorSetConfigType.php @@ -0,0 +1,92 @@ +setCaption(pht('Separate values with newlines or commas.')); + } + + protected function newCanonicalValue( + PhabricatorConfigOption $option, + $value) { + + $value = preg_split('/[\n,]+/', $value); + foreach ($value as $k => $v) { + if (!strlen($v)) { + unset($value[$k]); + } + $value[$k] = trim($v); + } + + return array_fill_keys($value, true); + } + + public function newValueFromCommandLineValue( + PhabricatorConfigOption $option, + $value) { + + try { + $value = phutil_json_decode($value); + } catch (Exception $ex) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the value you provided is not a '. + 'valid JSON list: when providing a set from the command line, '. + 'specify it as a list of values in JSON. You may need to quote the '. + 'value for your shell (for example: \'["a", "b", ...]\').', + $option->getKey(), + $this->getTypeKey())); + } + + if ($value) { + if (array_keys($value) !== range(0, count($value) - 1)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", and should be specified on the '. + 'command line as a JSON list of values. You may need to quote '. + 'the value for your shell (for example: \'["a", "b", ...]\').', + $option->getKey(), + $this->getTypeKey())); + } + } + + return array_fill_keys($value, true); + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + return implode("\n", array_keys($value)); + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + + if (!is_array($value)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a list.', + $option->getKey(), + $this->getTypeKey())); + } + + foreach ($value as $k => $v) { + if ($v !== true) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the value at index "%s" of the '. + 'list is not "true".', + $option->getKey(), + $this->getTypeKey(), + $k)); + } + } + } + +} diff --git a/src/applications/config/type/PhabricatorStringConfigType.php b/src/applications/config/type/PhabricatorStringConfigType.php new file mode 100644 index 0000000000..da05b75780 --- /dev/null +++ b/src/applications/config/type/PhabricatorStringConfigType.php @@ -0,0 +1,22 @@ +newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a string.', + $option->getKey(), + $this->getTypeKey())); + } + } + +} diff --git a/src/applications/config/type/PhabricatorStringListConfigType.php b/src/applications/config/type/PhabricatorStringListConfigType.php new file mode 100644 index 0000000000..70f4c04316 --- /dev/null +++ b/src/applications/config/type/PhabricatorStringListConfigType.php @@ -0,0 +1,8 @@ +'; + +} diff --git a/src/applications/config/type/PhabricatorTextConfigType.php b/src/applications/config/type/PhabricatorTextConfigType.php new file mode 100644 index 0000000000..8e67d65b7a --- /dev/null +++ b/src/applications/config/type/PhabricatorTextConfigType.php @@ -0,0 +1,27 @@ +setCaption(pht('Separate values with newlines.')); + } + + protected function newCanonicalValue( + PhabricatorConfigOption $option, + $value) { + + $value = phutil_split_lines($value, $retain_endings = false); + foreach ($value as $k => $v) { + if (!strlen($v)) { + unset($value[$k]); + } + } + + return array_values($value); + } + + public function newValueFromCommandLineValue( + PhabricatorConfigOption $option, + $value) { + + try { + $value = phutil_json_decode($value); + } catch (Exception $ex) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the value you provided is not a '. + 'valid JSON list. When setting a list option from the command '. + 'line, specify the value in JSON. You may need to quote the '. + 'value for your shell (for example: \'["a", "b", ...]\').', + $option->getKey(), + $this->getTypeKey())); + } + + return $value; + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + return implode("\n", $value); + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + + if (!is_array($value)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a list.', + $option->getKey(), + $this->getTypeKey())); + } + + $expect_key = 0; + foreach ($value as $k => $v) { + if (!is_string($v)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the item at index "%s" of the '. + 'list is not a string.', + $option->getKey(), + $this->getTypeKey(), + $k)); + } + + // Make sure this is a list with keys "0, 1, 2, ...", not a map with + // arbitrary keys. + if ($k != $expect_key) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the value is not a list: it '. + 'is a map with unnatural or sparse keys.', + $option->getKey(), + $this->getTypeKey())); + } + $expect_key++; + + $this->validateStoredItem($option, $v); + } + } + + protected function validateStoredItem( + PhabricatorConfigOption $option, + $value) { + return; + } + +} diff --git a/src/applications/config/type/PhabricatorWildConfigType.php b/src/applications/config/type/PhabricatorWildConfigType.php new file mode 100644 index 0000000000..3f278ca4e9 --- /dev/null +++ b/src/applications/config/type/PhabricatorWildConfigType.php @@ -0,0 +1,39 @@ +newException( + pht( + 'Value for option "%s" (of type "%s") must be specified in JSON, '. + 'but input could not be decoded. (Did you forget to quote a string?)', + $option->getKey(), + $this->getTypeKey())); + } + + return $value; + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + return; + } + +} diff --git a/src/applications/config/view/PhabricatorConfigPageView.php b/src/applications/config/view/PhabricatorConfigPageView.php deleted file mode 100644 index 9fed2d4f47..0000000000 --- a/src/applications/config/view/PhabricatorConfigPageView.php +++ /dev/null @@ -1,60 +0,0 @@ -header = $header; - return $this; - } - - public function setContent($content) { - $this->content = $content; - return $this; - } - - public function setFooter($footer) { - $this->footer = $footer; - return $this; - } - - protected function getTagName() { - return 'div'; - } - - protected function getTagAttributes() { - require_celerity_resource('config-page-css'); - - $classes = array(); - $classes[] = 'config-page'; - - return array( - 'class' => implode(' ', $classes), - ); - } - - protected function getTagContent() { - - $header = null; - if ($this->header) { - $header = phutil_tag_div('config-page-header', $this->header); - } - - $content = null; - if ($this->content) { - $content = phutil_tag_div('config-page-content', $this->content); - } - - $footer = null; - if ($this->footer) { - $footer = phutil_tag_div('config-page-footer', $this->footer); - } - - return array($header, $content, $footer); - - } - -} diff --git a/src/applications/config/view/PhabricatorSetupIssueView.php b/src/applications/config/view/PhabricatorSetupIssueView.php index 10e802eb3f..6fd75568ac 100644 --- a/src/applications/config/view/PhabricatorSetupIssueView.php +++ b/src/applications/config/view/PhabricatorSetupIssueView.php @@ -129,7 +129,7 @@ final class PhabricatorSetupIssueView extends AphrontView { array( 'href' => '/config/unignore/'.$issue->getIssueKey().'/', 'sigil' => 'workflow', - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht('Unignore Setup Issue')); } else { @@ -138,7 +138,7 @@ final class PhabricatorSetupIssueView extends AphrontView { array( 'href' => '/config/ignore/'.$issue->getIssueKey().'/', 'sigil' => 'workflow', - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht('Ignore Setup Issue')); } @@ -147,8 +147,7 @@ final class PhabricatorSetupIssueView extends AphrontView { 'a', array( 'href' => '/config/issue/'.$issue->getIssueKey().'/', - 'class' => 'button grey', - 'style' => 'float: right', + 'class' => 'button button-grey', ), pht('Reload Page')); } @@ -194,14 +193,24 @@ final class PhabricatorSetupIssueView extends AphrontView { array( 'class' => 'setup-issue-head', ), - array($name, $status)); + $name); + + $body = phutil_tag( + 'div', + array( + 'class' => 'setup-issue-body', + ), + array( + $status, + $description, + )); $tail = phutil_tag( 'div', array( 'class' => 'setup-issue-tail', ), - array($actions)); + $actions); $issue = phutil_tag( 'div', @@ -210,7 +219,7 @@ final class PhabricatorSetupIssueView extends AphrontView { ), array( $head, - $description, + $body, $tail, )); diff --git a/src/applications/conpherence/application/PhabricatorConpherenceApplication.php b/src/applications/conpherence/application/PhabricatorConpherenceApplication.php index e969b48447..a0c21bd33a 100644 --- a/src/applications/conpherence/application/PhabricatorConpherenceApplication.php +++ b/src/applications/conpherence/application/PhabricatorConpherenceApplication.php @@ -45,8 +45,10 @@ final class PhabricatorConpherenceApplication extends PhabricatorApplication { => 'ConpherenceViewController', 'columnview/' => 'ConpherenceColumnViewController', - 'new/' - => 'ConpherenceNewRoomController', + $this->getEditRoutePattern('new/') + => 'ConpherenceRoomEditController', + $this->getEditRoutePattern('edit/') + => 'ConpherenceRoomEditController', 'picture/(?P[1-9]\d*)/' => 'ConpherenceRoomPictureController', 'search/(?:query/(?P[^/]+)/)?' diff --git a/src/applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php index 9cae87e10a..e751318c16 100644 --- a/src/applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php +++ b/src/applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php @@ -11,6 +11,16 @@ final class ConpherenceCreateThreadConduitAPIMethod return pht('Create a new conpherence thread.'); } + public function getMethodStatus() { + return self::METHOD_STATUS_FROZEN; + } + + public function getMethodStatusDescription() { + return pht( + 'This method is frozen and will eventually be deprecated. New code '. + 'should use "conpherence.edit" instead.'); + } + protected function defineParamTypes() { return array( 'title' => 'required string', diff --git a/src/applications/conpherence/conduit/ConpherenceEditConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceEditConduitAPIMethod.php new file mode 100644 index 0000000000..9fc799c194 --- /dev/null +++ b/src/applications/conpherence/conduit/ConpherenceEditConduitAPIMethod.php @@ -0,0 +1,19 @@ + 'optional int', diff --git a/src/applications/conpherence/constants/ConpherenceUpdateActions.php b/src/applications/conpherence/constants/ConpherenceUpdateActions.php index a2b97f9c6a..afec800de3 100644 --- a/src/applications/conpherence/constants/ConpherenceUpdateActions.php +++ b/src/applications/conpherence/constants/ConpherenceUpdateActions.php @@ -2,7 +2,6 @@ final class ConpherenceUpdateActions extends ConpherenceConstants { - const METADATA = 'metadata'; const MESSAGE = 'message'; const DRAFT = 'draft'; const JOIN_ROOM = 'join_room'; diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index f7fd7ef1b4..f47e7bf1ad 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -71,7 +71,7 @@ abstract class ConpherenceController extends PhabricatorController { if (strlen($data['topic'])) { $topic = id(new PHUITagView()) ->setName($data['topic']) - ->setShade(PHUITagView::COLOR_VIOLET) + ->setColor(PHUITagView::COLOR_VIOLET) ->setType(PHUITagView::TYPE_SHADE) ->addClass('conpherence-header-topic'); $header->addTag($topic); @@ -91,7 +91,8 @@ abstract class ConpherenceController extends PhabricatorController { $header->addActionItem( id(new PHUIIconCircleView()) - ->setHref($this->getApplicationURI("update/{$id}/")) + ->setHref( + $this->getApplicationURI('edit/'.$conpherence->getID()).'/') ->setIcon('fa-pencil') ->addClass('hide-on-device') ->setColor('violet') @@ -139,7 +140,7 @@ abstract class ConpherenceController extends PhabricatorController { 'button', array( 'type' => 'SUBMIT', - 'class' => 'button green mlr', + 'class' => 'button button-green mlr', ), pht('Join Room')); diff --git a/src/applications/conpherence/controller/ConpherenceNewRoomController.php b/src/applications/conpherence/controller/ConpherenceNewRoomController.php deleted file mode 100644 index 6cbe86aaa2..0000000000 --- a/src/applications/conpherence/controller/ConpherenceNewRoomController.php +++ /dev/null @@ -1,117 +0,0 @@ -getUser(); - - $title = pht('New Room'); - $e_title = true; - $validation_exception = null; - - $conpherence = ConpherenceThread::initializeNewRoom($user); - $participants = array(); - if ($request->isFormPost()) { - $editor = new ConpherenceEditor(); - $xactions = array(); - - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) - ->setNewValue($request->getStr('title')); - - $participants = $request->getArr('participants'); - $participants[] = $user->getPHID(); - $participants = array_unique($participants); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType( - ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) - ->setNewValue(array('+' => $participants)); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceThreadTopicTransaction::TRANSACTIONTYPE) - ->setNewValue($request->getStr('topic')); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($request->getStr('viewPolicy')); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) - ->setNewValue($request->getStr('editPolicy')); - - try { - $editor - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->setActor($user) - ->applyTransactions($conpherence, $xactions); - - return id(new AphrontRedirectResponse()) - ->setURI('/'.$conpherence->getMonogram()); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_title = $ex->getShortMessage( - ConpherenceThreadTitleTransaction::TRANSACTIONTYPE); - - $conpherence->setViewPolicy($request->getStr('viewPolicy')); - $conpherence->setEditPolicy($request->getStr('editPolicy')); - } - } else { - if ($request->getStr('participant')) { - $participants[] = $request->getStr('participant'); - } - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($user) - ->setObject($conpherence) - ->execute(); - - $submit_uri = $this->getApplicationURI('new/'); - $cancel_uri = $this->getApplicationURI('search/'); - - $dialog = $this->newDialog() - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->setValidationException($validation_exception) - ->setUser($user) - ->setTitle($title) - ->addCancelButton($cancel_uri) - ->addSubmitButton(pht('Create Room')); - - $form = id(new PHUIFormLayoutView()) - ->setUser($user) - ->appendChild( - id(new AphrontFormTextControl()) - ->setError($e_title) - ->setLabel(pht('Name')) - ->setName('title') - ->setValue($request->getStr('title'))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Topic')) - ->setName('topic') - ->setValue($request->getStr('topic'))) - ->appendChild( - id(new AphrontFormTokenizerControl()) - ->setName('participants') - ->setUser($user) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setValue($participants) - ->setLabel(pht('Other Participants'))) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($conpherence) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($conpherence) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)); - - $dialog->appendChild($form); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - -} diff --git a/src/applications/conpherence/controller/ConpherenceRoomEditController.php b/src/applications/conpherence/controller/ConpherenceRoomEditController.php new file mode 100644 index 0000000000..56f808056c --- /dev/null +++ b/src/applications/conpherence/controller/ConpherenceRoomEditController.php @@ -0,0 +1,11 @@ +setController($this) + ->buildResponse(); + } +} diff --git a/src/applications/conpherence/controller/ConpherenceRoomPictureController.php b/src/applications/conpherence/controller/ConpherenceRoomPictureController.php index 6a39481377..fbf3def9cd 100644 --- a/src/applications/conpherence/controller/ConpherenceRoomPictureController.php +++ b/src/applications/conpherence/controller/ConpherenceRoomPictureController.php @@ -132,7 +132,7 @@ final class ConpherenceRoomPictureController $button = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $spec['tip'], diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 728d917589..a792a5a4d4 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -12,7 +12,7 @@ final class ConpherenceUpdateController $need_participants = false; $needed_capabilities = array(PhabricatorPolicyCapability::CAN_VIEW); - $action = $request->getStr('action', ConpherenceUpdateActions::METADATA); + $action = $request->getStr('action'); switch ($action) { case ConpherenceUpdateActions::REMOVE_PERSON: $person_phid = $request->getStr('remove_person'); @@ -21,7 +21,6 @@ final class ConpherenceUpdateController } break; case ConpherenceUpdateActions::ADD_PERSON: - case ConpherenceUpdateActions::METADATA: $needed_capabilities[] = PhabricatorPolicyCapability::CAN_EDIT; break; case ConpherenceUpdateActions::LOAD: @@ -110,35 +109,6 @@ final class ConpherenceUpdateController $response_mode = 'go-home'; } break; - case ConpherenceUpdateActions::METADATA: - $title = $request->getStr('title'); - $topic = $request->getStr('topic'); - - // all other metadata updates are continue requests - if (!$request->isContinueRequest()) { - break; - } - - $title = $request->getStr('title'); - $topic = $request->getStr('topic'); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType( - ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) - ->setNewValue($title); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType( - ConpherenceThreadTopicTransaction::TRANSACTIONTYPE) - ->setNewValue($topic); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($request->getStr('viewPolicy')); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) - ->setNewValue($request->getStr('editPolicy')); - if (!$request->getExists('force_ajax')) { - $response_mode = 'redirect'; - } - break; case ConpherenceUpdateActions::LOAD: $updated = false; $response_mode = 'ajax'; @@ -208,10 +178,6 @@ final class ConpherenceUpdateController case ConpherenceUpdateActions::REMOVE_PERSON: $dialog = $this->renderRemovePersonDialog($conpherence); break; - case ConpherenceUpdateActions::METADATA: - default: - $dialog = $this->renderMetadataDialog($conpherence, $error_view); - break; } return @@ -332,62 +298,6 @@ final class ConpherenceUpdateController return $dialog; } - private function renderMetadataDialog( - ConpherenceThread $conpherence, - $error_view) { - - $request = $this->getRequest(); - $user = $request->getUser(); - - $title = pht('Update Room'); - $form = id(new PHUIFormLayoutView()) - ->appendChild($error_view) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Title')) - ->setName('title') - ->setValue($conpherence->getTitle())) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Topic')) - ->setName('topic') - ->setValue($conpherence->getTopic())); - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($user) - ->setObject($conpherence) - ->execute(); - - $form - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($conpherence) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($conpherence) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)); - - $view = id(new AphrontDialogView()) - ->setTitle($title) - ->addHiddenInput('action', 'metadata') - ->addHiddenInput( - 'latest_transaction_id', - $request->getInt('latest_transaction_id')) - ->addHiddenInput('__continue__', true) - ->appendChild($form); - - if ($request->getExists('force_ajax')) { - $view->addHiddenInput('force_ajax', true); - } - - return $view; - } - private function loadAndRenderUpdates( $action, $conpherence_id, @@ -395,7 +305,6 @@ final class ConpherenceUpdateController $need_transactions = false; switch ($action) { - case ConpherenceUpdateActions::METADATA: case ConpherenceUpdateActions::LOAD: $need_transactions = true; break; @@ -444,15 +353,6 @@ final class ConpherenceUpdateController $header = null; $people_widget = null; switch ($action) { - case ConpherenceUpdateActions::METADATA: - $header = $this->buildHeaderPaneContent($conpherence); - $header = hsprintf('%s', $header); - $nav_item = id(new ConpherenceThreadListView()) - ->setUser($user) - ->setBaseURI($this->getApplicationURI()) - ->renderThreadItem($conpherence); - $nav_item = hsprintf('%s', $nav_item); - break; case ConpherenceUpdateActions::ADD_PERSON: $people_widget = id(new ConpherenceParticipantView()) ->setUser($user) @@ -469,9 +369,17 @@ final class ConpherenceUpdateController ->setViewer($user); $dropdown_query->execute(); - $sounds = $this->getSoundForParticipant($user, $participant); - $receive_sound = $sounds[ConpherenceRoomSettings::SOUND_RECEIVE]; - $mention_sound = $sounds[ConpherenceRoomSettings::SOUND_MENTION]; + $map = ConpherenceRoomSettings::getSoundMap(); + $default_receive = ConpherenceRoomSettings::DEFAULT_RECEIVE_SOUND; + $receive_sound = $map[$default_receive]['rsrc']; + $mention_sound = null; + + // Get the user's defaults if logged in + if ($participant) { + $sounds = $this->getSoundForParticipant($user, $participant); + $receive_sound = $sounds[ConpherenceRoomSettings::SOUND_RECEIVE]; + $mention_sound = $sounds[ConpherenceRoomSettings::SOUND_MENTION]; + } $content = array( 'non_update' => $non_update, diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 792a7f8182..996417a307 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -195,7 +195,7 @@ final class ConpherenceViewController extends ->appendChild( id(new PHUIButtonView()) ->setTag('a') - ->setText(pht('Login to Participate')) + ->setText(pht('Log In to Participate')) ->setHref((string)$login_href)); } } diff --git a/src/applications/conpherence/editor/ConpherenceEditEngine.php b/src/applications/conpherence/editor/ConpherenceEditEngine.php new file mode 100644 index 0000000000..91cd8fd081 --- /dev/null +++ b/src/applications/conpherence/editor/ConpherenceEditEngine.php @@ -0,0 +1,118 @@ +getViewer()); + } + + protected function newObjectQuery() { + return new ConpherenceThreadQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Room'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Room: %s', $object->getTitle()); + } + + protected function getObjectEditShortText($object) { + return $object->getTitle(); + } + + protected function getObjectCreateShortText() { + return pht('Create Room'); + } + + protected function getObjectName() { + return pht('Room'); + } + + protected function getObjectCreateCancelURI($object) { + return $this->getApplication()->getApplicationURI('/'); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('edit/'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + public function isEngineConfigurable() { + return false; + } + + protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + + if ($this->getIsCreate()) { + $participant_phids = array($viewer->getPHID()); + $initial_phids = array(); + } else { + $participant_phids = $object->getParticipantPHIDs(); + $initial_phids = $participant_phids; + } + + // Only show participants on create or conduit, not edit + $conduit_only = !$this->getIsCreate(); + + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Room name.')) + ->setConduitTypeDescription(pht('New Room name.')) + ->setIsRequired(true) + ->setTransactionType( + ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) + ->setValue($object->getTitle()), + + id(new PhabricatorTextEditField()) + ->setKey('topic') + ->setLabel(pht('Topic')) + ->setDescription(pht('Room topic.')) + ->setConduitTypeDescription(pht('New Room topic.')) + ->setTransactionType( + ConpherenceThreadTopicTransaction::TRANSACTIONTYPE) + ->setValue($object->getTopic()), + + id(new PhabricatorUsersEditField()) + ->setKey('participants') + ->setValue($participant_phids) + ->setInitialValue($initial_phids) + ->setIsConduitOnly($conduit_only) + ->setAliases(array('users', 'members', 'participants', 'userPHID')) + ->setDescription(pht('Room participants.')) + ->setUseEdgeTransactions(true) + ->setConduitTypeDescription(pht('New Room participants.')) + ->setTransactionType( + ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) + ->setLabel(pht('Initial Participants')), + + ); + } + +} diff --git a/src/applications/conpherence/lipsum/PhabricatorConpherenceRoomContextFreeGrammar.php b/src/applications/conpherence/lipsum/PhabricatorConpherenceRoomContextFreeGrammar.php new file mode 100644 index 0000000000..5ff68b096f --- /dev/null +++ b/src/applications/conpherence/lipsum/PhabricatorConpherenceRoomContextFreeGrammar.php @@ -0,0 +1,97 @@ + array( + '[dept]', + '[dept]', + '[dept]', + '[dept]', + '[dept]', + '[dept]', + '[dept]', + '[dept] ([city])', + '[dept] ([city])', + '[dept] - [city]', + '[dept] - [room]', + '[dept] / [room]', + '[dept] [room]', + '[city] ([dept]) - [room]', + '[dept] ([city]) - [room]', + '[dept] ([city]) [room]', + ), + 'dept' => array( + 'Eng', + 'Engineering', + 'User Interface', + 'Design', + 'Data Science', + 'Database', + 'Marketing', + 'Content', + 'Ads', + 'Operations', + 'Network Ops', + 'Ops', + 'Server Ops', + 'IT', + 'Information Technology', + 'i18n', + 'Internationalization', + 'Human Resources', + 'HR', + 'Research & Development', + 'R&D', + 'Management', + 'Directors', + 'Managers', + 'Support', + 'Customer Support', + 'Finance', + 'Sales', + 'Purchasing', + 'Education', + 'Hardware Engineering', + 'Software', + 'Supply Management', + 'Logistics', + 'Growth', + 'Content Strategy', + 'Developer Relations', + 'Accounting', + 'Production', + ), + 'city' => array( + 'Palo Alto', + 'Mtn View', + 'Cupertino', + 'Los Altos', + 'Menlo Park', + 'Santa Cruz', + 'S.F.', + 'San Francisco', + 'Seattle', + 'London', + 'New York', + 'Dublin', + 'Tokyo', + ), + 'room' => array( + 'General', + 'Announcements', + 'Staff', + 'Interns', + 'Managers', + 'Book Club', + 'Parking', + 'Sports', + 'Social', + 'Commuting', + 'For Sale', + 'Parents@', + ), + ); + } +} diff --git a/src/applications/conpherence/lipsum/PhabricatorConpherenceRoomTestDataGenerator.php b/src/applications/conpherence/lipsum/PhabricatorConpherenceRoomTestDataGenerator.php new file mode 100644 index 0000000000..8346df3313 --- /dev/null +++ b/src/applications/conpherence/lipsum/PhabricatorConpherenceRoomTestDataGenerator.php @@ -0,0 +1,76 @@ +loadRandomUser(); + + $name = $this->newRoomName(); + + $participants = array(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + $participants[] = $this->loadRandomUser(); + + $rando_phids = array(); + $rando_phids[] = $author->getPHID(); + foreach ($participants as $actor) { + $rando_phids[] = $actor->getPHID(); + } + + $xactions = array(); + + $xactions[] = array( + 'type' => 'name', + 'value' => $name, + ); + + $xactions[] = array( + 'type' => 'participants.set', + 'value' => $rando_phids, + ); + + $xactions[] = array( + 'type' => 'view', + 'value' => 'users', + ); + + $xactions[] = array( + 'type' => 'edit', + 'value' => 'users', + ); + + $params = array( + 'transactions' => $xactions, + ); + + $result = id(new ConduitCall('conpherence.edit', $params)) + ->setUser($author) + ->execute(); + + return $result['object']['phid']; + } + + protected function newRoomName() { + $generator = new PhabricatorConpherenceRoomContextFreeGrammar(); + $name = $generator->generate(); + return $name; + } + + + +} diff --git a/src/applications/conpherence/view/ConpherenceDurableColumnView.php b/src/applications/conpherence/view/ConpherenceDurableColumnView.php index e3240bc2e4..83c40aa32e 100644 --- a/src/applications/conpherence/view/ConpherenceDurableColumnView.php +++ b/src/applications/conpherence/view/ConpherenceDurableColumnView.php @@ -363,9 +363,9 @@ final class ConpherenceDurableColumnView extends AphrontTagView { $actions[] = array( 'name' => pht('Edit Room'), 'disabled' => !$can_edit, - 'href' => '/conpherence/update/'.$conpherence->getID().'/?nopic', + 'href' => '/conpherence/edit/'.$conpherence->getID().'/', 'icon' => 'fa-pencil', - 'key' => ConpherenceUpdateActions::METADATA, + 'key' => 'go_edit', ); $actions[] = array( 'name' => pht('View in Conpherence'), @@ -404,7 +404,7 @@ final class ConpherenceDurableColumnView extends AphrontTagView { 'a', array( 'href' => '/conpherence/search/', - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht('Find Rooms')), ); diff --git a/src/applications/conpherence/view/ConpherenceThreadListView.php b/src/applications/conpherence/view/ConpherenceThreadListView.php index af15f82a6e..42f1b605c1 100644 --- a/src/applications/conpherence/view/ConpherenceThreadListView.php +++ b/src/applications/conpherence/view/ConpherenceThreadListView.php @@ -117,7 +117,7 @@ final class ConpherenceThreadListView extends AphrontView { $new_icon = id(new PHUIIconView()) ->setIcon('fa-plus-square') ->addSigil('has-tooltip') - ->setHref('/conpherence/new/') + ->setHref('/conpherence/edit/') ->setWorkflow(true) ->setMetaData(array( 'tip' => pht('New Room'), diff --git a/src/applications/console/plugin/DarkConsoleServicesPlugin.php b/src/applications/console/plugin/DarkConsoleServicesPlugin.php index ea5a100550..7d779027da 100644 --- a/src/applications/console/plugin/DarkConsoleServicesPlugin.php +++ b/src/applications/console/plugin/DarkConsoleServicesPlugin.php @@ -170,7 +170,8 @@ final class DarkConsoleServicesPlugin extends DarkConsolePlugin { 'a', array( 'href' => $data['analyzeURI'], - 'class' => $data['didAnalyze'] ? 'disabled button' : 'green button', + 'class' => $data['didAnalyze'] ? + 'disabled button' : 'button button-green', ), pht('Analyze Query Plans')), phutil_tag('h1', array(), pht('Calls to External Services')), diff --git a/src/applications/console/plugin/DarkConsoleXHProfPlugin.php b/src/applications/console/plugin/DarkConsoleXHProfPlugin.php index f751cc31a5..f76b77c850 100644 --- a/src/applications/console/plugin/DarkConsoleXHProfPlugin.php +++ b/src/applications/console/plugin/DarkConsoleXHProfPlugin.php @@ -67,7 +67,7 @@ final class DarkConsoleXHProfPlugin extends DarkConsolePlugin { 'a', array( 'href' => $profile_uri, - 'class' => $run ? 'disabled button' : 'green button', + 'class' => $run ? 'disabled button' : 'button button-green', ), pht('Profile Page')), phutil_tag('h1', array(), pht('XHProf Profiler')), diff --git a/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php b/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php index 3ff26e3db7..fd7de9dd33 100644 --- a/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php +++ b/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php @@ -19,10 +19,9 @@ final class PhabricatorDaemonLogGarbageCollector queryfx( $conn_w, - 'DELETE FROM %T WHERE dateCreated < %d AND status != %s LIMIT 100', + 'DELETE FROM %T WHERE dateModified < %d LIMIT 100', $table->getTableName(), - $this->getGarbageEpoch(), - PhabricatorDaemonLog::STATUS_RUNNING); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/daemon/storage/PhabricatorDaemonLog.php b/src/applications/daemon/storage/PhabricatorDaemonLog.php index 2e61da3872..d3ac485ea2 100644 --- a/src/applications/daemon/storage/PhabricatorDaemonLog.php +++ b/src/applications/daemon/storage/PhabricatorDaemonLog.php @@ -37,13 +37,13 @@ final class PhabricatorDaemonLog extends PhabricatorDaemonDAO 'status' => array( 'columns' => array('status'), ), - 'dateCreated' => array( - 'columns' => array('dateCreated'), - ), 'key_daemonID' => array( 'columns' => array('daemonID'), 'unique' => true, ), + 'key_modified' => array( + 'columns' => array('dateModified'), + ), ), ) + parent::getConfiguration(); } diff --git a/src/applications/daemon/view/PhabricatorDaemonTasksTableView.php b/src/applications/daemon/view/PhabricatorDaemonTasksTableView.php index 4fded0b68a..440610be25 100644 --- a/src/applications/daemon/view/PhabricatorDaemonTasksTableView.php +++ b/src/applications/daemon/view/PhabricatorDaemonTasksTableView.php @@ -41,7 +41,7 @@ final class PhabricatorDaemonTasksTableView extends AphrontView { 'a', array( 'href' => '/daemon/task/'.$task->getID().'/', - 'class' => 'button small grey', + 'class' => 'button small button-grey', ), pht('View Task')), ); diff --git a/src/applications/dashboard/controller/PhabricatorDashboardEditController.php b/src/applications/dashboard/controller/PhabricatorDashboardEditController.php index 0edda37102..6b3a96d80c 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardEditController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardEditController.php @@ -184,19 +184,14 @@ final class PhabricatorDashboardEditController ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Dashboard')) + ->setHeaderText($title) ->setForm($form) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setValidationException($validation_exception); $crumbs->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter($box); return $this->newPage() diff --git a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php index 07faee8f05..41441f09f4 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php @@ -47,6 +47,7 @@ final class PhabricatorDashboardViewController ->setTag('a') ->setText('Install Dashboard') ->setIcon('fa-plus') + ->setColor(PHUIButtonView::GREEN) ->setWorkflow(true) ->setHref($this->getApplicationURI("/install/{$id}/")); $header->addActionLink($install_button); diff --git a/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php b/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php index 958883d34e..d534d32adc 100644 --- a/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php +++ b/src/applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php @@ -42,8 +42,7 @@ final class PhabricatorDashboardPanelDatasource $properties = $panel->getProperties(); $result = id(new PhabricatorTypeaheadResult()) - ->setName($panel->getName()) - ->setDisplayName($monogram.' '.$panel->getName()) + ->setName($monogram.' '.$panel->getName()) ->setPHID($id) ->setIcon($impl->getIcon()) ->addAttribute($type_text); diff --git a/src/applications/differential/application/PhabricatorDifferentialApplication.php b/src/applications/differential/application/PhabricatorDifferentialApplication.php index 1b6443a9e0..1c8926f585 100644 --- a/src/applications/differential/application/PhabricatorDifferentialApplication.php +++ b/src/applications/differential/application/PhabricatorDifferentialApplication.php @@ -10,8 +10,12 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication { return pht('Differential'); } + public function getMenuName() { + return pht('Code Review'); + } + public function getShortDescription() { - return pht('Review Code'); + return pht('Pre-Commit Review'); } public function getIcon() { @@ -41,12 +45,6 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication { return "\xE2\x9A\x99"; } - public function getEventListeners() { - return array( - new DifferentialLandingActionMenuEventListener(), - ); - } - public function getOverview() { return pht( 'Differential is a **code review application** which allows '. @@ -69,14 +67,14 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication { => 'DifferentialRevisionEditController', $this->getEditRoutePattern('attach/(?P[^/]+)/to/') => 'DifferentialRevisionEditController', - 'land/(?:(?P[1-9]\d*))/(?P[^/]+)/' - => 'DifferentialRevisionLandController', 'closedetails/(?P[^/]+)/' => 'DifferentialRevisionCloseDetailsController', 'update/(?P[1-9]\d*)/' => 'DifferentialDiffCreateController', 'operation/(?P[1-9]\d*)/' => 'DifferentialRevisionOperationController', + 'inlines/(?P[1-9]\d*)/' + => 'DifferentialRevisionInlinesController', ), 'comment/' => array( 'preview/(?P[1-9]\d*)/' => 'DifferentialCommentPreviewController', diff --git a/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php index 30f8a5e5b7..12ecda1262 100644 --- a/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php @@ -52,8 +52,9 @@ final class DifferentialCloseConduitAPIMethod $xactions = array(); $xactions[] = id(new DifferentialTransaction()) - ->setTransactionType(DifferentialTransaction::TYPE_ACTION) - ->setNewValue(DifferentialAction::ACTION_CLOSE); + ->setTransactionType( + DifferentialRevisionCloseTransaction::TRANSACTIONTYPE) + ->setNewValue(true); $content_source = $request->newContentSource(); diff --git a/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php index 52736d6f3b..21b055a1e4 100644 --- a/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php @@ -49,6 +49,7 @@ final class DifferentialCreateCommentConduitAPIMethod ->withIDs(array($request->getValue('revision_id'))) ->needReviewers(true) ->needReviewerAuthority(true) + ->needActiveDiffs(true) ->executeOne(); if (!$revision) { throw new ConduitException('ERR_BAD_REVISION'); @@ -62,6 +63,7 @@ final class DifferentialCreateCommentConduitAPIMethod 'resign' => DifferentialRevisionResignTransaction::TRANSACTIONTYPE, 'request_review' => DifferentialRevisionRequestReviewTransaction::TRANSACTIONTYPE, + 'rethink' => DifferentialRevisionPlanChangesTransaction::TRANSACTIONTYPE, ); $action = $request->getValue('action'); @@ -75,9 +77,10 @@ final class DifferentialCreateCommentConduitAPIMethod case 'none': break; default: - $xactions[] = id(new DifferentialTransaction()) - ->setTransactionType(DifferentialTransaction::TYPE_ACTION) - ->setNewValue($action); + throw new Exception( + pht( + 'Unsupported action "%s".', + $action)); break; } } diff --git a/src/applications/differential/conduit/DifferentialFindConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialFindConduitAPIMethod.php deleted file mode 100644 index 92dce067fe..0000000000 --- a/src/applications/differential/conduit/DifferentialFindConduitAPIMethod.php +++ /dev/null @@ -1,101 +0,0 @@ - 'required '.$this->formatStringConstants($types), - 'guids' => 'required nonempty list', - ); - } - - protected function defineReturnType() { - return 'nonempty list'; - } - - protected function execute(ConduitAPIRequest $request) { - $type = $request->getValue('query'); - $guids = $request->getValue('guids'); - - $results = array(); - if (!$guids) { - return $results; - } - - $query = id(new DifferentialRevisionQuery()) - ->setViewer($request->getUser()); - - switch ($type) { - case 'open': - $query - ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) - ->withAuthors($guids); - break; - case 'committable': - $query - ->withStatus(DifferentialRevisionQuery::STATUS_ACCEPTED) - ->withAuthors($guids); - break; - case 'revision-ids': - $query - ->withIDs($guids); - break; - case 'owned': - $query->withAuthors($guids); - break; - case 'phids': - $query - ->withPHIDs($guids); - break; - } - - $revisions = $query->execute(); - - foreach ($revisions as $revision) { - $diff = $revision->loadActiveDiff(); - if (!$diff) { - continue; - } - $id = $revision->getID(); - $results[] = array( - 'id' => $id, - 'phid' => $revision->getPHID(), - 'name' => $revision->getTitle(), - 'uri' => PhabricatorEnv::getProductionURI('/D'.$id), - 'dateCreated' => $revision->getDateCreated(), - 'authorPHID' => $revision->getAuthorPHID(), - 'statusName' => - ArcanistDifferentialRevisionStatus::getNameForRevisionStatus( - $revision->getStatus()), - 'sourcePath' => $diff->getSourcePath(), - ); - } - - return $results; - } - -} diff --git a/src/applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php index b05efb5c15..a8dd8c4d56 100644 --- a/src/applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php @@ -82,10 +82,8 @@ final class DifferentialGetRevisionConduitAPIMethod 'authorPHID' => $revision->getAuthorPHID(), 'uri' => PhabricatorEnv::getURI('/D'.$revision->getID()), 'title' => $revision->getTitle(), - 'status' => $revision->getStatus(), - 'statusName' => - ArcanistDifferentialRevisionStatus::getNameForRevisionStatus( - $revision->getStatus()), + 'status' => $revision->getLegacyRevisionStatus(), + 'statusName' => $revision->getStatusDisplayName(), 'summary' => $revision->getSummary(), 'testPlan' => $revision->getTestPlan(), 'lineCount' => $revision->getLineCount(), diff --git a/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php index 3b88087a67..eeef36ee69 100644 --- a/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php @@ -25,12 +25,7 @@ final class DifferentialQueryConduitAPIMethod $hash_types = ArcanistDifferentialRevisionHash::getTypes(); $hash_const = $this->formatStringConstants($hash_types); - $status_types = array( - DifferentialRevisionQuery::STATUS_ANY, - DifferentialRevisionQuery::STATUS_OPEN, - DifferentialRevisionQuery::STATUS_ACCEPTED, - DifferentialRevisionQuery::STATUS_CLOSED, - ); + $status_types = DifferentialLegacyQuery::getAllConstants(); $status_const = $this->formatStringConstants($status_types); $order_types = array( @@ -155,7 +150,10 @@ final class DifferentialQueryConduitAPIMethod } if ($status) { - $query->withStatus($status); + $statuses = DifferentialLegacyQuery::getModernValues($status); + if ($statuses) { + $query->withStatuses($statuses); + } } if ($order) { $query->setOrder($order); @@ -220,10 +218,12 @@ final class DifferentialQueryConduitAPIMethod 'dateCreated' => $revision->getDateCreated(), 'dateModified' => $revision->getDateModified(), 'authorPHID' => $revision->getAuthorPHID(), - 'status' => $revision->getStatus(), - 'statusName' => - ArcanistDifferentialRevisionStatus::getNameForRevisionStatus( - $revision->getStatus()), + + // NOTE: For backward compatibility this is explicitly a string, like + // "2", even though the value of the string is an integer. See PHI14. + 'status' => (string)$revision->getLegacyRevisionStatus(), + + 'statusName' => $revision->getStatusDisplayName(), 'properties' => $revision->getProperties(), 'branch' => $diff->getBranch(), 'summary' => $revision->getSummary(), diff --git a/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php index 3ad5b07564..d9b0779211 100644 --- a/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php @@ -69,7 +69,7 @@ final class DifferentialUpdateRevisionConduitAPIMethod throw new ConduitException('ERR_BAD_REVISION'); } - if ($revision->getStatus() == ArcanistDifferentialRevisionStatus::CLOSED) { + if ($revision->isPublished()) { throw new ConduitException('ERR_CLOSED'); } diff --git a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php index a3db26bef6..7ff886664f 100644 --- a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php +++ b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php @@ -260,7 +260,10 @@ EOHELP ->setDescription( pht('Format for inlined or attached patches.')) ->setEnumOptions( - array('unified' => 'unified', 'git' => 'git')), + array( + 'unified' => pht('Unified'), + 'git' => pht('Git'), + )), ); } diff --git a/src/applications/differential/constants/DifferentialChangeType.php b/src/applications/differential/constants/DifferentialChangeType.php index 7ee3c89b65..fd0bcf81bb 100644 --- a/src/applications/differential/constants/DifferentialChangeType.php +++ b/src/applications/differential/constants/DifferentialChangeType.php @@ -71,7 +71,7 @@ final class DifferentialChangeType extends Phobject { self::FILE_TEXT => 'fa-file-text-o', self::FILE_IMAGE => 'fa-file-image-o', self::FILE_BINARY => 'fa-file', - self::FILE_DIRECTORY => 'fa-folder-open', + self::FILE_DIRECTORY => 'fa-folder', self::FILE_SYMLINK => 'fa-link', self::FILE_DELETED => 'fa-file', self::FILE_NORMAL => 'fa-file-text-o', @@ -83,14 +83,14 @@ final class DifferentialChangeType extends Phobject { public static function getIconColorForFileType($type) { static $icons = array( - self::FILE_TEXT => 'black', - self::FILE_IMAGE => 'black', + self::FILE_TEXT => 'bluetext', + self::FILE_IMAGE => 'bluetext', self::FILE_BINARY => 'green', - self::FILE_DIRECTORY => 'blue', - self::FILE_SYMLINK => 'blue', + self::FILE_DIRECTORY => 'bluetext', + self::FILE_SYMLINK => 'bluetext', self::FILE_DELETED => 'red', - self::FILE_NORMAL => 'black', - self::FILE_SUBMODULE => 'blue', + self::FILE_NORMAL => 'bluetext', + self::FILE_SUBMODULE => 'bluetext', ); return idx($icons, $type, 'black'); diff --git a/src/applications/differential/constants/DifferentialLegacyQuery.php b/src/applications/differential/constants/DifferentialLegacyQuery.php new file mode 100644 index 0000000000..26d2c4aee2 --- /dev/null +++ b/src/applications/differential/constants/DifferentialLegacyQuery.php @@ -0,0 +1,75 @@ +isClosedStatus()) { + $closed[] = $status_object->getKey(); + } else { + $open[] = $status_object->getKey(); + } + } + + return array( + self::STATUS_ANY => $all, + self::STATUS_OPEN => $open, + self::STATUS_ACCEPTED => array( + DifferentialRevisionStatus::ACCEPTED, + ), + self::STATUS_NEEDS_REVIEW => array( + DifferentialRevisionStatus::NEEDS_REVIEW, + ), + self::STATUS_NEEDS_REVISION => array( + DifferentialRevisionStatus::NEEDS_REVISION, + ), + self::STATUS_CLOSED => $closed, + self::STATUS_ABANDONED => array( + DifferentialRevisionStatus::ABANDONED, + ), + ); + } + +} diff --git a/src/applications/differential/constants/DifferentialRevisionStatus.php b/src/applications/differential/constants/DifferentialRevisionStatus.php index 4f332838ac..b4b291f9c4 100644 --- a/src/applications/differential/constants/DifferentialRevisionStatus.php +++ b/src/applications/differential/constants/DifferentialRevisionStatus.php @@ -1,107 +1,169 @@ - self::COLOR_STATUS_DEFAULT, - ArcanistDifferentialRevisionStatus::NEEDS_REVISION => - self::COLOR_STATUS_RED, - ArcanistDifferentialRevisionStatus::CHANGES_PLANNED => - self::COLOR_STATUS_RED, - ArcanistDifferentialRevisionStatus::ACCEPTED => - self::COLOR_STATUS_GREEN, - ArcanistDifferentialRevisionStatus::CLOSED => - self::COLOR_STATUS_DARK, - ArcanistDifferentialRevisionStatus::ABANDONED => - self::COLOR_STATUS_DARK, - ArcanistDifferentialRevisionStatus::IN_PREPARATION => - self::COLOR_STATUS_BLUE, - ); - return idx($map, $status, $default); + public function getKey() { + return $this->key; } - public static function getRevisionStatusIcon($status) { - $default = 'fa-square-o bluegrey'; - - $map = array( - ArcanistDifferentialRevisionStatus::NEEDS_REVIEW => - 'fa-square-o bluegrey', - ArcanistDifferentialRevisionStatus::NEEDS_REVISION => - 'fa-refresh', - ArcanistDifferentialRevisionStatus::CHANGES_PLANNED => - 'fa-headphones', - ArcanistDifferentialRevisionStatus::ACCEPTED => - 'fa-check', - ArcanistDifferentialRevisionStatus::CLOSED => - 'fa-check-square-o', - ArcanistDifferentialRevisionStatus::ABANDONED => - 'fa-plane', - ArcanistDifferentialRevisionStatus::IN_PREPARATION => - 'fa-question-circle', - ); - return idx($map, $status, $default); + public function getLegacyKey() { + return idx($this->spec, 'legacy'); } - public static function renderFullDescription($status) { - $status_name = - ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($status); - - $tag = id(new PHUITagView()) - ->setName($status_name) - ->setIcon(self::getRevisionStatusIcon($status)) - ->setShade(self::getRevisionStatusColor($status)) - ->setType(PHUITagView::TYPE_SHADE); - - return $tag; + public function getIcon() { + return idx($this->spec, 'icon'); } - public static function getClosedStatuses() { - $statuses = array( - ArcanistDifferentialRevisionStatus::CLOSED, - ArcanistDifferentialRevisionStatus::ABANDONED, - ); + public function getIconColor() { + return idx($this->spec, 'color.icon', 'bluegrey'); + } - if (PhabricatorEnv::getEnvConfig('differential.close-on-accept')) { - $statuses[] = ArcanistDifferentialRevisionStatus::ACCEPTED; + public function getTagColor() { + return idx($this->spec, 'color.tag', 'bluegrey'); + } + + public function getTimelineIcon() { + return idx($this->spec, 'icon.timeline'); + } + + public function getTimelineColor() { + return idx($this->spec, 'color.timeline'); + } + + public function getANSIColor() { + return idx($this->spec, 'color.ansi'); + } + + public function getDisplayName() { + return idx($this->spec, 'name'); + } + + public function isClosedStatus() { + return idx($this->spec, 'closed'); + } + + public function isAbandoned() { + return ($this->key === self::ABANDONED); + } + + public function isAccepted() { + return ($this->key === self::ACCEPTED); + } + + public function isNeedsReview() { + return ($this->key === self::NEEDS_REVIEW); + } + + public function isNeedsRevision() { + return ($this->key === self::NEEDS_REVISION); + } + + public function isPublished() { + return ($this->key === self::PUBLISHED); + } + + public function isChangePlanned() { + return ($this->key === self::CHANGES_PLANNED); + } + + public static function newForStatus($status) { + $result = new self(); + + $map = self::getMap(); + if (isset($map[$status])) { + $result->key = $status; + $result->spec = $map[$status]; } - return $statuses; + return $result; } - public static function getOpenStatuses() { - return array_diff(self::getAllStatuses(), self::getClosedStatuses()); + public static function getAll() { + $result = array(); + + foreach (self::getMap() as $key => $spec) { + $result[$key] = self::newForStatus($key); + } + + return $result; } - public static function getAllStatuses() { + private static function getMap() { + $close_on_accept = PhabricatorEnv::getEnvConfig( + 'differential.close-on-accept'); + return array( - ArcanistDifferentialRevisionStatus::NEEDS_REVIEW, - ArcanistDifferentialRevisionStatus::NEEDS_REVISION, - ArcanistDifferentialRevisionStatus::CHANGES_PLANNED, - ArcanistDifferentialRevisionStatus::ACCEPTED, - ArcanistDifferentialRevisionStatus::CLOSED, - ArcanistDifferentialRevisionStatus::ABANDONED, - ArcanistDifferentialRevisionStatus::IN_PREPARATION, + self::NEEDS_REVIEW => array( + 'name' => pht('Needs Review'), + 'legacy' => 0, + 'icon' => 'fa-code', + 'icon.timeline' => 'fa-undo', + 'closed' => false, + 'color.icon' => 'grey', + 'color.tag' => 'bluegrey', + 'color.ansi' => 'magenta', + 'color.timeline' => 'orange', + ), + self::NEEDS_REVISION => array( + 'name' => pht('Needs Revision'), + 'legacy' => 1, + 'icon' => 'fa-refresh', + 'icon.timeline' => 'fa-times', + 'closed' => false, + 'color.icon' => 'red', + 'color.tag' => 'red', + 'color.ansi' => 'red', + 'color.timeline' => 'red', + ), + self::CHANGES_PLANNED => array( + 'name' => pht('Changes Planned'), + 'legacy' => 5, + 'icon' => 'fa-headphones', + 'closed' => false, + 'color.icon' => 'red', + 'color.tag' => 'red', + 'color.ansi' => 'red', + ), + self::ACCEPTED => array( + 'name' => pht('Accepted'), + 'legacy' => 2, + 'icon' => 'fa-check', + 'icon.timeline' => 'fa-check', + 'closed' => $close_on_accept, + 'color.icon' => 'green', + 'color.tag' => 'green', + 'color.ansi' => 'green', + 'color.timeline' => 'green', + ), + self::PUBLISHED => array( + 'name' => pht('Closed'), + 'legacy' => 3, + 'icon' => 'fa-check-square-o', + 'closed' => true, + 'color.icon' => 'black', + 'color.tag' => 'indigo', + 'color.ansi' => 'cyan', + ), + self::ABANDONED => array( + 'name' => pht('Abandoned'), + 'legacy' => 4, + 'icon' => 'fa-plane', + 'closed' => true, + 'color.icon' => 'black', + 'color.tag' => 'indigo', + 'color.ansi' => null, + ), ); } - public static function isClosedStatus($status) { - return in_array($status, self::getClosedStatuses()); - } - } diff --git a/src/applications/differential/controller/DifferentialDiffCreateController.php b/src/applications/differential/controller/DifferentialDiffCreateController.php index e54d78d6a5..fb7994f033 100644 --- a/src/applications/differential/controller/DifferentialDiffCreateController.php +++ b/src/applications/differential/controller/DifferentialDiffCreateController.php @@ -199,10 +199,10 @@ final class DifferentialDiffCreateController extends DifferentialController { ->setValue($button)); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Diff')) + ->setHeaderText($title) ->setValidationException($validation_exception) ->setForm($form) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setFormErrors($errors); $crumbs = $this->buildApplicationCrumbs(); @@ -214,15 +214,10 @@ final class DifferentialDiffCreateController extends DifferentialController { $crumbs->addTextCrumb($title); $crumbs->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter(array( - $info_view, $form_box, + $info_view, )); return $this->newPage() diff --git a/src/applications/differential/controller/DifferentialDiffViewController.php b/src/applications/differential/controller/DifferentialDiffViewController.php index dae65bb200..7340687bc4 100644 --- a/src/applications/differential/controller/DifferentialDiffViewController.php +++ b/src/applications/differential/controller/DifferentialDiffViewController.php @@ -177,7 +177,7 @@ final class DifferentialDiffViewController extends DifferentialController { $revisions = id(new DifferentialRevisionQuery()) ->setViewer($viewer) ->withAuthors(array($viewer->getPHID())) - ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) + ->withIsOpen(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/differential/controller/DifferentialRevisionInlinesController.php b/src/applications/differential/controller/DifferentialRevisionInlinesController.php new file mode 100644 index 0000000000..c5eb063e99 --- /dev/null +++ b/src/applications/differential/controller/DifferentialRevisionInlinesController.php @@ -0,0 +1,190 @@ +getViewer(); + $id = $request->getURIData('id'); + + $revision = id(new DifferentialRevisionQuery()) + ->withIDs(array($id)) + ->setViewer($viewer) + ->needDiffIDs(true) + ->executeOne(); + if (!$revision) { + return new Aphront404Response(); + } + + $revision_monogram = $revision->getMonogram(); + $revision_uri = $revision->getURI(); + $revision_title = $revision->getTitle(); + + $query = id(new DifferentialInlineCommentQuery()) + ->setViewer($viewer) + ->needHidden(true) + ->withRevisionPHIDs(array($revision->getPHID())); + $inlines = $query->execute(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($revision_monogram, $revision_uri); + $crumbs->addTextCrumb(pht('Inline Comments')); + $crumbs->setBorder(true); + + $content = $this->renderInlineTable($revision, $inlines); + $header = $this->buildHeader($revision); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($content); + + return $this->newPage() + ->setTitle( + array( + "{$revision_monogram} {$revision_title}", + pht('Inlines'), + )) + ->setCrumbs($crumbs) + ->appendChild($view); + } + + private function buildHeader(DifferentialRevision $revision) { + $viewer = $this->getViewer(); + + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-chevron-left') + ->setHref($revision->getURI()) + ->setText(pht('Back to Revision')); + + return id(new PHUIHeaderView()) + ->setHeader($revision->getTitle()) + ->setUser($viewer) + ->setHeaderIcon('fa-cog') + ->addActionLink($button); + } + + private function renderInlineTable( + DifferentialRevision $revision, + array $inlines) { + + $viewer = $this->getViewer(); + $inlines = id(new PHUIDiffInlineThreader()) + ->reorderAndThreadCommments($inlines); + + $handle_phids = array(); + $changeset_ids = array(); + foreach ($inlines as $inline) { + $handle_phids[] = $inline->getAuthorPHID(); + $changeset_ids[] = $inline->getChangesetID(); + } + $handles = $viewer->loadHandles($handle_phids); + $handles = iterator_to_array($handles); + + if ($changeset_ids) { + $changesets = id(new DifferentialChangesetQuery()) + ->setViewer($viewer) + ->withIDs($changeset_ids) + ->execute(); + $changesets = mpull($changesets, null, 'getID'); + } else { + $changesets = array(); + } + + $current_changeset = head($revision->getDiffIDs()); + + $rows = array(); + foreach ($inlines as $inline) { + $status_icons = array(); + + $c_id = $inline->getChangesetID(); + $d_id = $changesets[$c_id]->getDiffID(); + + if ($d_id == $current_changeset) { + $diff_id = phutil_tag('strong', array(), pht('Current')); + } else { + $diff_id = pht('Diff %d', $d_id); + } + + $reviewer = $handles[$inline->getAuthorPHID()]->renderLink(); + $now = PhabricatorTime::getNow(); + $then = $inline->getDateModified(); + $datetime = phutil_format_relative_time($now - $then); + + $comment_href = $revision->getURI().'#inline-'.$inline->getID(); + $comment = phutil_tag( + 'a', + array( + 'href' => $comment_href, + ), + $inline->getContent()); + + $state = $inline->getFixedState(); + if ($state == PhabricatorInlineCommentInterface::STATE_DONE) { + $status_icons[] = id(new PHUIIconView()) + ->setIcon('fa-check green') + ->addClass('mmr'); + } else if ($inline->getReplyToCommentPHID() && + $inline->getAuthorPHID() == $revision->getAuthorPHID()) { + $status_icons[] = id(new PHUIIconView()) + ->setIcon('fa-commenting-o blue') + ->addClass('mmr'); + } else { + $status_icons[] = id(new PHUIIconView()) + ->setIcon('fa-circle-o grey') + ->addClass('mmr'); + } + + + if ($inline->getReplyToCommentPHID()) { + $reply_icon = id(new PHUIIconView()) + ->setIcon('fa-reply mmr darkgrey'); + $comment = array($reply_icon, $comment); + } + + $rows[] = array( + $diff_id, + $status_icons, + $reviewer, + AphrontTableView::renderSingleDisplayLine($comment), + $datetime, + ); + } + + $table = new AphrontTableView($rows); + $table->setHeaders( + array( + pht('Diff'), + pht('Status'), + pht('Reviewer'), + pht('Comment'), + pht('Created'), + )); + $table->setColumnClasses( + array( + '', + '', + '', + 'wide', + 'right', + )); + $table->setColumnVisibility( + array( + true, + true, + true, + true, + true, + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Inline Comments')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } + +} diff --git a/src/applications/differential/controller/DifferentialRevisionLandController.php b/src/applications/differential/controller/DifferentialRevisionLandController.php deleted file mode 100644 index 0e222370df..0000000000 --- a/src/applications/differential/controller/DifferentialRevisionLandController.php +++ /dev/null @@ -1,157 +0,0 @@ -getViewer(); - $revision_id = $request->getURIData('id'); - $strategy_class = $request->getURIData('strategy'); - - $revision = id(new DifferentialRevisionQuery()) - ->withIDs(array($revision_id)) - ->setViewer($viewer) - ->executeOne(); - if (!$revision) { - return new Aphront404Response(); - } - - if (is_subclass_of($strategy_class, 'DifferentialLandingStrategy')) { - $this->pushStrategy = newv($strategy_class, array()); - } else { - throw new Exception( - pht( - "Strategy type must be a valid class name and must subclass ". - "%s. '%s' is not a subclass of %s", - 'DifferentialLandingStrategy', - $strategy_class, - 'DifferentialLandingStrategy')); - } - - if ($request->isDialogFormPost()) { - $response = null; - $text = ''; - try { - $response = $this->attemptLand($revision, $request); - $title = pht('Success!'); - $text = pht('Revision was successfully landed.'); - } catch (Exception $ex) { - $title = pht('Failed to land revision'); - if ($ex instanceof PhutilProxyException) { - $text = hsprintf( - '%s:
%s
', - $ex->getMessage(), - $ex->getPreviousException()->getMessage()); - } else { - $text = phutil_tag('pre', array(), $ex->getMessage()); - } - $text = id(new PHUIInfoView()) - ->appendChild($text); - } - - if ($response instanceof AphrontDialogView) { - $dialog = $response; - } else { - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle($title) - ->appendChild(phutil_tag('p', array(), $text)) - ->addCancelButton('/D'.$revision_id, pht('Done')); - } - return id(new AphrontDialogResponse())->setDialog($dialog); - } - - $is_disabled = $this->pushStrategy->isActionDisabled( - $viewer, - $revision, - $revision->getRepository()); - if ($is_disabled) { - if (is_string($is_disabled)) { - $explain = $is_disabled; - } else { - $explain = pht('This action is not currently enabled.'); - } - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle(pht("Can't land revision")) - ->appendChild($explain) - ->addCancelButton('/D'.$revision_id); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - - - $prompt = hsprintf('%s

%s', - pht( - 'This will squash and rebase revision %s, and push it to '. - 'the default / master branch.', - $revision_id), - pht('It is an experimental feature and may not work.')); - - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle(pht('Land Revision %s?', $revision_id)) - ->appendChild($prompt) - ->setSubmitURI($request->getRequestURI()) - ->addSubmitButton(pht('Land it!')) - ->addCancelButton('/D'.$revision_id); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - - private function attemptLand($revision, $request) { - $status = $revision->getStatus(); - if ($status != ArcanistDifferentialRevisionStatus::ACCEPTED) { - throw new Exception(pht('Only Accepted revisions can be landed.')); - } - - $repository = $revision->getRepository(); - - if ($repository === null) { - throw new Exception(pht('Revision is not attached to a repository.')); - } - - $can_push = PhabricatorPolicyFilter::hasCapability( - $request->getUser(), - $repository, - DiffusionPushCapability::CAPABILITY); - - if (!$can_push) { - throw new Exception( - pht('You do not have permission to push to this repository.')); - } - - $lock = $this->lockRepository($repository); - - try { - $response = $this->pushStrategy->processLandRequest( - $request, - $revision, - $repository); - } catch (Exception $e) { - $lock->unlock(); - throw $e; - } - - $lock->unlock(); - - $looksoon = new ConduitCall( - 'diffusion.looksoon', - array( - 'repositories' => array($repository->getPHID()), - )); - $looksoon->setUser($request->getUser()); - $looksoon->execute(); - - return $response; - } - - private function lockRepository($repository) { - $lock_name = __CLASS__.':'.($repository->getPHID()); - $lock = PhabricatorGlobalLock::newLock($lock_name); - $lock->lock(); - return $lock; - } - -} diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 6cb39c1510..06d8cf0a3f 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -156,7 +156,7 @@ final class DifferentialRevisionViewController extends DifferentialController { phutil_tag( 'a', array( - 'class' => 'button grey', + 'class' => 'button button-grey', 'href' => $request_uri ->alter('large', 'true') ->setFragment('toc'), @@ -281,6 +281,12 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setTitle(pht('Diff %s', $target->getID())) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); + + $revision_id = $revision->getID(); + $inline_list_uri = "/revision/inlines/{$revision_id}/"; + $inline_list_uri = $this->getApplicationURI($inline_list_uri); + $changeset_view->setInlineListURI($inline_list_uri); + if ($repository) { $changeset_view->setRepository($repository); } @@ -465,7 +471,6 @@ final class DifferentialRevisionViewController extends DifferentialController { } Javelin::initBehavior('differential-user-select'); - Javelin::initBehavior('differential-keyboard-navigation'); $view = id(new PHUITwoColumnView()) ->setHeader($header) @@ -503,11 +508,13 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setPolicyObject($revision) ->setHeaderIcon('fa-cog'); - $status = $revision->getStatus(); - $status_name = - DifferentialRevisionStatus::renderFullDescription($status); + $status_tag = id(new PHUITagView()) + ->setName($revision->getStatusDisplayName()) + ->setIcon($revision->getStatusIcon()) + ->setColor($revision->getStatusIconColor()) + ->setType(PHUITagView::TYPE_SHADE); - $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name); + $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_tag); return $view; } @@ -610,6 +617,29 @@ final class DifferentialRevisionViewController extends DifferentialController { $curtain->addAction($relationship_submenu); } + $repository = $revision->getRepository(); + if ($repository && $repository->canPerformAutomation()) { + $revision_id = $revision->getID(); + + $op = new DrydockLandRepositoryOperation(); + $barrier = $op->getBarrierToLanding($viewer, $revision); + + if ($barrier) { + $can_land = false; + } else { + $can_land = true; + } + + $action = id(new PhabricatorActionView()) + ->setName(pht('Land Revision')) + ->setIcon('fa-fighter-jet') + ->setHref("/differential/revision/operation/{$revision_id}/") + ->setWorkflow(true) + ->setDisabled(!$can_land); + + $curtain->addAction($action); + } + return $curtain; } @@ -778,7 +808,7 @@ final class DifferentialRevisionViewController extends DifferentialController { $query = id(new DifferentialRevisionQuery()) ->setViewer($this->getRequest()->getUser()) - ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) + ->withIsOpen(true) ->withUpdatedEpochBetween($recent, null) ->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED) ->setLimit(10) diff --git a/src/applications/differential/customfield/DifferentialBranchField.php b/src/applications/differential/customfield/DifferentialBranchField.php index 16be0ce0c9..2387e3cd3f 100644 --- a/src/applications/differential/customfield/DifferentialBranchField.php +++ b/src/applications/differential/customfield/DifferentialBranchField.php @@ -76,16 +76,17 @@ final class DifferentialBranchField PhabricatorApplicationTransactionEditor $editor, array $xactions) { - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; + $revision = $this->getObject(); // Show the "BRANCH" section only if there's a new diff or the revision // is "Accepted". - if ((!$editor->getDiffUpdateTransaction($xactions)) && - ($this->getObject()->getStatus() != $status_accepted)) { + $is_update = (bool)$editor->getDiffUpdateTransaction($xactions); + $is_accepted = $revision->isAccepted(); + if (!$is_update && !$is_accepted) { return; } - $branch = $this->getBranchDescription($this->getObject()->getActiveDiff()); + $branch = $this->getBranchDescription($revision->getActiveDiff()); if ($branch === null) { return; } diff --git a/src/applications/differential/customfield/DifferentialReviewersField.php b/src/applications/differential/customfield/DifferentialReviewersField.php index 7633bfb492..f835854e2f 100644 --- a/src/applications/differential/customfield/DifferentialReviewersField.php +++ b/src/applications/differential/customfield/DifferentialReviewersField.php @@ -68,8 +68,7 @@ final class DifferentialReviewersField public function getWarningsForRevisionHeader(array $handles) { $revision = $this->getObject(); - $status_needs_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; - if ($revision->getStatus() != $status_needs_review) { + if (!$revision->isNeedsReview()) { return array(); } diff --git a/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php b/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php index 9583486c98..59223f20da 100644 --- a/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php +++ b/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php @@ -35,8 +35,7 @@ final class DifferentialDoorkeeperRevisionFeedStoryPublisher } public function getActiveUserPHIDs($object) { - $status = $object->getStatus(); - if ($status == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) { + if ($object->isNeedsReview()) { return $object->getReviewerPHIDs(); } else { return array(); @@ -44,8 +43,7 @@ final class DifferentialDoorkeeperRevisionFeedStoryPublisher } public function getPassiveUserPHIDs($object) { - $status = $object->getStatus(); - if ($status == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) { + if ($object->isNeedsReview()) { return array(); } else { return $object->getReviewerPHIDs(); diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 0086c101e2..e916728c86 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -69,9 +69,7 @@ final class DifferentialTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_INLINESTATE; - $types[] = DifferentialTransaction::TYPE_ACTION; $types[] = DifferentialTransaction::TYPE_INLINE; - $types[] = DifferentialTransaction::TYPE_STATUS; $types[] = DifferentialTransaction::TYPE_UPDATE; return $types; @@ -82,8 +80,6 @@ final class DifferentialTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case DifferentialTransaction::TYPE_ACTION: - return null; case DifferentialTransaction::TYPE_INLINE: return null; case DifferentialTransaction::TYPE_UPDATE: @@ -102,7 +98,6 @@ final class DifferentialTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case DifferentialTransaction::TYPE_ACTION: case DifferentialTransaction::TYPE_UPDATE: return $xaction->getNewValue(); case DifferentialTransaction::TYPE_INLINE: @@ -112,63 +107,20 @@ final class DifferentialTransactionEditor return parent::getCustomTransactionNewValue($object, $xaction); } - protected function transactionHasEffect( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $actor_phid = $this->getActingAsPHID(); - - switch ($xaction->getTransactionType()) { - case DifferentialTransaction::TYPE_INLINE: - return $xaction->hasComment(); - case DifferentialTransaction::TYPE_ACTION: - $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; - $status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED; - $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; - $status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION; - $status_plan = ArcanistDifferentialRevisionStatus::CHANGES_PLANNED; - - $action_type = $xaction->getNewValue(); - switch ($action_type) { - case DifferentialAction::ACTION_CLOSE: - return ($object->getStatus() != $status_closed); - case DifferentialAction::ACTION_ABANDON: - return ($object->getStatus() != $status_abandoned); - case DifferentialAction::ACTION_RECLAIM: - return ($object->getStatus() == $status_abandoned); - case DifferentialAction::ACTION_REOPEN: - return ($object->getStatus() == $status_closed); - case DifferentialAction::ACTION_RETHINK: - return ($object->getStatus() != $status_plan); - case DifferentialAction::ACTION_CLAIM: - return ($actor_phid != $object->getAuthorPHID()); - } - } - - return parent::transactionHasEffect($object, $xaction); - } - protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { - $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; - $status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION; - $status_plan = ArcanistDifferentialRevisionStatus::CHANGES_PLANNED; - $status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED; - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; - switch ($xaction->getTransactionType()) { case DifferentialTransaction::TYPE_INLINE: return; case DifferentialTransaction::TYPE_UPDATE: if (!$this->getIsCloseByCommit()) { - switch ($object->getStatus()) { - case $status_revision: - case $status_plan: - case $status_abandoned: - $object->setStatus($status_review); - break; + if ($object->isNeedsRevision() || + $object->isChangePlanned() || + $object->isAbandoned()) { + $object->setModernRevisionStatus( + DifferentialRevisionStatus::NEEDS_REVIEW); } } @@ -184,38 +136,6 @@ final class DifferentialTransactionEditor // TODO: Update the `diffPHID` once we add that. return; - case DifferentialTransaction::TYPE_ACTION: - switch ($xaction->getNewValue()) { - case DifferentialAction::ACTION_ABANDON: - $object->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED); - return; - case DifferentialAction::ACTION_RETHINK: - $object->setStatus($status_plan); - return; - case DifferentialAction::ACTION_RECLAIM: - $object->setStatus($status_review); - return; - case DifferentialAction::ACTION_REOPEN: - $object->setStatus($status_review); - return; - case DifferentialAction::ACTION_CLOSE: - $old_status = $object->getStatus(); - $object->setStatus(ArcanistDifferentialRevisionStatus::CLOSED); - $was_accepted = ($old_status == $status_accepted); - $object->setProperty( - DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED, - $was_accepted); - return; - case DifferentialAction::ACTION_CLAIM: - $object->setAuthorPHID($this->getActingAsPHID()); - return; - default: - throw new Exception( - pht( - 'Differential action "%s" is not a valid action!', - $xaction->getNewValue())); - } - break; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -255,9 +175,6 @@ final class DifferentialTransactionEditor $actor_phid = $this->getActingAsPHID(); $type_edge = PhabricatorTransactions::TYPE_EDGE; - $status_plan = ArcanistDifferentialRevisionStatus::CHANGES_PLANNED; - - $edge_reviewer = DifferentialRevisionHasReviewerEdgeType::EDGECONST; $edge_ref_task = DifferentialRevisionHasTaskEdgeType::EDGECONST; $is_sticky_accept = PhabricatorEnv::getEnvConfig( @@ -280,7 +197,7 @@ final class DifferentialTransactionEditor case DifferentialRevisionRequestReviewTransaction::TRANSACTIONTYPE: $downgrade_rejects = true; if ((!$is_sticky_accept) || - ($object->getStatus() != $status_plan)) { + (!$object->isChangePlanned())) { // If the old state isn't "changes planned", downgrade the accepts. // This exception allows an accepted revision to go through // "Plan Changes" -> "Request Review" and return to "accepted" if @@ -297,48 +214,6 @@ final class DifferentialTransactionEditor $old_accept = DifferentialReviewerStatus::STATUS_ACCEPTED_OLDER; $old_reject = DifferentialReviewerStatus::STATUS_REJECTED_OLDER; - if ($downgrade_rejects || $downgrade_accepts) { - // When a revision is updated, change all "reject" to "rejected older - // revision". This means we won't immediately push the update back into - // "needs review", but outstanding rejects will still block it from - // moving to "accepted". - - // We also do this for "Request Review", even though the diff is not - // updated directly. Essentially, this acts like an update which doesn't - // actually change the diff text. - - $edits = array(); - foreach ($object->getReviewers() as $reviewer) { - if ($downgrade_rejects) { - if ($reviewer->getReviewerStatus() == $new_reject) { - $edits[$reviewer->getReviewerPHID()] = array( - 'data' => array( - 'status' => $old_reject, - ), - ); - } - } - - if ($downgrade_accepts) { - if ($reviewer->getReviewerStatus() == $new_accept) { - $edits[$reviewer->getReviewerPHID()] = array( - 'data' => array( - 'status' => $old_accept, - ), - ); - } - } - } - - if ($edits) { - $results[] = id(new DifferentialTransaction()) - ->setTransactionType($type_edge) - ->setMetadataValue('edge:type', $edge_reviewer) - ->setIgnoreOnNoEffect(true) - ->setNewValue(array('+' => $edits)); - } - } - $downgrade = array(); if ($downgrade_accepts) { $downgrade[] = DifferentialReviewerStatus::STATUS_ACCEPTED; @@ -397,56 +272,9 @@ final class DifferentialTransactionEditor } break; - case PhabricatorTransactions::TYPE_COMMENT: - // When a user leaves a comment, upgrade their reviewer status from - // "added" to "commented" if they're also a reviewer. We may further - // upgrade this based on other actions in the transaction group. - - if ($this->hasReviewTransaction) { - // If we're also applying a review transaction, skip this. - break; - } - - $status_added = DifferentialReviewerStatus::STATUS_ADDED; - $status_commented = DifferentialReviewerStatus::STATUS_COMMENTED; - - $data = array( - 'status' => $status_commented, - ); - - $edits = array(); - foreach ($object->getReviewers() as $reviewer) { - if ($reviewer->getReviewerPHID() == $actor_phid) { - if ($reviewer->getReviewerStatus() == $status_added) { - $edits[$actor_phid] = array( - 'data' => $data, - ); - } - } - } - - if ($edits) { - $results[] = id(new DifferentialTransaction()) - ->setTransactionType($type_edge) - ->setMetadataValue('edge:type', $edge_reviewer) - ->setIgnoreOnNoEffect(true) - ->setNewValue(array('+' => $edits)); - } - break; - case DifferentialRevisionCommandeerTransaction::TRANSACTIONTYPE: $is_commandeer = true; break; - - case DifferentialTransaction::TYPE_ACTION: - $action_type = $xaction->getNewValue(); - - switch ($action_type) { - case DifferentialAction::ACTION_CLAIM: - $is_commandeer = true; - break; - } - break; } if ($is_commandeer) { @@ -456,7 +284,6 @@ final class DifferentialTransactionEditor if (!$this->didExpandInlineState) { switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: - case DifferentialTransaction::TYPE_ACTION: case DifferentialTransaction::TYPE_UPDATE: case DifferentialTransaction::TYPE_INLINE: $this->didExpandInlineState = true; @@ -502,8 +329,6 @@ final class DifferentialTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case DifferentialTransaction::TYPE_ACTION: - return; case DifferentialTransaction::TYPE_INLINE: $reply = $xaction->getComment()->getReplyToComment(); if ($reply && !$reply->getHasReplies()) { @@ -575,33 +400,6 @@ final class DifferentialTransactionEditor return parent::applyBuiltinExternalTransaction($object, $xaction); } - protected function mergeEdgeData($type, array $u, array $v) { - $result = parent::mergeEdgeData($type, $u, $v); - - switch ($type) { - case DifferentialRevisionHasReviewerEdgeType::EDGECONST: - // When the same reviewer has their status updated by multiple - // transactions, we want the strongest status to win. An example of - // this is when a user adds a comment and also accepts a revision which - // they are a reviewer on. The comment creates a "commented" status, - // while the accept creates an "accepted" status. Since accept is - // stronger, it should win and persist. - - $u_status = idx($u, 'status'); - $v_status = idx($v, 'status'); - $u_str = DifferentialReviewerStatus::getStatusStrength($u_status); - $v_str = DifferentialReviewerStatus::getStatusStrength($v_status); - if ($u_str > $v_str) { - $result['status'] = $u_status; - } else { - $result['status'] = $v_status; - } - break; - } - - return $result; - } - protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { @@ -641,101 +439,112 @@ final class DifferentialTransactionEditor } } - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; - $status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION; - $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; - - $is_sticky_accept = PhabricatorEnv::getEnvConfig( - 'differential.sticky-accept'); - - $old_status = $object->getStatus(); - $active_diff = $object->getActiveDiff(); - switch ($old_status) { - case $status_accepted: - case $status_revision: - case $status_review: - // Try to move a revision to "accepted". We look for: - // - // - at least one accepting reviewer who is a user; and - // - no rejects; and - // - no rejects of older diffs; and - // - no blocking reviewers. - - $has_accepting_user = false; - $has_rejecting_reviewer = false; - $has_rejecting_older_reviewer = false; - $has_blocking_reviewer = false; - foreach ($object->getReviewers() as $reviewer) { - $reviewer_status = $reviewer->getReviewerStatus(); - switch ($reviewer_status) { - case DifferentialReviewerStatus::STATUS_REJECTED: - $active_phid = $active_diff->getPHID(); - if ($reviewer->isRejected($active_phid)) { - $has_rejecting_reviewer = true; - } else { - $has_rejecting_older_reviewer = true; - } - break; - case DifferentialReviewerStatus::STATUS_REJECTED_OLDER: - $has_rejecting_older_reviewer = true; - break; - case DifferentialReviewerStatus::STATUS_BLOCKING: - $has_blocking_reviewer = true; - break; - case DifferentialReviewerStatus::STATUS_ACCEPTED: - if ($reviewer->isUser()) { - $active_phid = $active_diff->getPHID(); - if ($reviewer->isAccepted($active_phid)) { - $has_accepting_user = true; - } - } - break; - } - } - - $new_status = null; - if ($has_accepting_user && - !$has_rejecting_reviewer && - !$has_rejecting_older_reviewer && - !$has_blocking_reviewer) { - $new_status = $status_accepted; - } else if ($has_rejecting_reviewer) { - // This isn't accepted, and there's at least one rejecting reviewer, - // so the revision needs changes. This usually happens after a - // "reject". - $new_status = $status_revision; - } else if ($old_status == $status_accepted) { - // This revision was accepted, but it no longer satisfies the - // conditions for acceptance. This usually happens after an accepting - // reviewer resigns or is removed. - $new_status = $status_review; - } - - if ($new_status !== null && ($new_status != $old_status)) { - $xaction = id(new DifferentialTransaction()) - ->setTransactionType(DifferentialTransaction::TYPE_STATUS) - ->setOldValue($old_status) - ->setNewValue($new_status); - - $xaction = $this->populateTransaction($object, $xaction)->save(); - - $xactions[] = $xaction; - - $object->setStatus($new_status)->save(); - } - break; - default: - // Revisions can't transition out of other statuses (like closed or - // abandoned) as a side effect of reviewer status changes. - break; - } - - + $xactions = $this->updateReviewStatus($object, $xactions); $this->markReviewerComments($object, $xactions); return $xactions; } + private function updateReviewStatus( + DifferentialRevision $revision, + array $xactions) { + + $was_accepted = $revision->isAccepted(); + $was_revision = $revision->isNeedsRevision(); + $was_review = $revision->isNeedsReview(); + if (!$was_accepted && !$was_revision && !$was_review) { + // Revisions can't transition out of other statuses (like closed or + // abandoned) as a side effect of reviewer status changes. + return $xactions; + } + + // Try to move a revision to "accepted". We look for: + // + // - at least one accepting reviewer who is a user; and + // - no rejects; and + // - no rejects of older diffs; and + // - no blocking reviewers. + + $has_accepting_user = false; + $has_rejecting_reviewer = false; + $has_rejecting_older_reviewer = false; + $has_blocking_reviewer = false; + + $active_diff = $revision->getActiveDiff(); + foreach ($revision->getReviewers() as $reviewer) { + $reviewer_status = $reviewer->getReviewerStatus(); + switch ($reviewer_status) { + case DifferentialReviewerStatus::STATUS_REJECTED: + $active_phid = $active_diff->getPHID(); + if ($reviewer->isRejected($active_phid)) { + $has_rejecting_reviewer = true; + } else { + $has_rejecting_older_reviewer = true; + } + break; + case DifferentialReviewerStatus::STATUS_REJECTED_OLDER: + $has_rejecting_older_reviewer = true; + break; + case DifferentialReviewerStatus::STATUS_BLOCKING: + $has_blocking_reviewer = true; + break; + case DifferentialReviewerStatus::STATUS_ACCEPTED: + if ($reviewer->isUser()) { + $active_phid = $active_diff->getPHID(); + if ($reviewer->isAccepted($active_phid)) { + $has_accepting_user = true; + } + } + break; + } + } + + $new_status = null; + if ($has_accepting_user && + !$has_rejecting_reviewer && + !$has_rejecting_older_reviewer && + !$has_blocking_reviewer) { + $new_status = DifferentialRevisionStatus::ACCEPTED; + } else if ($has_rejecting_reviewer) { + // This isn't accepted, and there's at least one rejecting reviewer, + // so the revision needs changes. This usually happens after a + // "reject". + $new_status = DifferentialRevisionStatus::NEEDS_REVISION; + } else if ($was_accepted) { + // This revision was accepted, but it no longer satisfies the + // conditions for acceptance. This usually happens after an accepting + // reviewer resigns or is removed. + $new_status = DifferentialRevisionStatus::NEEDS_REVIEW; + } + + if ($new_status === null) { + return $xactions; + } + + $old_status = $revision->getModernRevisionStatus(); + if ($new_status == $old_status) { + return $xactions; + } + + $xaction = id(new DifferentialTransaction()) + ->setTransactionType( + DifferentialRevisionStatusTransaction::TRANSACTIONTYPE) + ->setOldValue($old_status) + ->setNewValue($new_status); + + $xaction = $this->populateTransaction($revision, $xaction) + ->save(); + $xactions[] = $xaction; + + // Save the status adjustment we made earlier. + $revision + ->setModernRevisionStatus($new_status) + ->save(); + + return $xactions; + } + + protected function validateTransaction( PhabricatorLiskDAO $object, $type, @@ -748,40 +557,6 @@ final class DifferentialTransactionEditor foreach ($xactions as $xaction) { switch ($type) { - case PhabricatorTransactions::TYPE_EDGE: - switch ($xaction->getMetadataValue('edge:type')) { - case DifferentialRevisionHasReviewerEdgeType::EDGECONST: - - // Prevent the author from becoming a reviewer. - - // NOTE: This is pretty gross, but this restriction is unusual. - // If we end up with too much more of this, we should try to clean - // this up -- maybe by moving validation to after transactions - // are adjusted (so we can just examine the final value) or adding - // a second phase there? - - $author_phid = $object->getAuthorPHID(); - $new = $xaction->getNewValue(); - - $add = idx($new, '+', array()); - $eq = idx($new, '=', array()); - $phids = array_keys($add + $eq); - - foreach ($phids as $phid) { - if (($phid == $author_phid) && - !$allow_self_accept && - !$xaction->getIsCommandeerSideEffect()) { - $errors[] = - new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('The author of a revision can not be a reviewer.'), - $xaction); - } - } - break; - } - break; case DifferentialTransaction::TYPE_UPDATE: $diff = $this->loadDiff($xaction->getNewValue()); if (!$diff) { @@ -801,172 +576,12 @@ final class DifferentialTransactionEditor $xaction); } break; - case DifferentialTransaction::TYPE_ACTION: - $error = $this->validateDifferentialAction( - $object, - $type, - $xaction, - $xaction->getNewValue()); - if ($error) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - $error, - $xaction); - } - break; } } return $errors; } - private function validateDifferentialAction( - DifferentialRevision $revision, - $type, - DifferentialTransaction $xaction, - $action) { - - $author_phid = $revision->getAuthorPHID(); - $actor_phid = $this->getActingAsPHID(); - $actor_is_author = ($author_phid == $actor_phid); - - $config_abandon_key = 'differential.always-allow-abandon'; - $always_allow_abandon = PhabricatorEnv::getEnvConfig($config_abandon_key); - - $config_close_key = 'differential.always-allow-close'; - $always_allow_close = PhabricatorEnv::getEnvConfig($config_close_key); - - $config_reopen_key = 'differential.allow-reopen'; - $allow_reopen = PhabricatorEnv::getEnvConfig($config_reopen_key); - - $config_self_accept_key = 'differential.allow-self-accept'; - $allow_self_accept = PhabricatorEnv::getEnvConfig($config_self_accept_key); - - $revision_status = $revision->getStatus(); - - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; - $status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED; - $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; - - switch ($action) { - case DifferentialAction::ACTION_CLAIM: - // You can claim a revision if you're not the owner. If you are, this - // is a no-op rather than invalid. - - if ($revision_status == $status_closed) { - return pht( - 'You can not commandeer this revision because it has already been '. - 'closed.'); - } - break; - - case DifferentialAction::ACTION_ABANDON: - if (!$actor_is_author && !$always_allow_abandon) { - return pht( - 'You can not abandon this revision because you do not own it. '. - 'You can only abandon revisions you own.'); - } - - if ($revision_status == $status_closed) { - return pht( - 'You can not abandon this revision because it has already been '. - 'closed.'); - } - - // NOTE: Abandons of already-abandoned revisions are treated as no-op - // instead of invalid. Other abandons are OK. - - break; - - case DifferentialAction::ACTION_RECLAIM: - if (!$actor_is_author) { - return pht( - 'You can not reclaim this revision because you do not own '. - 'it. You can only reclaim revisions you own.'); - } - - if ($revision_status == $status_closed) { - return pht( - 'You can not reclaim this revision because it has already been '. - 'closed.'); - } - - // NOTE: Reclaims of other non-abandoned revisions are treated as no-op - // instead of invalid. - - break; - - case DifferentialAction::ACTION_REOPEN: - if (!$allow_reopen) { - return pht( - 'The reopen action is not enabled on this Phabricator install. '. - 'Adjust your configuration to enable it.'); - } - - // NOTE: If the revision is not closed, this is caught as a no-op - // instead of an invalid transaction. - - break; - - case DifferentialAction::ACTION_RETHINK: - if (!$actor_is_author) { - return pht( - 'You can not plan changes to this revision because you do not '. - 'own it. To plan changes to a revision, you must be its owner.'); - } - - switch ($revision_status) { - case ArcanistDifferentialRevisionStatus::ACCEPTED: - case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: - case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: - // These are OK. - break; - case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED: - // Let this through, it's a no-op. - break; - case ArcanistDifferentialRevisionStatus::ABANDONED: - return pht( - 'You can not plan changes to this revision because it has '. - 'been abandoned.'); - case ArcanistDifferentialRevisionStatus::CLOSED: - return pht( - 'You can not plan changes to this revision because it has '. - 'already been closed.'); - default: - throw new Exception( - pht( - 'Encountered unexpected revision status ("%s") when '. - 'validating "%s" action.', - $revision_status, - $action)); - } - break; - - case DifferentialAction::ACTION_CLOSE: - // We force revisions closed when we discover a corresponding commit. - // In this case, revisions are allowed to transition to closed from - // any state. This is an automated action taken by the daemons. - - if (!$this->getIsCloseByCommit()) { - if (!$actor_is_author && !$always_allow_close) { - return pht( - 'You can not close this revision because you do not own it. To '. - 'close a revision, you must be its owner.'); - } - - if ($revision_status != $status_accepted) { - return pht( - 'You can not close this revision because it has not been '. - 'accepted. You can only close accepted revisions.'); - } - } - break; - } - - return null; - } - protected function sortTransactions(array $xactions) { $xactions = parent::sortTransactions($xactions); @@ -1191,12 +806,23 @@ final class DifferentialTransactionEditor array $changes, PhutilMarkupEngine $engine) { - $flat_blocks = mpull($changes, 'getNewValue'); - $huge_block = implode("\n\n", $flat_blocks); - + // For "Fixes ..." and "Depends on ...", we're only going to look at + // content blocks which are part of the revision itself (like "Summary" + // and "Test Plan"), not comments. + $content_parts = array(); + foreach ($changes as $change) { + if ($change->getTransaction()->isCommentTransaction()) { + continue; + } + $content_parts[] = $change->getNewValue(); + } + if (!$content_parts) { + return array(); + } + $content_block = implode("\n\n", $content_parts); $task_map = array(); $task_refs = id(new ManiphestCustomFieldStatusParser()) - ->parseCorpus($huge_block); + ->parseCorpus($content_block); foreach ($task_refs as $match) { foreach ($match['monograms'] as $monogram) { $task_id = (int)trim($monogram, 'tT'); @@ -1206,7 +832,7 @@ final class DifferentialTransactionEditor $rev_map = array(); $rev_refs = id(new DifferentialCustomFieldDependsOnParser()) - ->parseCorpus($huge_block); + ->parseCorpus($content_block); foreach ($rev_refs as $match) { foreach ($match['monograms'] as $monogram) { $rev_id = (int)trim($monogram, 'dD'); @@ -1363,14 +989,6 @@ final class DifferentialTransactionEditor // When users commandeer revisions, we may need to trigger // signatures or author-based rules. return true; - case DifferentialTransaction::TYPE_ACTION: - switch ($xaction->getNewValue()) { - case DifferentialAction::ACTION_CLAIM: - // When users commandeer revisions, we may need to trigger - // signatures or author-based rules. - return true; - } - break; } } diff --git a/src/applications/differential/field/DifferentialTestPlanCommitMessageField.php b/src/applications/differential/field/DifferentialTestPlanCommitMessageField.php index 178e51d985..a477a9036e 100644 --- a/src/applications/differential/field/DifferentialTestPlanCommitMessageField.php +++ b/src/applications/differential/field/DifferentialTestPlanCommitMessageField.php @@ -50,11 +50,4 @@ final class DifferentialTestPlanCommitMessageField ); } - public function validateTransactions($object, array $xactions) { - return $this->validateCommitMessageCorpusTransactions( - $object, - $xactions, - pht('Test Plan')); - } - } diff --git a/src/applications/differential/landing/DifferentialGitHubLandingStrategy.php b/src/applications/differential/landing/DifferentialGitHubLandingStrategy.php deleted file mode 100644 index b4ff727cbe..0000000000 --- a/src/applications/differential/landing/DifferentialGitHubLandingStrategy.php +++ /dev/null @@ -1,186 +0,0 @@ -getUser(); - $this->init($viewer, $repository); - - $workspace = $this->getGitWorkspace($repository); - - try { - id(new DifferentialHostedGitLandingStrategy()) - ->commitRevisionToWorkspace($revision, $workspace, $viewer); - } catch (Exception $e) { - throw new PhutilProxyException(pht('Failed to commit patch.'), $e); - } - - try { - $this->pushWorkspaceRepository($repository, $workspace); - } catch (Exception $e) { - // If it's a permission problem, we know more than git. - $dialog = $this->verifyRemotePermissions($viewer, $revision, $repository); - if ($dialog) { - return $dialog; - } - - // Else, throw what git said. - throw new PhutilProxyException( - pht('Failed to push changes upstream.'), - $e); - } - } - - /** - * Returns PhabricatorActionView or an array of PhabricatorActionView or null. - */ - public function createMenuItem( - PhabricatorUser $viewer, - DifferentialRevision $revision, - PhabricatorRepository $repository) { - - // TODO: This temporarily disables this action, because it doesn't work - // and is confusing to users. If you want to use it, comment out this line - // for now and we'll provide real support eventually. - return; - - $vcs = $repository->getVersionControlSystem(); - if ($vcs !== PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) { - return; - } - - if ($repository->isHosted()) { - return; - } - - try { - // These throw when failing. - $this->init($viewer, $repository); - $this->findGitHubRepo($repository); - } catch (Exception $e) { - return; - } - - return $this->createActionView($revision, pht('Land to GitHub')) - ->setIcon('fa-cloud-upload'); - } - - public function pushWorkspaceRepository( - PhabricatorRepository $repository, - ArcanistRepositoryAPI $workspace) { - - $token = $this->getAccessToken(); - - $github_repo = $this->findGitHubRepo($repository); - - $remote = urisprintf( - 'https://%s:x-oauth-basic@%s/%s.git', - $token, - $this->provider->getProviderDomain(), - $github_repo); - - $workspace->execxLocal( - 'push %P HEAD:master', - new PhutilOpaqueEnvelope($remote)); - } - - private function init($viewer, $repository) { - $repo_uri = $repository->getRemoteURIObject(); - $repo_domain = $repo_uri->getDomain(); - - $this->account = id(new PhabricatorExternalAccountQuery()) - ->setViewer($viewer) - ->withUserPHIDs(array($viewer->getPHID())) - ->withAccountTypes(array('github')) - ->withAccountDomains(array($repo_domain)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - - if (!$this->account) { - throw new Exception( - pht('No matching GitHub account found for %s.', $repo_domain)); - } - - $this->provider = PhabricatorAuthProvider::getEnabledProviderByKey( - $this->account->getProviderKey()); - if (!$this->provider) { - throw new Exception( - pht('GitHub provider for %s is not enabled.', $repo_domain)); - } - } - - private function findGitHubRepo(PhabricatorRepository $repository) { - $repo_uri = $repository->getRemoteURIObject(); - - $repo_path = $repo_uri->getPath(); - - if (substr($repo_path, -4) == '.git') { - $repo_path = substr($repo_path, 0, -4); - } - $repo_path = ltrim($repo_path, '/'); - - return $repo_path; - } - - private function getAccessToken() { - return $this->provider->getOAuthAccessToken($this->account); - } - - private function verifyRemotePermissions($viewer, $revision, $repository) { - $github_user = $this->account->getUsername(); - $github_repo = $this->findGitHubRepo($repository); - - $uri = urisprintf( - 'https://api.github.com/repos/%s/collaborators/%s', - $github_repo, - $github_user); - - $uri = new PhutilURI($uri); - $uri->setQueryParam('access_token', $this->getAccessToken()); - list($status, $body, $headers) = id(new HTTPSFuture($uri))->resolve(); - - // Likely status codes: - // 204 No Content: Has permissions. Token might be too weak. - // 404 Not Found: Not a collaborator. - // 401 Unauthorized: Token is bad/revoked. - - $no_permission = ($status->getStatusCode() == 404); - - if ($no_permission) { - throw new Exception( - pht( - "You don't have permission to push to this repository. ". - "Push permissions for this repository are managed on GitHub.")); - } - - $scopes = BaseHTTPFuture::getHeader($headers, 'X-OAuth-Scopes'); - if (strpos($scopes, 'public_repo') === false) { - $provider_key = $this->provider->getProviderKey(); - $refresh_token_uri = new PhutilURI("/auth/refresh/{$provider_key}/"); - $refresh_token_uri->setQueryParam('scope', 'public_repo'); - - return id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle(pht('Stronger token needed')) - ->appendChild(pht( - 'In order to complete this action, you need a '. - 'stronger GitHub token.')) - ->setSubmitURI($refresh_token_uri) - ->addCancelButton('/D'.$revision->getId()) - ->setDisableWorkflowOnSubmit(true) - ->addSubmitButton(pht('Refresh Account Link')); - } - } -} diff --git a/src/applications/differential/landing/DifferentialHostedGitLandingStrategy.php b/src/applications/differential/landing/DifferentialHostedGitLandingStrategy.php deleted file mode 100644 index cc0cf97998..0000000000 --- a/src/applications/differential/landing/DifferentialHostedGitLandingStrategy.php +++ /dev/null @@ -1,127 +0,0 @@ -getUser(); - $workspace = $this->getGitWorkspace($repository); - - try { - $this->commitRevisionToWorkspace($revision, $workspace, $viewer); - } catch (Exception $e) { - throw new PhutilProxyException( - pht('Failed to commit patch.'), - $e); - } - - try { - $this->pushWorkspaceRepository($repository, $workspace, $viewer); - } catch (Exception $e) { - throw new PhutilProxyException( - pht('Failed to push changes upstream.'), - $e); - } - } - - public function commitRevisionToWorkspace( - DifferentialRevision $revision, - ArcanistRepositoryAPI $workspace, - PhabricatorUser $user) { - - $diff_id = $revision->loadActiveDiff()->getID(); - - $call = new ConduitCall( - 'differential.getrawdiff', - array( - 'diffID' => $diff_id, - )); - - $call->setUser($user); - $raw_diff = $call->execute(); - - $missing_binary = - "\nindex " - ."0000000000000000000000000000000000000000.." - ."0000000000000000000000000000000000000000\n"; - if (strpos($raw_diff, $missing_binary) !== false) { - throw new Exception(pht('Patch is missing content for a binary file')); - } - - $future = $workspace->execFutureLocal('apply --index -'); - $future->write($raw_diff); - $future->resolvex(); - - $workspace->reloadWorkingCopy(); - - $call = new ConduitCall( - 'differential.getcommitmessage', - array( - 'revision_id' => $revision->getID(), - )); - - $call->setUser($user); - $message = $call->execute(); - - $author = id(new PhabricatorUser())->loadOneWhere( - 'phid = %s', - $revision->getAuthorPHID()); - - $author_string = sprintf( - '%s <%s>', - $author->getRealName(), - $author->loadPrimaryEmailAddress()); - $author_date = $revision->getDateCreated(); - - $workspace->execxLocal( - '-c user.name=%s -c user.email=%s '. - 'commit --date=%s --author=%s '. - '--message=%s', - // -c will set the 'committer' - $user->getRealName(), - $user->loadPrimaryEmailAddress(), - $author_date, - $author_string, - $message); - } - - public function pushWorkspaceRepository( - PhabricatorRepository $repository, - ArcanistRepositoryAPI $workspace, - PhabricatorUser $user) { - - $workspace->execxLocal('push origin HEAD:master'); - } - - public function createMenuItem( - PhabricatorUser $viewer, - DifferentialRevision $revision, - PhabricatorRepository $repository) { - - $vcs = $repository->getVersionControlSystem(); - if ($vcs !== PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) { - return; - } - - if (!$repository->isHosted()) { - return; - } - - if (!$repository->isWorkingCopyBare()) { - return; - } - - // TODO: This temporarily disables this action, because it doesn't work - // and is confusing to users. If you want to use it, comment out this line - // for now and we'll provide real support eventually. - return; - - return $this->createActionView( - $revision, - pht('Land to Hosted Repository')); - } -} diff --git a/src/applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php b/src/applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php deleted file mode 100644 index 38f7160958..0000000000 --- a/src/applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php +++ /dev/null @@ -1,106 +0,0 @@ -getUser(); - - $workspace = $this->getMercurialWorkspace($repository); - - try { - $this->commitRevisionToWorkspace($revision, $workspace, $viewer); - } catch (Exception $e) { - throw new PhutilProxyException(pht('Failed to commit patch.'), $e); - } - - try { - $this->pushWorkspaceRepository($repository, $workspace, $viewer); - } catch (Exception $e) { - throw new PhutilProxyException( - pht('Failed to push changes upstream.'), - $e); - } - } - - public function commitRevisionToWorkspace( - DifferentialRevision $revision, - ArcanistRepositoryAPI $workspace, - PhabricatorUser $user) { - - $diff_id = $revision->loadActiveDiff()->getID(); - - $call = new ConduitCall( - 'differential.getrawdiff', - array( - 'diffID' => $diff_id, - )); - - $call->setUser($user); - $raw_diff = $call->execute(); - - $future = $workspace->execFutureLocal('patch --no-commit -'); - $future->write($raw_diff); - $future->resolvex(); - - $workspace->reloadWorkingCopy(); - - $call = new ConduitCall( - 'differential.getcommitmessage', - array( - 'revision_id' => $revision->getID(), - )); - - $call->setUser($user); - $message = $call->execute(); - - $author = id(new PhabricatorUser())->loadOneWhere( - 'phid = %s', - $revision->getAuthorPHID()); - - $author_string = sprintf( - '%s <%s>', - $author->getRealName(), - $author->loadPrimaryEmailAddress()); - $author_date = $revision->getDateCreated(); - - $workspace->execxLocal( - 'commit --date=%s --user=%s '. - '--message=%s', - $author_date.' 0', - $author_string, - $message); - } - - - public function pushWorkspaceRepository( - PhabricatorRepository $repository, - ArcanistRepositoryAPI $workspace, - PhabricatorUser $user) { - - $workspace->execxLocal('push -b default'); - } - - public function createMenuItem( - PhabricatorUser $viewer, - DifferentialRevision $revision, - PhabricatorRepository $repository) { - - $vcs = $repository->getVersionControlSystem(); - if ($vcs !== PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL) { - return; - } - - if (!$repository->isHosted()) { - return; - } - - return $this->createActionView( - $revision, - pht('Land to Hosted Repository')); - } -} diff --git a/src/applications/differential/landing/DifferentialLandingActionMenuEventListener.php b/src/applications/differential/landing/DifferentialLandingActionMenuEventListener.php deleted file mode 100644 index 2cf3eaa4e5..0000000000 --- a/src/applications/differential/landing/DifferentialLandingActionMenuEventListener.php +++ /dev/null @@ -1,81 +0,0 @@ -listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS); - } - - public function handleEvent(PhutilEvent $event) { - switch ($event->getType()) { - case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS: - $this->handleActionsEvent($event); - break; - } - } - - private function handleActionsEvent(PhutilEvent $event) { - $object = $event->getValue('object'); - if ($object instanceof DifferentialRevision) { - $this->renderRevisionAction($event); - } - } - - private function renderRevisionAction(PhutilEvent $event) { - $viewer = $event->getUser(); - - if (!$this->canUseApplication($viewer)) { - return null; - } - - $revision = $event->getValue('object'); - - $repository = $revision->getRepository(); - if ($repository === null) { - return null; - } - - if ($repository->canPerformAutomation()) { - $revision_id = $revision->getID(); - - $op = new DrydockLandRepositoryOperation(); - $barrier = $op->getBarrierToLanding($viewer, $revision); - - if ($barrier) { - $can_land = false; - } else { - $can_land = true; - } - - $action = id(new PhabricatorActionView()) - ->setName(pht('Land Revision')) - ->setIcon('fa-fighter-jet') - ->setHref("/differential/revision/operation/{$revision_id}/") - ->setWorkflow(true) - ->setDisabled(!$can_land); - - - $this->addActionMenuItems($event, $action); - } - - $strategies = id(new PhutilClassMapQuery()) - ->setAncestorClass('DifferentialLandingStrategy') - ->execute(); - - foreach ($strategies as $strategy) { - $action = $strategy->createMenuItem($viewer, $revision, $repository); - if ($action == null) { - continue; - } - if ($strategy->isActionDisabled($viewer, $revision, $repository)) { - $action->setDisabled(true); - } - $this->addActionMenuItems($event, $action); - } - } - -} diff --git a/src/applications/differential/landing/DifferentialLandingStrategy.php b/src/applications/differential/landing/DifferentialLandingStrategy.php deleted file mode 100644 index 2cec11fefe..0000000000 --- a/src/applications/differential/landing/DifferentialLandingStrategy.php +++ /dev/null @@ -1,87 +0,0 @@ -getId(); - return id(new PhabricatorActionView()) - ->setRenderAsForm(true) - ->setWorkflow(true) - ->setName($name) - ->setHref("/differential/revision/land/{$revision_id}/{$strategy}/"); - } - - /** - * Check if this action should be disabled, and explain why. - * - * By default, this method checks for push permissions, and for the - * revision being Accepted. - * - * @return False for "not disabled"; human-readable text explaining why, if - * it is disabled. - */ - public function isActionDisabled( - PhabricatorUser $viewer, - DifferentialRevision $revision, - PhabricatorRepository $repository) { - - $status = $revision->getStatus(); - if ($status != ArcanistDifferentialRevisionStatus::ACCEPTED) { - return pht('Only Accepted revisions can be landed.'); - } - - if (!PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - DiffusionPushCapability::CAPABILITY)) { - return pht('You do not have permissions to push to this repository.'); - } - - return false; - } - - /** - * Might break if repository is not Git. - */ - protected function getGitWorkspace(PhabricatorRepository $repository) { - try { - return DifferentialGetWorkingCopy::getCleanGitWorkspace($repository); - } catch (Exception $e) { - throw new PhutilProxyException( - pht('Failed to allocate a workspace.'), - $e); - } - } - - /** - * Might break if repository is not Mercurial. - */ - protected function getMercurialWorkspace(PhabricatorRepository $repository) { - try { - return DifferentialGetWorkingCopy::getCleanMercurialWorkspace( - $repository); - } catch (Exception $e) { - throw new PhutilProxyException( - pht('Failed to allocate a workspace.'), - $e); - } - } - -} diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index c745d8e42a..88303cf420 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -1045,7 +1045,8 @@ final class DifferentialChangesetParser extends Phobject { } } - $this->comments = $this->reorderAndThreadComments($this->comments); + $this->comments = id(new PHUIDiffInlineThreader()) + ->reorderAndThreadCommments($this->comments); foreach ($this->comments as $comment) { $final = $comment->getLineNumber() + @@ -1617,68 +1618,6 @@ final class DifferentialChangesetParser extends Phobject { return array($old_back, $new_back); } - private function reorderAndThreadComments(array $comments) { - $comments = msort($comments, 'getID'); - - // Build an empty map of all the comments we actually have. If a comment - // is a reply but the parent has gone missing, we don't want it to vanish - // completely. - $comment_phids = mpull($comments, 'getPHID'); - $replies = array_fill_keys($comment_phids, array()); - - // Now, remove all comments which are replies, leaving only the top-level - // comments. - foreach ($comments as $key => $comment) { - $reply_phid = $comment->getReplyToCommentPHID(); - if (isset($replies[$reply_phid])) { - $replies[$reply_phid][] = $comment; - unset($comments[$key]); - } - } - - // For each top level comment, add the comment, then add any replies - // to it. Do this recursively so threads are shown in threaded order. - $results = array(); - foreach ($comments as $comment) { - $results[] = $comment; - $phid = $comment->getPHID(); - $descendants = $this->getInlineReplies($replies, $phid, 1); - foreach ($descendants as $descendant) { - $results[] = $descendant; - } - } - - // If we have anything left, they were cyclic references. Just dump - // them in a the end. This should be impossible, but users are very - // creative. - foreach ($replies as $phid => $comments) { - foreach ($comments as $comment) { - $results[] = $comment; - } - } - - return $results; - } - - private function getInlineReplies(array &$replies, $phid, $depth) { - $comments = idx($replies, $phid, array()); - unset($replies[$phid]); - - $results = array(); - foreach ($comments as $comment) { - $results[] = $comment; - $descendants = $this->getInlineReplies( - $replies, - $comment->getPHID(), - $depth + 1); - foreach ($descendants as $descendant) { - $results[] = $descendant; - } - } - - return $results; - } - private function getOffset(array $map, $line) { if (!$map) { return null; diff --git a/src/applications/differential/phid/DifferentialRevisionPHIDType.php b/src/applications/differential/phid/DifferentialRevisionPHIDType.php index 5a6cf701ae..a117690d66 100644 --- a/src/applications/differential/phid/DifferentialRevisionPHIDType.php +++ b/src/applications/differential/phid/DifferentialRevisionPHIDType.php @@ -33,7 +33,6 @@ final class DifferentialRevisionPHIDType extends PhabricatorPHIDType { $revision = $objects[$phid]; $title = $revision->getTitle(); - $status = $revision->getStatus(); $monogram = $revision->getMonogram(); $uri = $revision->getURI(); @@ -46,12 +45,9 @@ final class DifferentialRevisionPHIDType extends PhabricatorPHIDType { $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); } - $status = $revision->getStatus(); - - $icon = DifferentialRevisionStatus::getRevisionStatusIcon($status); - $color = DifferentialRevisionStatus::getRevisionStatusColor($status); - $name = ArcanistDifferentialRevisionStatus::getNameForRevisionStatus( - $status); + $icon = $revision->getStatusIcon(); + $color = $revision->getStatusIconColor(); + $name = $revision->getStatusDisplayName(); $handle ->setStateIcon($icon) diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index 701463e6a7..8fd91cbd7e 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -1,13 +1,6 @@ withStatus(DifferentialRevisionQuery::STATUS_OPEN) - * ->execute(); - * * @task config Query Configuration * @task exec Query Execution * @task internal Internals @@ -17,15 +10,6 @@ final class DifferentialRevisionQuery private $pathIDs = array(); - private $status = 'status-any'; - const STATUS_ANY = 'status-any'; - const STATUS_OPEN = 'status-open'; - const STATUS_ACCEPTED = 'status-accepted'; - const STATUS_NEEDS_REVIEW = 'status-needs-review'; - const STATUS_NEEDS_REVISION = 'status-needs-revision'; - const STATUS_CLOSED = 'status-closed'; - const STATUS_ABANDONED = 'status-abandoned'; - private $authors = array(); private $draftAuthors = array(); private $ccs = array(); @@ -39,6 +23,8 @@ final class DifferentialRevisionQuery private $repositoryPHIDs; private $updatedEpochMin; private $updatedEpochMax; + private $statuses; + private $isOpen; const ORDER_MODIFIED = 'order-modified'; const ORDER_CREATED = 'order-created'; @@ -147,16 +133,13 @@ final class DifferentialRevisionQuery return $this; } - /** - * Filter results to revisions with a given status. Provide a class constant, - * such as `DifferentialRevisionQuery::STATUS_OPEN`. - * - * @param const Class STATUS constant, like STATUS_OPEN. - * @return this - * @task config - */ - public function withStatus($status_constant) { - $this->status = $status_constant; + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + + public function withIsOpen($is_open) { + $this->isOpen = $is_open; return $this; } @@ -337,7 +320,7 @@ final class DifferentialRevisionQuery */ protected function loadPage() { $data = $this->loadData(); - + $data = $this->didLoadRawRows($data); $table = $this->newResultObject(); return $table->loadAllFromArray($data); } @@ -708,60 +691,25 @@ final class DifferentialRevisionQuery $this->updatedEpochMax); } - // NOTE: Although the status constants are integers in PHP, the column is a - // string column in MySQL, and MySQL won't use keys on string columns if - // you put integers in the query. + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn_r, + 'r.status in (%Ls)', + $this->statuses); + } - switch ($this->status) { - case self::STATUS_ANY: - break; - case self::STATUS_OPEN: - $where[] = qsprintf( - $conn_r, - 'r.status IN (%Ls)', - DifferentialRevisionStatus::getOpenStatuses()); - break; - case self::STATUS_NEEDS_REVIEW: - $where[] = qsprintf( - $conn_r, - 'r.status IN (%Ls)', - array( - ArcanistDifferentialRevisionStatus::NEEDS_REVIEW, - )); - break; - case self::STATUS_NEEDS_REVISION: - $where[] = qsprintf( - $conn_r, - 'r.status IN (%Ls)', - array( - ArcanistDifferentialRevisionStatus::NEEDS_REVISION, - )); - break; - case self::STATUS_ACCEPTED: - $where[] = qsprintf( - $conn_r, - 'r.status IN (%Ls)', - array( - ArcanistDifferentialRevisionStatus::ACCEPTED, - )); - break; - case self::STATUS_CLOSED: - $where[] = qsprintf( - $conn_r, - 'r.status IN (%Ls)', - DifferentialRevisionStatus::getClosedStatuses()); - break; - case self::STATUS_ABANDONED: - $where[] = qsprintf( - $conn_r, - 'r.status IN (%Ls)', - array( - ArcanistDifferentialRevisionStatus::ABANDONED, - )); - break; - default: - throw new Exception( - pht("Unknown revision status filter constant '%s'!", $this->status)); + if ($this->isOpen !== null) { + if ($this->isOpen) { + $statuses = DifferentialLegacyQuery::getModernValues( + DifferentialLegacyQuery::STATUS_OPEN); + } else { + $statuses = DifferentialLegacyQuery::getModernValues( + DifferentialLegacyQuery::STATUS_CLOSED); + } + $where[] = qsprintf( + $conn_r, + 'r.status in (%Ls)', + $statuses); } $where[] = $this->buildWhereClauseParts($conn_r); @@ -824,7 +772,7 @@ final class DifferentialRevisionQuery 'column' => 'dateModified', 'type' => 'int', ), - ); + ) + parent::getOrderableColumns(); } protected function getPagingValueMap($cursor, array $keys) { diff --git a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php index 6d83d97a47..195a430b1c 100644 --- a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php @@ -134,13 +134,11 @@ final class DifferentialRevisionRequiredActionResultBucket } private function filterShouldLand(array $phids) { - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; - $objects = $this->getRevisionsAuthored($this->objects, $phids); $results = array(); foreach ($objects as $key => $object) { - if ($object->getStatus() != $status_accepted) { + if (!$object->isAccepted()) { continue; } @@ -153,9 +151,8 @@ final class DifferentialRevisionRequiredActionResultBucket private function filterShouldUpdate(array $phids) { $statuses = array( - ArcanistDifferentialRevisionStatus::NEEDS_REVISION, - ArcanistDifferentialRevisionStatus::CHANGES_PLANNED, - ArcanistDifferentialRevisionStatus::IN_PREPARATION, + DifferentialRevisionStatus::NEEDS_REVISION, + DifferentialRevisionStatus::CHANGES_PLANNED, ); $statuses = array_fuse($statuses); @@ -163,7 +160,7 @@ final class DifferentialRevisionRequiredActionResultBucket $results = array(); foreach ($objects as $key => $object) { - if (empty($statuses[$object->getStatus()])) { + if (empty($statuses[$object->getModernRevisionStatus()])) { continue; } @@ -175,13 +172,11 @@ final class DifferentialRevisionRequiredActionResultBucket } private function filterWaitingForReview(array $phids) { - $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; - $objects = $this->getRevisionsAuthored($this->objects, $phids); $results = array(); foreach ($objects as $key => $object) { - if ($object->getStatus() != $status_review) { + if (!$object->isNeedsReview()) { continue; } @@ -194,10 +189,9 @@ final class DifferentialRevisionRequiredActionResultBucket private function filterWaitingOnAuthors(array $phids) { $statuses = array( - ArcanistDifferentialRevisionStatus::ACCEPTED, - ArcanistDifferentialRevisionStatus::NEEDS_REVISION, - ArcanistDifferentialRevisionStatus::CHANGES_PLANNED, - ArcanistDifferentialRevisionStatus::IN_PREPARATION, + DifferentialRevisionStatus::ACCEPTED, + DifferentialRevisionStatus::NEEDS_REVISION, + DifferentialRevisionStatus::CHANGES_PLANNED, ); $statuses = array_fuse($statuses); @@ -205,7 +199,7 @@ final class DifferentialRevisionRequiredActionResultBucket $results = array(); foreach ($objects as $key => $object) { - if (empty($statuses[$object->getStatus()])) { + if (empty($statuses[$object->getModernRevisionStatus()])) { continue; } @@ -217,16 +211,11 @@ final class DifferentialRevisionRequiredActionResultBucket } private function filterWaitingOnOtherReviewers(array $phids) { - $statuses = array( - ArcanistDifferentialRevisionStatus::NEEDS_REVIEW, - ); - $statuses = array_fuse($statuses); - $objects = $this->getRevisionsNotAuthored($this->objects, $phids); $results = array(); foreach ($objects as $key => $object) { - if (!isset($statuses[$object->getStatus()])) { + if (!$object->isNeedsReview()) { continue; } diff --git a/src/applications/differential/query/DifferentialRevisionResultBucket.php b/src/applications/differential/query/DifferentialRevisionResultBucket.php index 762e2d97f4..54705649eb 100644 --- a/src/applications/differential/query/DifferentialRevisionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionResultBucket.php @@ -15,9 +15,8 @@ abstract class DifferentialRevisionResultBucket $objects = $this->getRevisionsNotAuthored($objects, $phids); - $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; foreach ($objects as $key => $object) { - if ($object->getStatus() != $status_review) { + if (!$object->isNeedsReview()) { continue; } diff --git a/src/applications/differential/query/DifferentialRevisionSearchEngine.php b/src/applications/differential/query/DifferentialRevisionSearchEngine.php index aa525113aa..aa1d1ee71b 100644 --- a/src/applications/differential/query/DifferentialRevisionSearchEngine.php +++ b/src/applications/differential/query/DifferentialRevisionSearchEngine.php @@ -41,8 +41,8 @@ final class DifferentialRevisionSearchEngine $query->withRepositoryPHIDs($map['repositoryPHIDs']); } - if ($map['status']) { - $query->withStatus($map['status']); + if ($map['statuses']) { + $query->withStatuses($map['statuses']); } return $query; @@ -77,10 +77,11 @@ final class DifferentialRevisionSearchEngine ->setDatasource(new DiffusionRepositoryFunctionDatasource()) ->setDescription( pht('Find revisions from specific repositories.')), - id(new PhabricatorSearchSelectField()) - ->setLabel(pht('Status')) - ->setKey('status') - ->setOptions($this->getStatusOptions()) + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Statuses')) + ->setKey('statuses') + ->setAliases(array('status')) + ->setDatasource(new DifferentialRevisionStatusFunctionDatasource()) ->setDescription( pht('Find revisions with particular statuses.')), ); @@ -116,7 +117,7 @@ final class DifferentialRevisionSearchEngine return $query ->setParameter('responsiblePHIDs', array($viewer->getPHID())) - ->setParameter('status', DifferentialRevisionQuery::STATUS_OPEN) + ->setParameter('statuses', array('open()')) ->setParameter('bucket', $bucket_key); case 'authored': return $query @@ -133,13 +134,13 @@ final class DifferentialRevisionSearchEngine private function getStatusOptions() { return array( - DifferentialRevisionQuery::STATUS_ANY => pht('All'), - DifferentialRevisionQuery::STATUS_OPEN => pht('Open'), - DifferentialRevisionQuery::STATUS_ACCEPTED => pht('Accepted'), - DifferentialRevisionQuery::STATUS_NEEDS_REVIEW => pht('Needs Review'), - DifferentialRevisionQuery::STATUS_NEEDS_REVISION => pht('Needs Revision'), - DifferentialRevisionQuery::STATUS_CLOSED => pht('Closed'), - DifferentialRevisionQuery::STATUS_ABANDONED => pht('Abandoned'), + DifferentialLegacyQuery::STATUS_ANY => pht('All'), + DifferentialLegacyQuery::STATUS_OPEN => pht('Open'), + DifferentialLegacyQuery::STATUS_ACCEPTED => pht('Accepted'), + DifferentialLegacyQuery::STATUS_NEEDS_REVIEW => pht('Needs Review'), + DifferentialLegacyQuery::STATUS_NEEDS_REVISION => pht('Needs Revision'), + DifferentialLegacyQuery::STATUS_CLOSED => pht('Closed'), + DifferentialLegacyQuery::STATUS_ABANDONED => pht('Abandoned'), ); } @@ -239,11 +240,9 @@ final class DifferentialRevisionSearchEngine } private function loadUnlandedDependencies(array $revisions) { - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; - $phids = array(); foreach ($revisions as $revision) { - if ($revision->getStatus() != $status_accepted) { + if (!$revision->isAccepted()) { continue; } @@ -273,7 +272,7 @@ final class DifferentialRevisionSearchEngine $blocking_revisions = id(new DifferentialRevisionQuery()) ->setViewer($viewer) ->withPHIDs($revision_phids) - ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) + ->withIsOpen(true) ->execute(); $blocking_revisions = mpull($blocking_revisions, null, 'getPHID'); diff --git a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php index cbb5cbc6f4..7a694cfd98 100644 --- a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php @@ -41,8 +41,6 @@ final class DifferentialChangesetOneUpRenderer $column_width = 4; - $hidden = new PHUIDiffRevealIconView(); - $out = array(); foreach ($primitives as $k => $p) { $type = $p['type']; @@ -53,27 +51,6 @@ final class DifferentialChangesetOneUpRenderer case 'new-file': $is_old = ($type == 'old' || $type == 'old-file'); - $o_hidden = array(); - $n_hidden = array(); - - for ($look = $k + 1; isset($primitives[$look]); $look++) { - $next = $primitives[$look]; - switch ($next['type']) { - case 'inline': - $comment = $next['comment']; - if ($comment->isHidden()) { - if ($next['right']) { - $n_hidden[] = $comment; - } else { - $o_hidden[] = $comment; - } - } - break; - default: - break 2; - } - } - $cells = array(); if ($is_old) { if ($p['htype']) { @@ -93,9 +70,6 @@ final class DifferentialChangesetOneUpRenderer } $line = $p['line']; - if ($o_hidden) { - $line = array($hidden, $line); - } $cells[] = phutil_tag( 'th', @@ -122,9 +96,6 @@ final class DifferentialChangesetOneUpRenderer } $oline = $p['oline']; - if ($o_hidden) { - $oline = array($hidden, $oline); - } $cells[] = phutil_tag('th', array('id' => $left_id), $oline); } @@ -140,9 +111,6 @@ final class DifferentialChangesetOneUpRenderer } $line = $p['line']; - if ($n_hidden) { - $line = array($hidden, $line); - } $cells[] = phutil_tag( 'th', diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php index 9e4f9c049d..5d476f5136 100644 --- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php @@ -69,8 +69,6 @@ final class DifferentialChangesetTwoUpRenderer $depths = $this->getDepths(); $mask = $this->getMask(); - $hidden = new PHUIDiffRevealIconView(); - for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) { if (empty($mask[$ii])) { // If we aren't going to show this line, we've just entered a gap. @@ -241,9 +239,6 @@ final class DifferentialChangesetTwoUpRenderer $new_comments = $this->getNewComments(); $scaffolds = array(); - $o_hidden = array(); - $n_hidden = array(); - if ($o_num && isset($old_comments[$o_num])) { foreach ($old_comments[$o_num] as $comment) { $inline = $this->buildInlineComment( @@ -251,10 +246,6 @@ final class DifferentialChangesetTwoUpRenderer $on_right = false); $scaffold = $this->getRowScaffoldForInline($inline); - if ($comment->isHidden()) { - $o_hidden[] = $comment; - } - if ($n_num && isset($new_comments[$n_num])) { foreach ($new_comments[$n_num] as $key => $new_comment) { if ($comment->isCompatible($new_comment)) { @@ -262,10 +253,6 @@ final class DifferentialChangesetTwoUpRenderer $new_comment, $on_right = true); - if ($new_comment->isHidden()) { - $n_hidden = $new_comment; - } - $scaffold->addInlineView($companion); unset($new_comments[$n_num][$key]); break; @@ -284,22 +271,10 @@ final class DifferentialChangesetTwoUpRenderer $comment, $on_right = true); - if ($comment->isHidden()) { - $n_hidden[] = $comment; - } - $scaffolds[] = $this->getRowScaffoldForInline($inline); } } - if ($o_hidden) { - $o_num = array($hidden, $o_num); - } - - if ($n_hidden) { - $n_num = array($hidden, $n_num); - } - // NOTE: This is a unicode zero-width space, which we use as a hint when // intercepting 'copy' events to make sure sensible text ends up on the // clipboard. See the 'phabricator-oncopy' behavior. @@ -348,6 +323,12 @@ final class DifferentialChangesetTwoUpRenderer $new = $this->renderImageStage($new_file); } + // If we don't have an explicit "vs" changeset, it's the left side of the + // "id" changeset. + if (!$vs) { + $vs = $id; + } + $html_old = array(); $html_new = array(); foreach ($this->getOldComments() as $on_line => $comment_group) { diff --git a/src/applications/differential/search/DifferentialRevisionFerretEngine.php b/src/applications/differential/search/DifferentialRevisionFerretEngine.php new file mode 100644 index 0000000000..2fc7d5905e --- /dev/null +++ b/src/applications/differential/search/DifferentialRevisionFerretEngine.php @@ -0,0 +1,26 @@ +getStatus() == $status_review) { + if ($revision->isNeedsReview()) { $reviewers = $revision->getReviewerPHIDs(); $reviewers = array_fuse($reviewers); diff --git a/src/applications/differential/storage/DifferentialChangeset.php b/src/applications/differential/storage/DifferentialChangeset.php index be83c5e73b..9c718a8765 100644 --- a/src/applications/differential/storage/DifferentialChangeset.php +++ b/src/applications/differential/storage/DifferentialChangeset.php @@ -170,7 +170,7 @@ final class DifferentialChangeset extends DifferentialDAO } public function getAnchorName() { - return substr(md5($this->getFilename()), 0, 8); + return 'change-'.PhabricatorHash::digestForIndex($this->getFilename()); } public function getAbsoluteRepositoryPath( diff --git a/src/applications/differential/storage/DifferentialDraft.php b/src/applications/differential/storage/DifferentialDraft.php deleted file mode 100644 index 7dbe2f68bd..0000000000 --- a/src/applications/differential/storage/DifferentialDraft.php +++ /dev/null @@ -1,23 +0,0 @@ - array( - 'draftKey' => 'text64', - ), - self::CONFIG_KEY_SCHEMA => array( - 'key_unique' => array( - 'columns' => array('objectPHID', 'authorPHID', 'draftKey'), - 'unique' => true, - ), - ), - ) + parent::getConfiguration(); - } - -} diff --git a/src/applications/differential/storage/DifferentialInlineComment.php b/src/applications/differential/storage/DifferentialInlineComment.php index bdc231671f..cbe05663db 100644 --- a/src/applications/differential/storage/DifferentialInlineComment.php +++ b/src/applications/differential/storage/DifferentialInlineComment.php @@ -255,6 +255,13 @@ final class DifferentialInlineComment return $this; } + public function getDateModified() { + return $this->proxy->getDateModified(); + } + + public function getDateCreated() { + return $this->proxy->getDateCreated(); + } /* -( PhabricatorMarkupInterface Implementation )-------------------------- */ diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 0e968dccf4..3615c6e78b 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -15,6 +15,7 @@ final class DifferentialRevision extends DifferentialDAO PhabricatorDestructibleInterface, PhabricatorProjectInterface, PhabricatorFulltextInterface, + PhabricatorFerretInterface, PhabricatorConduitResultInterface, PhabricatorDraftInterface { @@ -72,7 +73,7 @@ final class DifferentialRevision extends DifferentialDAO ->attachRepository(null) ->attachActiveDiff(null) ->attachReviewers(array()) - ->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW); + ->setModernRevisionStatus(DifferentialRevisionStatus::NEEDS_REVIEW); } protected function getConfiguration() { @@ -523,7 +524,6 @@ final class DifferentialRevision extends DifferentialDAO switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: - $description[] = pht("A revision's reviewers can always view it."); $description[] = pht( 'If a revision belongs to a repository, other users must be able '. 'to view the repository in order to view the revision.'); @@ -613,43 +613,61 @@ final class DifferentialRevision extends DifferentialDAO return $this; } + public function setModernRevisionStatus($status) { + return $this->setStatus($status); + } + + public function getModernRevisionStatus() { + return $this->getStatus(); + } + + public function getLegacyRevisionStatus() { + return $this->getStatusObject()->getLegacyKey(); + } + public function isClosed() { - return DifferentialRevisionStatus::isClosedStatus($this->getStatus()); + return $this->getStatusObject()->isClosedStatus(); } public function isAbandoned() { - $status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED; - return ($this->getStatus() == $status_abandoned); + return $this->getStatusObject()->isAbandoned(); } public function isAccepted() { - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; - return ($this->getStatus() == $status_accepted); + return $this->getStatusObject()->isAccepted(); + } + + public function isNeedsReview() { + return $this->getStatusObject()->isNeedsReview(); + } + + public function isNeedsRevision() { + return $this->getStatusObject()->isNeedsRevision(); + } + + public function isChangePlanned() { + return $this->getStatusObject()->isChangePlanned(); + } + + public function isPublished() { + return $this->getStatusObject()->isPublished(); } public function getStatusIcon() { - $map = array( - ArcanistDifferentialRevisionStatus::NEEDS_REVIEW - => 'fa-code grey', - ArcanistDifferentialRevisionStatus::NEEDS_REVISION - => 'fa-refresh red', - ArcanistDifferentialRevisionStatus::CHANGES_PLANNED - => 'fa-headphones red', - ArcanistDifferentialRevisionStatus::ACCEPTED - => 'fa-check green', - ArcanistDifferentialRevisionStatus::CLOSED - => 'fa-check-square-o black', - ArcanistDifferentialRevisionStatus::ABANDONED - => 'fa-plane black', - ); - - return idx($map, $this->getStatus()); + return $this->getStatusObject()->getIcon(); } public function getStatusDisplayName() { + return $this->getStatusObject()->getDisplayName(); + } + + public function getStatusIconColor() { + return $this->getStatusObject()->getIconColor(); + } + + public function getStatusObject() { $status = $this->getStatus(); - return ArcanistDifferentialRevisionStatus::getNameForRevisionStatus( - $status); + return DifferentialRevisionStatus::newForStatus($status); } public function getFlag(PhabricatorUser $viewer) { @@ -883,6 +901,14 @@ final class DifferentialRevision extends DifferentialDAO } +/* -( PhabricatorFerretInterface )----------------------------------------- */ + + + public function newFerretEngine() { + return new DifferentialRevisionFerretEngine(); + } + + /* -( PhabricatorConduitResultInterface )---------------------------------- */ @@ -896,13 +922,26 @@ final class DifferentialRevision extends DifferentialDAO ->setKey('authorPHID') ->setType('phid') ->setDescription(pht('Revision author PHID.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('status') + ->setType('map') + ->setDescription(pht('Information about revision status.')), ); } public function getFieldValuesForConduit() { + $status = $this->getStatusObject(); + $status_info = array( + 'value' => $status->getKey(), + 'name' => $status->getDisplayName(), + 'closed' => $status->isClosedStatus(), + 'color.ansi' => $status->getANSIColor(), + ); + return array( 'title' => $this->getTitle(), 'authorPHID' => $this->getAuthorPHID(), + 'status' => $status_info, ); } diff --git a/src/applications/differential/storage/DifferentialTransaction.php b/src/applications/differential/storage/DifferentialTransaction.php index c6c55e0cdd..ea0d7789cb 100644 --- a/src/applications/differential/storage/DifferentialTransaction.php +++ b/src/applications/differential/storage/DifferentialTransaction.php @@ -8,7 +8,6 @@ final class DifferentialTransaction const TYPE_INLINE = 'differential:inline'; const TYPE_UPDATE = 'differential:update'; const TYPE_ACTION = 'differential:action'; - const TYPE_STATUS = 'differential:status'; const MAILTAG_REVIEWERS = 'differential-reviewers'; const MAILTAG_CLOSED = 'differential-committed'; @@ -106,6 +105,18 @@ final class DifferentialTransaction return parent::shouldHide(); } + public function shouldHideForMail(array $xactions) { + switch ($this->getTransactionType()) { + case DifferentialRevisionReviewersTransaction::TRANSACTIONTYPE: + // Don't hide the initial "X added reviewers: ..." transaction during + // object creation from mail. See T12118 and PHI54. + return false; + } + + return parent::shouldHideForMail($xactions); + } + + public function isInlineCommentTransaction() { switch ($this->getTransactionType()) { case self::TYPE_INLINE: @@ -305,15 +316,6 @@ final class DifferentialTransaction return DifferentialAction::getBasicStoryText($new, $author_handle); } break; - case self::TYPE_STATUS: - switch ($this->getNewValue()) { - case ArcanistDifferentialRevisionStatus::ACCEPTED: - return pht('This revision is now accepted and ready to land.'); - case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: - return pht('This revision now requires changes to proceed.'); - case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: - return pht('This revision now requires review to proceed.'); - } } return parent::getTitle(); @@ -457,21 +459,6 @@ final class DifferentialTransaction $object_link); } break; - case self::TYPE_STATUS: - switch ($this->getNewValue()) { - case ArcanistDifferentialRevisionStatus::ACCEPTED: - return pht( - '%s is now accepted and ready to land.', - $object_link); - case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: - return pht( - '%s now requires changes to proceed.', - $object_link); - case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: - return pht( - '%s now requires review to proceed.', - $object_link); - } } return parent::getTitleForFeed(); @@ -483,16 +470,6 @@ final class DifferentialTransaction return 'fa-comment'; case self::TYPE_UPDATE: return 'fa-refresh'; - case self::TYPE_STATUS: - switch ($this->getNewValue()) { - case ArcanistDifferentialRevisionStatus::ACCEPTED: - return 'fa-check'; - case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: - return 'fa-times'; - case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: - return 'fa-undo'; - } - break; case self::TYPE_ACTION: switch ($this->getNewValue()) { case DifferentialAction::ACTION_CLOSE: @@ -530,14 +507,12 @@ final class DifferentialTransaction // Never group status changes with other types of actions, they're indirect // and don't make sense when combined with direct actions. - $type_status = self::TYPE_STATUS; - - if ($this->getTransactionType() == $type_status) { + if ($this->isStatusTransaction($this)) { return false; } foreach ($group as $xaction) { - if ($xaction->getTransactionType() == $type_status) { + if ($this->isStatusTransaction($xaction)) { return false; } } @@ -545,21 +520,20 @@ final class DifferentialTransaction return parent::shouldDisplayGroupWith($group); } + private function isStatusTransaction($xaction) { + $status_type = DifferentialRevisionStatusTransaction::TRANSACTIONTYPE; + if ($xaction->getTransactionType() == $status_type) { + return true; + } + + return false; + } + public function getColor() { switch ($this->getTransactionType()) { case self::TYPE_UPDATE: return PhabricatorTransactions::COLOR_SKY; - case self::TYPE_STATUS: - switch ($this->getNewValue()) { - case ArcanistDifferentialRevisionStatus::ACCEPTED: - return PhabricatorTransactions::COLOR_GREEN; - case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: - return PhabricatorTransactions::COLOR_RED; - case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: - return PhabricatorTransactions::COLOR_ORANGE; - } - break; case self::TYPE_ACTION: switch ($this->getNewValue()) { case DifferentialAction::ACTION_CLOSE: diff --git a/src/applications/differential/typeahead/DifferentialRevisionClosedStatusDatasource.php b/src/applications/differential/typeahead/DifferentialRevisionClosedStatusDatasource.php new file mode 100644 index 0000000000..8487111452 --- /dev/null +++ b/src/applications/differential/typeahead/DifferentialRevisionClosedStatusDatasource.php @@ -0,0 +1,74 @@ + array( + 'name' => pht('Any Closed Status'), + 'summary' => pht('Find results with any closed status.'), + 'description' => pht( + 'This function includes results which have any closed status.'), + ), + ); + } + + public function loadResults() { + $results = array( + $this->buildClosedResult(), + ); + return $this->filterResultsAgainstTokens($results); + } + + protected function evaluateFunction($function, array $argv_list) { + $results = array(); + + $map = DifferentialRevisionStatus::getAll(); + foreach ($argv_list as $argv) { + foreach ($map as $status) { + if ($status->isClosedStatus()) { + $results[] = $status->getKey(); + } + } + } + + return $results; + } + + public function renderFunctionTokens($function, array $argv_list) { + $results = array(); + + foreach ($argv_list as $argv) { + $results[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult( + $this->buildClosedResult()); + } + + return $results; + } + + private function buildClosedResult() { + $name = pht('Any Closed Status'); + return $this->newFunctionResult() + ->setName($name.' closed') + ->setDisplayName($name) + ->setPHID(self::FUNCTION_TOKEN) + ->setUnique(true) + ->addAttribute(pht('Select any closed status.')); + } + +} diff --git a/src/applications/differential/typeahead/DifferentialRevisionOpenStatusDatasource.php b/src/applications/differential/typeahead/DifferentialRevisionOpenStatusDatasource.php new file mode 100644 index 0000000000..0f00e470c3 --- /dev/null +++ b/src/applications/differential/typeahead/DifferentialRevisionOpenStatusDatasource.php @@ -0,0 +1,74 @@ + array( + 'name' => pht('Any Open Status'), + 'summary' => pht('Find results with any open status.'), + 'description' => pht( + 'This function includes results which have any open status.'), + ), + ); + } + + public function loadResults() { + $results = array( + $this->buildOpenResult(), + ); + return $this->filterResultsAgainstTokens($results); + } + + protected function evaluateFunction($function, array $argv_list) { + $results = array(); + + $map = DifferentialRevisionStatus::getAll(); + foreach ($argv_list as $argv) { + foreach ($map as $status) { + if (!$status->isClosedStatus()) { + $results[] = $status->getKey(); + } + } + } + + return $results; + } + + public function renderFunctionTokens($function, array $argv_list) { + $results = array(); + + foreach ($argv_list as $argv) { + $results[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult( + $this->buildOpenResult()); + } + + return $results; + } + + private function buildOpenResult() { + $name = pht('Any Open Status'); + return $this->newFunctionResult() + ->setName($name.' open') + ->setDisplayName($name) + ->setPHID(self::FUNCTION_TOKEN) + ->setUnique(true) + ->addAttribute(pht('Select any open status.')); + } + +} diff --git a/src/applications/differential/typeahead/DifferentialRevisionStatusDatasource.php b/src/applications/differential/typeahead/DifferentialRevisionStatusDatasource.php new file mode 100644 index 0000000000..5e240d3c29 --- /dev/null +++ b/src/applications/differential/typeahead/DifferentialRevisionStatusDatasource.php @@ -0,0 +1,52 @@ +buildResults(); + return $this->filterResultsAgainstTokens($results); + } + + + protected function renderSpecialTokens(array $values) { + return $this->renderTokensFromResults($this->buildResults(), $values); + } + + private function buildResults() { + $results = array(); + + $statuses = DifferentialRevisionStatus::getAll(); + foreach ($statuses as $status) { + $key = $status->getKey(); + + $result = id(new PhabricatorTypeaheadResult()) + ->setIcon($status->getIcon()) + ->setPHID($key) + ->setName($status->getDisplayName()); + + if ($status->isClosedStatus()) { + $result->addAttribute(pht('Closed Status')); + } else { + $result->addAttribute(pht('Open Status')); + } + + $results[$key] = $result; + } + + return $results; + } + +} diff --git a/src/applications/differential/typeahead/DifferentialRevisionStatusFunctionDatasource.php b/src/applications/differential/typeahead/DifferentialRevisionStatusFunctionDatasource.php new file mode 100644 index 0000000000..2d4ec38363 --- /dev/null +++ b/src/applications/differential/typeahead/DifferentialRevisionStatusFunctionDatasource.php @@ -0,0 +1,22 @@ +getRenderer()); + $changeset_id = $this->changeset->getID(); + + $vs_id = $this->getVsChangesetID(); + if (!$vs_id) { + // Showing a changeset normally. + $left_id = $changeset_id; + $right_id = $changeset_id; + } else if ($vs_id == -1) { + // Showing a synthetic "deleted" changeset for a file which was + // removed between changes. + $left_id = $changeset_id; + $right_id = null; + } else { + // Showing a diff-of-diffs. + $left_id = $vs_id; + $right_id = $changeset_id; + } + + // In the persistent banner, emphasize the current filename. + $path_part = dirname($display_filename); + $file_part = basename($display_filename); + $display_parts = array(); + if (strlen($path_part)) { + $path_part = $path_part.'/'; + $display_parts[] = phutil_tag( + 'span', + array( + 'class' => 'diff-banner-path', + ), + $path_part); + } + $display_parts[] = phutil_tag( + 'span', + array( + 'class' => 'diff-banner-file', + ), + $file_part); + return javelin_tag( 'div', array( 'sigil' => 'differential-changeset', 'meta' => array( - 'left' => nonempty( - $this->getVsChangesetID(), - $this->changeset->getID()), - 'right' => $this->changeset->getID(), + 'left' => $left_id, + 'right' => $right_id, 'renderURI' => $this->getRenderURI(), 'whitespace' => $this->getWhitespace(), 'highlight' => null, @@ -166,7 +202,9 @@ final class DifferentialChangesetDetailView extends AphrontView { 'ref' => $this->getRenderingRef(), 'autoload' => $this->getAutoload(), 'loaded' => $this->getLoaded(), - 'undoTemplates' => $renderer->renderUndoTemplates(), + 'undoTemplates' => hsprintf('%s', $renderer->renderUndoTemplates()), + 'displayPath' => hsprintf('%s', $display_parts), + 'icon' => $display_icon, ), 'class' => $class, 'id' => $id, diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 533f579b0a..307d424b0e 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -14,6 +14,7 @@ final class DifferentialChangesetListView extends AphrontView { private $standaloneURI; private $leftRawFileURI; private $rightRawFileURI; + private $inlineListURI; private $symbolIndexes = array(); private $repository; @@ -64,6 +65,15 @@ final class DifferentialChangesetListView extends AphrontView { return $this; } + public function setInlineListURI($uri) { + $this->inlineListURI = $uri; + return $this; + } + + public function getInlineListURI() { + return $this->inlineListURI; + } + public function setRepository(PhabricatorRepository $repository) { $this->repository = $repository; return $this; @@ -131,36 +141,6 @@ final class DifferentialChangesetListView extends AphrontView { $changesets = $this->changesets; - Javelin::initBehavior('differential-toggle-files', array( - 'pht' => array( - 'undo' => pht('Undo'), - 'collapsed' => pht('This file content has been collapsed.'), - ), - )); - - Javelin::initBehavior( - 'differential-dropdown-menus', - array( - 'pht' => array( - 'Open in Editor' => pht('Open in Editor'), - 'Show All Context' => pht('Show All Context'), - 'All Context Shown' => pht('All Context Shown'), - "Can't Toggle Unloaded File" => pht("Can't Toggle Unloaded File"), - 'Expand File' => pht('Expand File'), - 'Collapse File' => pht('Collapse File'), - 'Browse in Diffusion' => pht('Browse in Diffusion'), - 'View Standalone' => pht('View Standalone'), - 'Show Raw File (Left)' => pht('Show Raw File (Left)'), - 'Show Raw File (Right)' => pht('Show Raw File (Right)'), - 'Configure Editor' => pht('Configure Editor'), - 'Load Changes' => pht('Load Changes'), - 'View Side-by-Side' => pht('View Side-by-Side'), - 'View Unified' => pht('View Unified'), - 'Change Text Encoding...' => pht('Change Text Encoding...'), - 'Highlight As...' => pht('Highlight As...'), - ), - )); - $renderer = DifferentialChangesetParser::getDefaultRendererForViewer( $viewer); @@ -169,11 +149,6 @@ final class DifferentialChangesetListView extends AphrontView { foreach ($changesets as $key => $changeset) { $file = $changeset->getFilename(); - $class = 'differential-changeset'; - if (!$this->inlineURI) { - $class .= ' differential-changeset-noneditable'; - } - $ref = $this->references[$key]; $detail = id(new DifferentialChangesetDetailView()) @@ -209,7 +184,7 @@ final class DifferentialChangesetListView extends AphrontView { $load = javelin_tag( 'a', array( - 'class' => 'button grey', + 'class' => 'button button-grey', 'href' => '#'.$uniq_id, 'sigil' => 'differential-load', 'meta' => array( @@ -238,20 +213,97 @@ final class DifferentialChangesetListView extends AphrontView { $this->requireResource('aphront-tooltip-css'); - $this->initBehavior('differential-populate', array( + $this->initBehavior( + 'differential-populate', + array( 'changesetViewIDs' => $ids, + 'inlineURI' => $this->inlineURI, + 'inlineListURI' => $this->inlineListURI, + 'pht' => array( + 'Open in Editor' => pht('Open in Editor'), + 'Show All Context' => pht('Show All Context'), + 'All Context Shown' => pht('All Context Shown'), + "Can't Toggle Unloaded File" => pht("Can't Toggle Unloaded File"), + 'Expand File' => pht('Expand File'), + 'Collapse File' => pht('Collapse File'), + 'Browse in Diffusion' => pht('Browse in Diffusion'), + 'View Standalone' => pht('View Standalone'), + 'Show Raw File (Left)' => pht('Show Raw File (Left)'), + 'Show Raw File (Right)' => pht('Show Raw File (Right)'), + 'Configure Editor' => pht('Configure Editor'), + 'Load Changes' => pht('Load Changes'), + 'View Side-by-Side' => pht('View Side-by-Side'), + 'View Unified' => pht('View Unified'), + 'Change Text Encoding...' => pht('Change Text Encoding...'), + 'Highlight As...' => pht('Highlight As...'), + + 'Loading...' => pht('Loading...'), + + 'Editing Comment' => pht('Editing Comment'), + + 'Jump to next change.' => pht('Jump to next change.'), + 'Jump to previous change.' => pht('Jump to previous change.'), + 'Jump to next file.' => pht('Jump to next file.'), + 'Jump to previous file.' => pht('Jump to previous file.'), + 'Jump to next inline comment.' => pht('Jump to next inline comment.'), + 'Jump to previous inline comment.' => + pht('Jump to previous inline comment.'), + 'Jump to the table of contents.' => + pht('Jump to the table of contents.'), + + 'Edit selected inline comment.' => + pht('Edit selected inline comment.'), + 'You must select a comment to edit.' => + pht('You must select a comment to edit.'), + + 'Reply to selected inline comment or change.' => + pht('Reply to selected inline comment or change.'), + 'You must select a comment or change to reply to.' => + pht('You must select a comment or change to reply to.'), + 'Reply and quote selected inline comment.' => + pht('Reply and quote selected inline comment.'), + + 'Mark or unmark selected inline comment as done.' => + pht('Mark or unmark selected inline comment as done.'), + 'You must select a comment to mark done.' => + pht('You must select a comment to mark done.'), + + 'Collapse or expand inline comment.' => + pht('Collapse or expand inline comment.'), + 'You must select a comment to hide.' => + pht('You must select a comment to hide.'), + + 'Jump to next inline comment, including collapsed comments.' => + pht('Jump to next inline comment, including collapsed comments.'), + 'Jump to previous inline comment, including collapsed comments.' => + pht('Jump to previous inline comment, including collapsed comments.'), + + 'This file content has been collapsed.' => + pht('This file content has been collapsed.'), + 'Show Content' => pht('Show Content'), + + 'Hide or show the current file.' => + pht('Hide or show the current file.'), + 'You must select a file to hide or show.' => + pht('You must select a file to hide or show.'), + + 'Unsaved' => pht('Unsaved'), + 'Unsubmitted' => pht('Unsubmitted'), + 'Comments' => pht('Comments'), + + 'Hide "Done" Inlines' => pht('Hide "Done" Inlines'), + 'Hide Collapsed Inlines' => pht('Hide Collapsed Inlines'), + 'Hide Older Inlines' => pht('Hide Older Inlines'), + 'Hide All Inlines' => pht('Hide All Inlines'), + 'Show All Inlines' => pht('Show All Inlines'), + + 'List Inline Comments' => pht('List Inline Comments'), + + 'Hide or show all inline comments.' => + pht('Hide or show all inline comments.'), + ), )); - $this->initBehavior('differential-comment-jump', array()); - - if ($this->inlineURI) { - Javelin::initBehavior('differential-edit-inline-comments', array( - 'uri' => $this->inlineURI, - 'stage' => 'differential-review-stage', - 'revealIcon' => hsprintf('%s', new PHUIDiffRevealIconView()), - )); - } - if ($this->header) { $header = $this->header; } else { diff --git a/src/applications/differential/view/DifferentialReviewersView.php b/src/applications/differential/view/DifferentialReviewersView.php index 034bf99edb..3fcb1c0ccf 100644 --- a/src/applications/differential/view/DifferentialReviewersView.php +++ b/src/applications/differential/view/DifferentialReviewersView.php @@ -116,7 +116,7 @@ final class DifferentialReviewersView extends AphrontView { } } else { $icon = 'fa-times-circle-o'; - $color = 'bluegrey'; + $color = 'red'; if ($authority_name !== null) { $label = pht( 'Requested Changes to Prior Diff (by %s)', @@ -158,7 +158,6 @@ final class DifferentialReviewersView extends AphrontView { private function isCurrent($action_phid) { if (!$this->diff) { - echo "A\n"; return true; } diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php index 5aacafbb69..ed97435746 100644 --- a/src/applications/differential/view/DifferentialRevisionListView.php +++ b/src/applications/differential/view/DifferentialRevisionListView.php @@ -5,7 +5,7 @@ */ final class DifferentialRevisionListView extends AphrontView { - private $revisions; + private $revisions = array(); private $handles; private $header; private $noDataString; @@ -145,8 +145,11 @@ final class DifferentialRevisionListView extends AphrontView { $item->setDisabled(true); } + $icon = $revision->getStatusIcon(); + $color = $revision->getStatusIconColor(); + $item->setStatusIcon( - $revision->getStatusIcon(), + "{$icon} {$color}", $revision->getStatusDisplayName()); $list->addItem($item); diff --git a/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php index 2f221bd8c6..b7b0e7f123 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php @@ -47,7 +47,8 @@ final class DifferentialRevisionAbandonTransaction } public function applyInternalEffects($object, $value) { - $object->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED); + $status_abandoned = DifferentialRevisionStatus::ABANDONED; + $object->setModernRevisionStatus($status_abandoned); } protected function validateAction($object, PhabricatorUser $viewer) { diff --git a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php index 7d1f1a92ef..f01ce4b487 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php @@ -50,7 +50,8 @@ final class DifferentialRevisionAcceptTransaction protected function getActionOptions( PhabricatorUser $viewer, - DifferentialRevision $revision) { + DifferentialRevision $revision, + $include_accepted = false) { $reviewers = $revision->getReviewers(); @@ -98,10 +99,13 @@ final class DifferentialRevisionAcceptTransaction } } - if ($reviewer->isAccepted($diff_phid)) { - // If a reviewer is already in a full "accepted" state, don't - // include that reviewer as an option. - continue; + if (!$include_accepted) { + if ($reviewer->isAccepted($diff_phid)) { + // If a reviewer is already in a full "accepted" state, don't + // include that reviewer as an option unless we're listing all + // reviewers, including reviewers who have already accepted. + continue; + } } $reviewer_phids[$reviewer_phid] = $reviewer_phid; @@ -185,7 +189,12 @@ final class DifferentialRevisionAcceptTransaction 'least one reviewer.')); } - list($options) = $this->getActionOptions($actor, $object); + // NOTE: We're including reviewers who have already been accepted in this + // check. Legitimate users may race one another to accept on behalf of + // packages. If we get a form submission which includes a reviewer which + // someone has already accepted, that's fine. See T12757. + + list($options) = $this->getActionOptions($actor, $object, true); foreach ($value as $phid) { if (!isset($options[$phid])) { throw new Exception( diff --git a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php index 30bfd5044f..d71b30950a 100644 --- a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php @@ -35,14 +35,10 @@ final class DifferentialRevisionCloseTransaction } public function applyInternalEffects($object, $value) { - $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; + $was_accepted = $object->isAccepted(); - $old_status = $object->getStatus(); - - $object->setStatus($status_closed); - - $was_accepted = ($old_status == $status_accepted); + $status_published = DifferentialRevisionStatus::PUBLISHED; + $object->setModernRevisionStatus($status_published); $object->setProperty( DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED, @@ -50,6 +46,14 @@ final class DifferentialRevisionCloseTransaction } protected function validateAction($object, PhabricatorUser $viewer) { + if ($this->hasEditor()) { + if ($this->getEditor()->getIsCloseByCommit()) { + // If we're closing a revision because we discovered a commit, we don't + // care what state it was in. + return; + } + } + if ($object->isClosed()) { throw new Exception( pht( @@ -78,9 +82,50 @@ final class DifferentialRevisionCloseTransaction } public function getTitle() { - return pht( - '%s closed this revision.', - $this->renderAuthor()); + if (!$this->getMetadataValue('isCommitClose')) { + return pht( + '%s closed this revision.', + $this->renderAuthor()); + } + + $commit_phid = $this->getMetadataValue('commitPHID'); + $committer_phid = $this->getMetadataValue('committerPHID'); + $author_phid = $this->getMetadataValue('authorPHID'); + + if ($committer_phid) { + $committer_name = $this->renderHandle($committer_phid); + } else { + $committer_name = $this->getMetadataValue('committerName'); + } + + if ($author_phid) { + $author_name = $this->renderHandle($author_phid); + } else { + $author_name = $this->getMetadatavalue('authorName'); + } + + $same_phid = + strlen($committer_phid) && + strlen($author_phid) && + ($committer_phid == $author_phid); + + $same_name = + !strlen($committer_phid) && + !strlen($author_phid) && + ($committer_name == $author_name); + + if ($same_name || $same_phid) { + return pht( + 'Closed by commit %s (authored by %s).', + $this->renderHandle($commit_phid), + $author_name); + } else { + return pht( + 'Closed by commit %s (authored by %s, committed by %s).', + $this->renderHandle($commit_phid), + $author_name, + $committer_name); + } } public function getTitleForFeed() { diff --git a/src/applications/differential/xaction/DifferentialRevisionInlineTransaction.php b/src/applications/differential/xaction/DifferentialRevisionInlineTransaction.php new file mode 100644 index 0000000000..35d5034033 --- /dev/null +++ b/src/applications/differential/xaction/DifferentialRevisionInlineTransaction.php @@ -0,0 +1,53 @@ +getViewer(); + + $changeset_ids = array(); + foreach ($xactions as $xaction) { + $changeset_ids[] = $xaction->getComment()->getChangesetID(); + } + + $changesets = id(new DifferentialChangesetQuery()) + ->setViewer($viewer) + ->withIDs($changeset_ids) + ->execute(); + + $changesets = mpull($changesets, null, 'getID'); + + return $changesets; + } + + public function getFieldValuesForConduit($object, $data) { + $comment = $object->getComment(); + + $changeset = $data[$comment->getChangesetID()]; + $diff = $changeset->getDiff(); + + return array( + 'diff' => array( + 'id' => (int)$diff->getID(), + 'phid' => $diff->getPHID(), + ), + 'path' => $changeset->getDisplayFilename(), + 'line' => (int)$comment->getLineNumber(), + 'length' => (int)($comment->getLineLength() + 1), + 'replyToCommentPHID' => $comment->getReplyToCommentPHID(), + ); + } + +} diff --git a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php index e7cdaa2455..6e584ceb36 100644 --- a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php @@ -46,19 +46,16 @@ final class DifferentialRevisionPlanChangesTransaction } public function generateOldValue($object) { - $status_planned = ArcanistDifferentialRevisionStatus::CHANGES_PLANNED; - return ($object->getStatus() == $status_planned); + return $object->isChangePlanned(); } public function applyInternalEffects($object, $value) { - $status_planned = ArcanistDifferentialRevisionStatus::CHANGES_PLANNED; - $object->setStatus($status_planned); + $status_planned = DifferentialRevisionStatus::CHANGES_PLANNED; + $object->setModernRevisionStatus($status_planned); } protected function validateAction($object, PhabricatorUser $viewer) { - $status_planned = ArcanistDifferentialRevisionStatus::CHANGES_PLANNED; - - if ($object->getStatus() == $status_planned) { + if ($object->isChangePlanned()) { throw new Exception( pht( 'You can not request review of this revision because this '. diff --git a/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php index 1f2f6a8d6c..4767924445 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php @@ -47,7 +47,8 @@ final class DifferentialRevisionReclaimTransaction } public function applyInternalEffects($object, $value) { - $object->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW); + $status_review = DifferentialRevisionStatus::NEEDS_REVIEW; + $object->setModernRevisionStatus($status_review); } protected function validateAction($object, PhabricatorUser $viewer) { diff --git a/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php index 84015b9286..1d28433429 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php @@ -35,14 +35,12 @@ final class DifferentialRevisionReopenTransaction } public function applyInternalEffects($object, $value) { - $object->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW); + $status_review = DifferentialRevisionStatus::NEEDS_REVIEW; + $object->setModernRevisionStatus($status_review); } protected function validateAction($object, PhabricatorUser $viewer) { - // Note that we're testing for "Closed", exactly, not just any closed - // status. - $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; - if ($object->getStatus() != $status_closed) { + if (!$object->isPublished()) { throw new Exception( pht( 'You can not reopen this revision because it is not closed. '. diff --git a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php index 32fcb3271e..d102b0c0c2 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php @@ -27,18 +27,16 @@ final class DifferentialRevisionRequestReviewTransaction } public function generateOldValue($object) { - $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; - return ($object->getStatus() == $status_review); + return $object->isNeedsReview(); } public function applyInternalEffects($object, $value) { - $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; - $object->setStatus($status_review); + $status_review = DifferentialRevisionStatus::NEEDS_REVIEW; + $object->setModernRevisionStatus($status_review); } protected function validateAction($object, PhabricatorUser $viewer) { - $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; - if ($object->getStatus() == $status_review) { + if ($object->isNeedsReview()) { throw new Exception( pht( 'You can not request review of this revision because this '. diff --git a/src/applications/differential/xaction/DifferentialRevisionReviewTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReviewTransaction.php index d44de8706c..b112eb638f 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReviewTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReviewTransaction.php @@ -17,6 +17,16 @@ abstract class DifferentialRevisionReviewTransaction $viewer = $this->getActor(); list($options, $default) = $this->getActionOptions($viewer, $object); + // Remove reviewers which aren't actionable. In the case of "Accept", we + // may allow the transaction to proceed with some reviewers who have + // already accepted, to avoid race conditions where two reviewers fill + // out the form at the same time and accept on behalf of the same package. + // It's okay for these reviewers to survive validation, but they should + // not survive beyond this point. + $value = array_fuse($value); + $value = array_intersect($value, array_keys($options)); + $value = array_values($value); + sort($default); sort($value); @@ -200,34 +210,6 @@ abstract class DifferentialRevisionReviewTransaction $map = array_select_keys($map, $value); } - // Convert reviewer statuses into edge data. - foreach ($map as $reviewer_phid => $reviewer_status) { - $map[$reviewer_phid] = array( - 'data' => array( - 'status' => $reviewer_status, - ), - ); - } - - // This is currently double-writing: to the old (edge) store and the new - // (reviewer) store. Do the old edge write first. - - $src_phid = $revision->getPHID(); - $edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST; - - $editor = new PhabricatorEdgeEditor(); - foreach ($map as $dst_phid => $edge_data) { - if ($status == DifferentialReviewerStatus::STATUS_RESIGNED) { - // TODO: For now, we just remove these reviewers. In the future, we will - // store resignations explicitly. - $editor->removeEdge($src_phid, $edge_type, $dst_phid); - } else { - $editor->addEdge($src_phid, $edge_type, $dst_phid, $edge_data); - } - } - - $editor->save(); - // Now, do the new write. if ($map) { @@ -239,6 +221,7 @@ abstract class DifferentialRevisionReviewTransaction } $table = new DifferentialReviewer(); + $src_phid = $revision->getPHID(); $reviewers = $table->loadAllWhere( 'revisionPHID = %s AND reviewerPHID IN (%Ls)', @@ -246,7 +229,7 @@ abstract class DifferentialRevisionReviewTransaction array_keys($map)); $reviewers = mpull($reviewers, null, 'getReviewerPHID'); - foreach ($map as $dst_phid => $edge_data) { + foreach (array_keys($map) as $dst_phid) { $reviewer = idx($reviewers, $dst_phid); if (!$reviewer) { $reviewer = id(new DifferentialReviewer()) diff --git a/src/applications/differential/xaction/DifferentialRevisionReviewersTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReviewersTransaction.php index a7122f9de4..8b1f9807c1 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReviewersTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReviewersTransaction.php @@ -106,43 +106,9 @@ final class DifferentialRevisionReviewersTransaction public function applyExternalEffects($object, $value) { $src_phid = $object->getPHID(); - // This is currently double-writing: to the old (edge) store and the new - // (reviewer) store. Do the old edge write first. - $old = $this->generateOldValue($object); $new = $value; - $edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST; - - $editor = new PhabricatorEdgeEditor(); - $rem = array_diff_key($old, $new); - foreach ($rem as $dst_phid => $status) { - $editor->removeEdge($src_phid, $edge_type, $dst_phid); - } - - foreach ($new as $dst_phid => $status) { - $old_status = idx($old, $dst_phid); - if ($old_status === $status) { - continue; - } - - $data = array( - 'data' => array( - 'status' => $status, - - // TODO: This seemes like it's buggy before the Modular Transactions - // changes. Figure out what's going on here? We don't have a very - // clean way to get the active diff ID right now. - 'diffID' => null, - ), - ); - - $editor->addEdge($src_phid, $edge_type, $dst_phid, $data); - } - - $editor->save(); - - // Now, do the new write. $table = new DifferentialReviewer(); $table_name = $table->getTableName(); diff --git a/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php b/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php new file mode 100644 index 0000000000..615ce38bcf --- /dev/null +++ b/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php @@ -0,0 +1,84 @@ +getModernRevisionStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setModernRevisionStatus($value); + } + + public function getTitle() { + $status = $this->newStatusObject(); + + if ($status->isAccepted()) { + return pht('This revision is now accepted and ready to land.'); + } + + if ($status->isNeedsRevision()) { + return pht('This revision now requires changes to proceed.'); + } + + if ($status->isNeedsReview()) { + return pht('This revision now requires review to proceed.'); + } + + return null; + } + + public function getTitleForFeed() { + $status = $this->newStatusObject(); + + if ($status->isAccepted()) { + return pht( + '%s is now accepted and ready to land.', + $this->renderObject()); + } + + if ($status->isNeedsRevision()) { + return pht( + '%s now requires changes to proceed.', + $this->renderObject()); + } + + if ($status->isNeedsReview()) { + return pht( + '%s now requires review to proceed.', + $this->renderObject()); + } + + return null; + } + + public function getIcon() { + $status = $this->newStatusObject(); + return $status->getTimelineIcon(); + } + + public function getColor() { + $status = $this->newStatusObject(); + return $status->getTimelineColor(); + } + + private function newStatusObject() { + $new = $this->getNewValue(); + return DifferentialRevisionStatus::newForStatus($new); + } + + public function getTransactionTypeForConduit($xaction) { + return 'status'; + } + + public function getFieldValuesForConduit($object, $data) { + return array( + 'old' => $object->getOldValue(), + 'new' => $object->getNewValue(), + ); + } + +} diff --git a/src/applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php b/src/applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php index bf2beab3d8..c7c77fbcff 100644 --- a/src/applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php @@ -55,7 +55,10 @@ final class DifferentialRevisionTestPlanTransaction } public function validateTransactions($object, array $xactions) { - $errors = array(); + $errors = $this->validateCommitMessageCorpusTransactions( + $object, + $xactions, + pht('Test Plan')); $is_required = PhabricatorEnv::getEnvConfig( 'differential.require-test-plan-field'); diff --git a/src/applications/differential/xaction/DifferentialRevisionTitleTransaction.php b/src/applications/differential/xaction/DifferentialRevisionTitleTransaction.php index 9b763c53ca..812464b26d 100644 --- a/src/applications/differential/xaction/DifferentialRevisionTitleTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionTitleTransaction.php @@ -55,4 +55,15 @@ final class DifferentialRevisionTitleTransaction return $errors; } + public function getTransactionTypeForConduit($xaction) { + return 'title'; + } + + public function getFieldValuesForConduit($object, $data) { + return array( + 'old' => $object->getOldValue(), + 'new' => $object->getNewValue(), + ); + } + } diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index 8fef05bd88..cf9c14aa8b 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -6,6 +6,10 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { return pht('Diffusion'); } + public function getMenuName() { + return pht('Repositories'); + } + public function getShortDescription() { return pht('Host and Browse Repositories'); } @@ -55,7 +59,9 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { '' => 'DiffusionRepositoryController', 'repository/(?P.*)' => 'DiffusionRepositoryController', 'change/(?P.*)' => 'DiffusionChangeController', + 'clone/' => 'DiffusionCloneController', 'history/(?P.*)' => 'DiffusionHistoryController', + 'graph/(?P.*)' => 'DiffusionGraphController', 'browse/(?P.*)' => 'DiffusionBrowseController', 'lastmodified/(?P.*)' => 'DiffusionLastModifiedController', 'diff/' => 'DiffusionDiffController', @@ -140,6 +146,8 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { $this->getEditRoutePattern('edit/') => 'DiffusionCommitEditController', ), + 'picture/(?P[0-9]\d*)/' + => 'DiffusionRepositoryProfilePictureController', ), ); } diff --git a/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php index e9b8f8d15e..389f931c0f 100644 --- a/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php @@ -57,11 +57,14 @@ final class DiffusionSearchQueryConduitAPIMethod $results = array(); $future = $repository->getLocalCommandFuture( // NOTE: --perl-regexp is available only with libpcre compiled in. - 'grep --extended-regexp --null -n --no-color -e %s %s -- %s', - $grep, + 'grep --extended-regexp --null -n --no-color -f - %s -- %s', $drequest->getStableCommit(), $path); + // NOTE: We're writing the pattern on stdin to avoid issues with UTF8 + // being mangled by the shell. See T12807. + $future->write($grep); + $binary_pattern = '/Binary file [^:]*:(.+) matches/'; $lines = new LinesOfALargeExecFuture($future); diff --git a/src/applications/diffusion/controller/DiffusionBranchTableController.php b/src/applications/diffusion/controller/DiffusionBranchTableController.php index e5e033f416..441421ba3f 100644 --- a/src/applications/diffusion/controller/DiffusionBranchTableController.php +++ b/src/applications/diffusion/controller/DiffusionBranchTableController.php @@ -48,7 +48,7 @@ final class DiffusionBranchTableController extends DiffusionController { ->withRepository($repository) ->execute(); - $table = id(new DiffusionBranchTableView()) + $list = id(new DiffusionBranchListView()) ->setUser($viewer) ->setBranches($branches) ->setCommits($commits) @@ -57,7 +57,8 @@ final class DiffusionBranchTableController extends DiffusionController { $content = id(new PHUIObjectBoxView()) ->setHeaderText($repository->getName()) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table) + ->addClass('diffusion-mobile-view') + ->setTable($list) ->setPager($pager); } @@ -71,8 +72,16 @@ final class DiffusionBranchTableController extends DiffusionController { ->setHeader(pht('Branches')) ->setHeaderIcon('fa-code-fork'); + if (!$repository->isSVN()) { + $branch_tag = $this->renderBranchTag($drequest); + $header->addTag($branch_tag); + } + + $tabs = $this->buildTabsView('branch'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setTabs($tabs) ->setFooter(array( $content, )); @@ -84,10 +93,7 @@ final class DiffusionBranchTableController extends DiffusionController { $repository->getDisplayName(), )) ->setCrumbs($crumbs) - ->appendChild( - array( - $view, - )); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 4eb6144ede..076e667dc4 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -5,6 +5,7 @@ final class DiffusionBrowseController extends DiffusionController { private $lintCommit; private $lintMessages; private $coverage; + private $corpusButtons = array(); public function shouldAllowPublic() { return true; @@ -22,8 +23,7 @@ final class DiffusionBrowseController extends DiffusionController { // list. $grep = $request->getStr('grep'); - $find = $request->getStr('find'); - if (strlen($grep) || strlen($find)) { + if (strlen($grep)) { return $this->browseSearch(); } @@ -57,14 +57,17 @@ final class DiffusionBrowseController extends DiffusionController { private function browseSearch() { $drequest = $this->getDiffusionRequest(); $header = $this->buildHeaderView($drequest); + $path = nonempty(basename($drequest->getPath()), '/'); - $search_form = $this->renderSearchForm(); $search_results = $this->renderSearchResults(); + $search_form = $this->renderSearchForm($path); - $search_form = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Search')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($search_form); + $search_form = phutil_tag( + 'div', + array( + 'class' => 'diffusion-mobile-search-form', + ), + $search_form); $crumbs = $this->buildCrumbs( array( @@ -74,8 +77,11 @@ final class DiffusionBrowseController extends DiffusionController { )); $crumbs->setBorder(true); + $tabs = $this->buildTabsView('code'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setTabs($tabs) ->setFooter( array( $search_form, @@ -106,7 +112,6 @@ final class DiffusionBrowseController extends DiffusionController { $path = $drequest->getPath(); $blame_key = PhabricatorDiffusionBlameSetting::SETTINGKEY; - $show_blame = $request->getBool( 'blame', $viewer->getUserSetting($blame_key)); @@ -160,6 +165,7 @@ final class DiffusionBrowseController extends DiffusionController { $hit_time_limit = $response['tooSlow']; $file_phid = $response['filePHID']; + $show_editor = false; if ($hit_byte_limit) { $corpus = $this->buildErrorCorpus( pht( @@ -215,6 +221,7 @@ final class DiffusionBrowseController extends DiffusionController { } else { $this->loadLintMessages(); $this->coverage = $drequest->loadCoverage(); + $show_editor = true; // Build the content of the file. $corpus = $this->buildCorpus( @@ -234,55 +241,47 @@ final class DiffusionBrowseController extends DiffusionController { require_celerity_resource('diffusion-source-css'); // Render the page. - $view = $this->buildCurtain($drequest); - $curtain = $this->enrichCurtain( - $view, - $drequest, - $show_blame); - - $properties = $this->buildPropertyView($drequest); + $bar = $this->buildButtonBar($drequest, $show_blame, $show_editor); $header = $this->buildHeaderView($drequest); $header->setHeaderIcon('fa-file-code-o'); - $content = array(); - $follow = $request->getStr('follow'); + $follow_notice = null; if ($follow) { - $notice = new PHUIInfoView(); - $notice->setSeverity(PHUIInfoView::SEVERITY_WARNING); - $notice->setTitle(pht('Unable to Continue')); + $follow_notice = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setTitle(pht('Unable to Continue')); switch ($follow) { case 'first': - $notice->appendChild( + $follow_notice->appendChild( pht( 'Unable to continue tracing the history of this file because '. 'this commit is the first commit in the repository.')); break; case 'created': - $notice->appendChild( + $follow_notice->appendChild( pht( 'Unable to continue tracing the history of this file because '. 'this commit created the file.')); break; } - $content[] = $notice; } $renamed = $request->getStr('renamed'); + $renamed_notice = null; if ($renamed) { - $notice = new PHUIInfoView(); - $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE); - $notice->setTitle(pht('File Renamed')); - $notice->appendChild( - pht( - 'File history passes through a rename from "%s" to "%s".', - $drequest->getPath(), - $renamed)); - $content[] = $notice; + $renamed_notice = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->setTitle(pht('File Renamed')) + ->appendChild( + pht( + 'File history passes through a rename from "%s" to "%s".', + $drequest->getPath(), + $renamed)); } - $content[] = $corpus; - $content[] = $this->buildOpenRevisions(); + $open_revisions = $this->buildOpenRevisions(); + $owners_list = $this->buildOwnersList($drequest); $crumbs = $this->buildCrumbs( array( @@ -293,17 +292,20 @@ final class DiffusionBrowseController extends DiffusionController { $crumbs->setBorder(true); $basename = basename($this->getDiffusionRequest()->getPath()); + $tabs = $this->buildTabsView('code'); + $bar->setRight($this->corpusButtons); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setCurtain($curtain) - ->setMainColumn(array( - $content, - )); - - if ($properties) { - $view->addPropertySection(pht('Details'), $properties); - } + ->setTabs($tabs) + ->setFooter(array( + $bar, + $follow_notice, + $renamed_notice, + $corpus, + $open_revisions, + $owners_list, + )); $title = array($basename, $repository->getDisplayName()); @@ -327,14 +329,12 @@ final class DiffusionBrowseController extends DiffusionController { $reason = $results->getReasonForEmptyResultSet(); - $curtain = $this->buildCurtain($drequest); + $this->buildActionButtons($drequest, true); $details = $this->buildPropertyView($drequest); $header = $this->buildHeaderView($drequest); $header->setHeaderIcon('fa-folder-open'); - $search_form = $this->renderSearchForm(); - $empty_result = null; $browse_panel = null; $branch_panel = null; @@ -363,22 +363,17 @@ final class DiffusionBrowseController extends DiffusionController { ->setPaths($results->getPaths()) ->setUser($request->getUser()); - $browse_header = id(new PHUIHeaderView()) - ->setHeader(nonempty(basename($drequest->getPath()), '/')) - ->setHeaderIcon('fa-folder-open'); + $title = nonempty(basename($drequest->getPath()), '/'); + $icon = 'fa-folder-open'; + $browse_header = $this->buildPanelHeaderView($title, $icon); $browse_panel = id(new PHUIObjectBoxView()) ->setHeader($browse_header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($browse_table) + ->addClass('diffusion-mobile-view') ->setPager($pager); - $browse_panel->setShowHide( - array(pht('Show Search')), - pht('Hide Search'), - $search_form, - '#'); - $path = $drequest->getPath(); $is_branch = (!strlen($path) && $repository->supportsBranchComparison()); if ($is_branch) { @@ -397,19 +392,23 @@ final class DiffusionBrowseController extends DiffusionController { )); $crumbs->setBorder(true); + $tabs = $this->buildTabsView('code'); + $owners_list = $this->buildOwnersList($drequest); + $bar = id(new PHUILeftRightView()) + ->setRight($this->corpusButtons) + ->addClass('diffusion-action-bar'); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setCurtain($curtain) - ->setMainColumn( + ->setTabs($tabs) + ->setFooter( array( + $bar, $branch_panel, $empty_result, $browse_panel, - )) - ->setFooter( - array( $open_revisions, + $owners_list, $readme, )); @@ -457,159 +456,58 @@ final class DiffusionBrowseController extends DiffusionController { 'limit' => $pager->getPageSize() + 1, 'offset' => $pager->getOffset(), )); - } else { // Filename search. - $search_mode = 'find'; - $query_string = $request->getStr('find'); - $results = $this->callConduitWithDiffusionRequest( - 'diffusion.querypaths', - array( - 'pattern' => $query_string, - 'commit' => $drequest->getStableCommit(), - 'path' => $drequest->getPath(), - 'limit' => $pager->getPageSize() + 1, - 'offset' => $pager->getOffset(), - )); } break; } $results = $pager->sliceResults($results); + $table = null; + $header = null; if ($search_mode == 'grep') { $table = $this->renderGrepResults($results, $query_string); - $header = pht( + $title = pht( 'File content matching "%s" under "%s"', $query_string, nonempty($drequest->getPath(), '/')); - } else { - $table = $this->renderFindResults($results); - $header = pht( - 'Paths matching "%s" under "%s"', - $query_string, - nonempty($drequest->getPath(), '/')); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->addClass('diffusion-search-result-header'); } - return id(new PHUIObjectBoxView()) - ->setHeaderText($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table) - ->setPager($pager); + return array($header, $table, $pager); } private function renderGrepResults(array $results, $pattern) { $drequest = $this->getDiffusionRequest(); - require_celerity_resource('phabricator-search-results-css'); - $rows = array(); - foreach ($results as $result) { - list($path, $line, $string) = $result; - - $href = $drequest->generateURI(array( - 'action' => 'browse', - 'path' => $path, - 'line' => $line, - )); - - $matches = null; - $count = @preg_match_all( - '('.$pattern.')u', - $string, - $matches, - PREG_OFFSET_CAPTURE); - - if (!$count) { - $output = ltrim($string); - } else { - $output = array(); - $cursor = 0; - $length = strlen($string); - foreach ($matches[0] as $match) { - $offset = $match[1]; - if ($cursor != $offset) { - $output[] = array( - 'text' => substr($string, $cursor, $offset), - 'highlight' => false, - ); - } - $output[] = array( - 'text' => $match[0], - 'highlight' => true, - ); - $cursor = $offset + strlen($match[0]); - } - if ($cursor != $length) { - $output[] = array( - 'text' => substr($string, $cursor), - 'highlight' => false, - ); - } - - if ($output) { - $output[0]['text'] = ltrim($output[0]['text']); - } - - foreach ($output as $key => $segment) { - if ($segment['highlight']) { - $output[$key] = phutil_tag('strong', array(), $segment['text']); - } else { - $output[$key] = $segment['text']; - } - } - } - - $string = phutil_tag( - 'pre', - array('class' => 'PhabricatorMonospaced phui-source-fragment'), - $output); - - $path = Filesystem::readablePath($path, $drequest->getPath()); - - $rows[] = array( - phutil_tag('a', array('href' => $href), $path), - $line, - $string, - ); + if (!$results) { + return id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NODATA) + ->appendChild( + pht( + 'The pattern you searched for was not found in the content of any '. + 'files.')); } - $table = id(new AphrontTableView($rows)) - ->setClassName('remarkup-code') - ->setHeaders(array(pht('Path'), pht('Line'), pht('String'))) - ->setColumnClasses(array('', 'n', 'wide')) - ->setNoDataString( - pht( - 'The pattern you searched for was not found in the content of any '. - 'files.')); - - return $table; - } - - private function renderFindResults(array $results) { - $drequest = $this->getDiffusionRequest(); - - $rows = array(); - foreach ($results as $result) { - $href = $drequest->generateURI(array( - 'action' => 'browse', - 'path' => $result, - )); - - $readable = Filesystem::readablePath($result, $drequest->getPath()); - - $rows[] = array( - phutil_tag('a', array('href' => $href), $readable), - ); + $grouped = array(); + foreach ($results as $file) { + list($path, $line, $string) = $file; + $grouped[$path][] = array($line, $string); } - $table = id(new AphrontTableView($rows)) - ->setHeaders(array(pht('Path'))) - ->setColumnClasses(array('wide')) - ->setNoDataString( - pht( - 'The pattern you searched for did not match the names of any '. - 'files.')); + $view = array(); + foreach ($grouped as $path => $matches) { + $view[] = id(new DiffusionPatternSearchView()) + ->setPath($path) + ->setMatches($matches) + ->setPattern($pattern) + ->setDiffusionRequest($drequest) + ->render(); + } - return $table; + return $view; } private function loadLintMessages() { @@ -698,6 +596,8 @@ final class DiffusionBrowseController extends DiffusionController { ), $rows); + $corpus_table = phutil_tag_div('diffusion-source-wrap', $corpus_table); + if ($this->getRequest()->isAjax()) { return $corpus_table; } @@ -745,19 +645,19 @@ final class DiffusionBrowseController extends DiffusionController { Javelin::initBehavior('load-blame', array('id' => $id)); + $this->corpusButtons[] = $this->renderFileButton(); + $title = basename($this->getDiffusionRequest()->getPath()); + $icon = 'fa-file-code-o'; + $drequest = $this->getDiffusionRequest(); + $this->buildActionButtons($drequest); - $edit = $this->renderEditButton(); - $file = $this->renderFileButton(); - $header = id(new PHUIHeaderView()) - ->setHeader(basename($this->getDiffusionRequest()->getPath())) - ->setHeaderIcon('fa-file-code-o') - ->addActionLink($edit) - ->addActionLink($file); + $header = $this->buildPanelHeaderView($title, $icon); $corpus = id(new PHUIObjectBoxView()) ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($corpus) + ->addClass('diffusion-mobile-view') ->setCollapsed(true); $messages = array(); @@ -791,23 +691,34 @@ final class DiffusionBrowseController extends DiffusionController { return $corpus; } - private function enrichCurtain( - PHUICurtainView $curtain, + private function buildButtonBar( DiffusionRequest $drequest, - $show_blame) { + $show_blame, + $show_editor) { $viewer = $this->getViewer(); $base_uri = $this->getRequest()->getRequestURI(); - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Show Last Change')) + $user = $this->getRequest()->getUser(); + $repository = $drequest->getRepository(); + $path = $drequest->getPath(); + $line = nonempty((int)$drequest->getLine(), 1); + $buttons = array(); + + $editor_link = $user->loadEditorLink($path, $line, $repository); + $template = $user->loadEditorLink($path, '%l', $repository); + + $buttons[] = + id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Last Change')) + ->setColor(PHUIButtonView::GREY) ->setHref( $drequest->generateURI( array( 'action' => 'change', ))) - ->setIcon('fa-backward')); + ->setIcon('fa-backward'); if ($show_blame) { $blame_text = pht('Disable Blame'); @@ -819,39 +730,77 @@ final class DiffusionBrowseController extends DiffusionController { $blame_value = 1; } - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName($blame_text) - ->setHref($base_uri->alter('blame', $blame_value)) - ->setIcon($blame_icon) - ->setUser($viewer) - ->setRenderAsForm($viewer->isLoggedIn())); + $blame = id(new PHUIButtonView()) + ->setText($blame_text) + ->setIcon($blame_icon) + ->setUser($viewer) + ->setSelected(!$blame_value) + ->setColor(PHUIButtonView::GREY); + + if ($viewer->isLoggedIn()) { + $blame = phabricator_form( + $viewer, + array( + 'action' => $base_uri->alter('blame', $blame_value), + 'method' => 'POST', + 'style' => 'display: inline-block;', + ), + $blame); + } else { + $blame->setTag('a'); + $blame->setHref($base_uri->alter('blame', $blame_value)); + } + $buttons[] = $blame; + + if ($editor_link) { + $buttons[] = + id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Open File')) + ->setHref($editor_link) + ->setIcon('fa-pencil') + ->setID('editor_link') + ->setMetadata(array('link_template' => $template)) + ->setDisabled(!$editor_link) + ->setColor(PHUIButtonView::GREY); + } $href = null; + $show_lint = true; if ($this->getRequest()->getStr('lint') !== null) { - $lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages)); + $lint_text = pht('Hide Lint'); $href = $base_uri->alter('lint', null); } else if ($this->lintCommit === null) { - $lint_text = pht('Lint not Available'); + $show_lint = false; } else { - $lint_text = pht( - 'Show %d Lint Message(s)', - count($this->lintMessages)); + $lint_text = pht('Show Lint'); $href = $this->getDiffusionRequest()->generateURI(array( 'action' => 'browse', 'commit' => $this->lintCommit, ))->alter('lint', ''); } - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName($lint_text) - ->setHref($href) - ->setIcon('fa-exclamation-triangle') - ->setDisabled(!$href)); + if ($show_lint) { + $buttons[] = + id(new PHUIButtonView()) + ->setTag('a') + ->setText($lint_text) + ->setHref($href) + ->setIcon('fa-exclamation-triangle') + ->setDisabled(!$href) + ->setColor(PHUIButtonView::GREY); + } + $bar = id(new PHUILeftRightView()) + ->setLeft($buttons) + ->addClass('diffusion-action-bar full-mobile-buttons'); + return $bar; + } + private function buildOwnersList(DiffusionRequest $drequest) { + + $viewer = $this->getViewer(); $repository = $drequest->getRepository(); $owners = 'PhabricatorOwnersApplication'; @@ -871,55 +820,55 @@ final class DiffusionBrowseController extends DiffusionController { $repository->getPHID(), $drequest->getPath()); + $ownership = id(new PHUIObjectItemListView()) + ->setUser($viewer) + ->setNoDataString(pht('No Owners')); + if ($packages) { - $ownership = id(new PHUIStatusListView()) - ->setUser($viewer); - foreach ($packages as $package) { - $icon = 'fa-list-alt'; - $color = 'grey'; + $item = id(new PHUIObjectItemView()) + ->setObject($package) + ->setObjectName($package->getMonogram()) + ->setHeader($package->getName()) + ->setHref($package->getURI()); - $item = id(new PHUIStatusItemView()) - ->setIcon($icon, $color) - ->setTarget($viewer->renderHandle($package->getPHID())); + $owners = $package->getOwners(); + if ($owners) { + $owner_list = $viewer->renderHandleList( + mpull($owners, 'getUserPHID')); + } else { + $owner_list = phutil_tag('em', array(), pht('None')); + } + $item->addAttribute(pht('Owners: %s', $owner_list)); + + $auto = $package->getAutoReview(); + $autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap(); + $spec = idx($autoreview_map, $auto, array()); + $name = idx($spec, 'name', $auto); + $item->addIcon('fa-code', $name); + + if ($package->getAuditingEnabled()) { + $item->addIcon('fa-check', pht('Auditing Enabled')); + } else { + $item->addIcon('fa-ban', pht('No Auditing')); + } + + if ($package->isArchived()) { + $item->setDisabled(true); + } $ownership->addItem($item); } - } else { - $ownership = phutil_tag('em', array(), pht('None')); } - $curtain->newPanel() - ->setHeaderText(pht('Owners')) - ->appendChild($ownership); + $view = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Owner Packages')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addClass('diffusion-mobile-view') + ->setObjectList($ownership); } - return $curtain; - } - - private function renderEditButton() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $drequest = $this->getDiffusionRequest(); - - $repository = $drequest->getRepository(); - $path = $drequest->getPath(); - $line = nonempty((int)$drequest->getLine(), 1); - - $editor_link = $user->loadEditorLink($path, $line, $repository); - $template = $user->loadEditorLink($path, '%l', $repository); - - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Open in Editor')) - ->setHref($editor_link) - ->setIcon('fa-pencil') - ->setID('editor_link') - ->setMetadata(array('link_template' => $template)) - ->setDisabled(!$editor_link); - - return $button; + return $view; } private function renderFileButton($file_uri = null, $label = null) { @@ -927,11 +876,11 @@ final class DiffusionBrowseController extends DiffusionController { $base_uri = $this->getRequest()->getRequestURI(); if ($file_uri) { - $text = pht('Download Raw File'); + $text = pht('Download File'); $href = $file_uri; $icon = 'fa-download'; } else { - $text = pht('View Raw File'); + $text = pht('Raw File'); $href = $base_uri->alter('view', 'raw'); $icon = 'fa-file-text'; } @@ -944,7 +893,8 @@ final class DiffusionBrowseController extends DiffusionController { ->setTag('a') ->setText($text) ->setHref($href) - ->setIcon($icon); + ->setIcon($icon) + ->setColor(PHUIButtonView::GREY); return $button; } @@ -1173,7 +1123,6 @@ final class DiffusionBrowseController extends DiffusionController { )); } - $skip_text = pht('Skip Past This Commit'); foreach ($display as $line_index => $line) { $row = array(); @@ -1189,12 +1138,14 @@ final class DiffusionBrowseController extends DiffusionController { $revision_link = null; $commit_link = null; $before_link = null; + $commit_date = null; - $style = 'background: '.$line['color'].';'; + $style = 'border-right: 3px solid '.$line['color'].';'; if ($identifier && !$line['duplicate']) { if (isset($commit_links[$identifier])) { - $commit_link = $commit_links[$identifier]; + $commit_link = $commit_links[$identifier]['link']; + $commit_date = $commit_links[$identifier]['date']; } if (isset($revision_map[$identifier])) { @@ -1205,6 +1156,10 @@ final class DiffusionBrowseController extends DiffusionController { } $skip_href = $line_href.'?before='.$identifier.'&view=blame'; + $skip_text = pht('Skip Past This Commit'); + $icon = id(new PHUIIconView()) + ->setIcon('fa-caret-square-o-left'); + $before_link = javelin_tag( 'a', array( @@ -1216,7 +1171,7 @@ final class DiffusionBrowseController extends DiffusionController { 'size' => 300, ), ), - "\xC2\xAB"); + $icon); } if ($show_blame) { @@ -1227,33 +1182,41 @@ final class DiffusionBrowseController extends DiffusionController { ), $before_link); - $object_links = array(); - $object_links[] = $commit_link; - if ($revision_link) { - $object_links[] = phutil_tag('span', array(), '/'); - $object_links[] = $revision_link; - } - $row[] = phutil_tag( 'th', array( 'class' => 'diffusion-rev-link', ), - $object_links); + $commit_link); + + if ($revision_map) { + $row[] = phutil_tag( + 'th', + array( + 'class' => 'diffusion-blame-revision', + ), + $revision_link); + } + + $row[] = phutil_tag( + 'th', + array( + 'class' => 'diffusion-blame-date', + ), + $commit_date); } $line_link = phutil_tag( 'a', array( 'href' => $line_href, - 'style' => $style, ), $line_number); $row[] = javelin_tag( 'th', array( - 'class' => 'diffusion-line-link', + 'class' => 'diffusion-line-link ', 'sigil' => 'phabricator-source-line', 'style' => $style, ), @@ -1373,15 +1336,17 @@ final class DiffusionBrowseController extends DiffusionController { 'src' => $file_uri, ))); - $file = $this->renderFileButton($file_uri); - $header = id(new PHUIHeaderView()) - ->setHeader(basename($this->getDiffusionRequest()->getPath())) - ->addActionLink($file) - ->setHeaderIcon('fa-file-image-o'); + $this->corpusButtons[] = $this->renderFileButton($file_uri); + $title = basename($this->getDiffusionRequest()->getPath()); + $icon = 'fa-file-image-o'; + $drequest = $this->getDiffusionRequest(); + $this->buildActionButtons($drequest); + $header = $this->buildPanelHeaderView($title, $icon); return id(new PHUIObjectBoxView()) ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addClass('diffusion-mobile-view') ->addPropertyList($properties); } @@ -1392,14 +1357,17 @@ final class DiffusionBrowseController extends DiffusionController { ->addPadding(PHUI::PADDING_LARGE) ->appendChild($text); - $file = $this->renderFileButton($file_uri); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Details')) - ->addActionLink($file); + $this->corpusButtons[] = $this->renderFileButton($file_uri); + $title = basename($this->getDiffusionRequest()->getPath()); + $icon = 'fa-file'; + $drequest = $this->getDiffusionRequest(); + $this->buildActionButtons($drequest); + $header = $this->buildPanelHeaderView($title, $icon); $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addClass('diffusion-mobile-view') ->appendChild($text); return $box; @@ -1564,70 +1532,6 @@ final class DiffusionBrowseController extends DiffusionController { return head($parents); } - private function renderRevisionTooltip( - DifferentialRevision $revision, - $handles) { - $viewer = $this->getRequest()->getUser(); - - $date = phabricator_date($revision->getDateModified(), $viewer); - $id = $revision->getID(); - $title = $revision->getTitle(); - $header = "D{$id} {$title}"; - - $author = $handles[$revision->getAuthorPHID()]->getName(); - - return "{$header}\n{$date} \xC2\xB7 {$author}"; - } - - private function renderCommitTooltip( - PhabricatorRepositoryCommit $commit, - $author) { - - $viewer = $this->getRequest()->getUser(); - - $date = phabricator_date($commit->getEpoch(), $viewer); - $summary = trim($commit->getSummary()); - - return "{$summary}\n{$date} \xC2\xB7 {$author}"; - } - - protected function renderSearchForm() { - $drequest = $this->getDiffusionRequest(); - - $forms = array(); - $form = id(new AphrontFormView()) - ->setUser($this->getViewer()) - ->setMethod('GET'); - - switch ($drequest->getRepository()->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $forms[] = id(clone $form) - ->appendChild(pht('Search is not available in Subversion.')); - break; - default: - $forms[] = id(clone $form) - ->appendChild( - id(new AphrontFormTextWithSubmitControl()) - ->setLabel(pht('File Name')) - ->setSubmitLabel(pht('Search File Names')) - ->setName('find') - ->setValue($this->getRequest()->getStr('find'))); - $forms[] = id(clone $form) - ->appendChild( - id(new AphrontFormTextWithSubmitControl()) - ->setLabel(pht('Pattern')) - ->setSubmitLabel(pht('Grep File Content')) - ->setName('grep') - ->setValue($this->getRequest()->getStr('grep'))); - break; - } - - require_celerity_resource('diffusion-icons-css'); - $form_box = phutil_tag_div('diffusion-search-boxen', $forms); - - return $form_box; - } - protected function markupText($text) { $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine(); $engine->setConfig('viewer', $this->getRequest()->getUser()); @@ -1645,63 +1549,85 @@ final class DiffusionBrowseController extends DiffusionController { protected function buildHeaderView(DiffusionRequest $drequest) { $viewer = $this->getViewer(); + $repository = $drequest->getRepository(); - $tag = $this->renderCommitHashTag($drequest); + $commit_tag = $this->renderCommitHashTag($drequest); + + $path = nonempty(basename($drequest->getPath()), '/'); + $search = $this->renderSearchForm($path); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($this->renderPathLinks($drequest, $mode = 'browse')) - ->addTag($tag); + ->addActionItem($search) + ->addTag($commit_tag) + ->addClass('diffusion-browse-header'); + + if (!$repository->isSVN()) { + $branch_tag = $this->renderBranchTag($drequest); + $header->addTag($branch_tag); + } return $header; } - protected function buildCurtain(DiffusionRequest $drequest) { + protected function buildPanelHeaderView($title, $icon) { + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($icon) + ->addClass('diffusion-panel-header-view'); + + return $header; + + } + + protected function buildActionButtons( + DiffusionRequest $drequest, + $is_directory = false) { + $viewer = $this->getViewer(); $repository = $drequest->getRepository(); - - $curtain = $this->newCurtainView($drequest); - - $history_uri = $drequest->generateURI( - array( - 'action' => 'history', - )); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View History')) - ->setHref($history_uri) - ->setIcon('fa-list')); - + $history_uri = $drequest->generateURI(array('action' => 'history')); $behind_head = $drequest->getSymbolicCommit(); - - if ($repository->supportsBranchComparison()) { - $compare_uri = $drequest->generateURI( - array( - 'action' => 'compare', - )); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Compare Against...')) - ->setIcon('fa-code-fork') - ->setWorkflow(true) - ->setHref($compare_uri)); - } - + $compare = null; $head_uri = $drequest->generateURI( array( 'commit' => '', 'action' => 'browse', )); - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Jump to HEAD')) + + if ($repository->supportsBranchComparison() && $is_directory) { + $compare_uri = $drequest->generateURI(array('action' => 'compare')); + $compare = id(new PHUIButtonView()) + ->setText(pht('Compare')) + ->setIcon('fa-code-fork') + ->setWorkflow(true) + ->setTag('a') + ->setHref($compare_uri) + ->setColor(PHUIButtonView::GREY); + $this->corpusButtons[] = $compare; + } + + $head = null; + if ($behind_head) { + $head = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Back to HEAD')) ->setHref($head_uri) ->setIcon('fa-home') - ->setDisabled(!$behind_head)); + ->setColor(PHUIButtonView::GREY); + $this->corpusButtons[] = $head; + } + + $history = id(new PHUIButtonView()) + ->setText(pht('History')) + ->setHref($history_uri) + ->setTag('a') + ->setIcon('fa-history') + ->setColor(PHUIButtonView::GREY); + $this->corpusButtons[] = $history; - return $curtain; } protected function buildPropertyView( @@ -1758,7 +1684,7 @@ final class DiffusionBrowseController extends DiffusionController { $revisions = id(new DifferentialRevisionQuery()) ->setViewer($viewer) ->withPath($repository->getID(), $path_id) - ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) + ->withIsOpen(true) ->withUpdatedEpochBetween($recent, null) ->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED) ->setLimit(10) @@ -1772,17 +1698,22 @@ final class DiffusionBrowseController extends DiffusionController { } $header = id(new PHUIHeaderView()) - ->setHeader(pht('Recently Open Revisions')) - ->setHeaderIcon('fa-gear'); + ->setHeader(pht('Recently Open Revisions')); - $view = id(new DifferentialRevisionListView()) - ->setHeader($header) + $list = id(new DifferentialRevisionListView()) ->setRevisions($revisions) - ->setUser($viewer); + ->setUser($viewer) + ->setNoBox(true); - $phids = $view->getRequiredHandlePHIDs(); + $phids = $list->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); - $view->setHandles($handles); + $list->setHandles($handles); + + $view = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addClass('diffusion-mobile-view') + ->appendChild($list); return $view; } @@ -1807,9 +1738,6 @@ final class DiffusionBrowseController extends DiffusionController { ->setViewer($viewer) ->withRepository($repository) ->withIdentifiers($identifiers) - // TODO: We only fetch this to improve author display behavior, but - // shouldn't really need to? - ->needCommitData(true) ->execute(); $commits = mpull($commits, null, 'getCommitIdentifier'); } else { @@ -1821,25 +1749,27 @@ final class DiffusionBrowseController extends DiffusionController { private function renderCommitLinks(array $commits, $handles) { $links = array(); + $viewer = $this->getViewer(); foreach ($commits as $identifier => $commit) { - $tooltip = $this->renderCommitTooltip( - $commit, - $commit->renderAuthorShortName($handles)); + $date = phabricator_date($commit->getEpoch(), $viewer); + $summary = trim($commit->getSummary()); - $commit_link = javelin_tag( + $commit_link = phutil_tag( 'a', array( 'href' => $commit->getURI(), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $tooltip, - 'align' => 'E', - 'size' => 600, - ), ), - $commit->getLocalName()); + $summary); - $links[$identifier] = $commit_link; + $commit_date = phutil_tag( + 'a', + array( + 'href' => $commit->getURI(), + ), + $date); + + $links[$identifier]['link'] = $commit_link; + $links[$identifier]['date'] = $commit_date; } return $links; @@ -1850,19 +1780,10 @@ final class DiffusionBrowseController extends DiffusionController { foreach ($revisions as $revision) { $revision_id = $revision->getID(); - - $tooltip = $this->renderRevisionTooltip($revision, $handles); - - $revision_link = javelin_tag( + $revision_link = phutil_tag( 'a', array( 'href' => '/'.$revision->getMonogram(), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $tooltip, - 'align' => 'E', - 'size' => 600, - ), ), $revision->getMonogram()); @@ -1902,9 +1823,11 @@ final class DiffusionBrowseController extends DiffusionController { // show the user an error if we can't, rather than making them click // through to hit an error. - $header = id(new PHUIHeaderView()) - ->setHeader(basename($this->getDiffusionRequest()->getPath())) - ->setHeaderIcon('fa-archive'); + $title = basename($this->getDiffusionRequest()->getPath()); + $icon = 'fa-archive'; + $drequest = $this->getDiffusionRequest(); + $this->buildActionButtons($drequest); + $header = $this->buildPanelHeaderView($title, $icon); $severity = PHUIInfoView::SEVERITY_NOTICE; @@ -1916,18 +1839,18 @@ final class DiffusionBrowseController extends DiffusionController { try { $file = $this->loadGitLFSFile($ref); $data = $this->renderGitLFSButton(); - $header->addActionLink($data); } catch (Exception $ex) { $severity = PHUIInfoView::SEVERITY_ERROR; $messages[] = pht('The data for this file could not be loaded.'); } - $raw = $this->renderFileButton(null, pht('View Raw LFS Pointer')); - $header->addActionLink($raw); + $this->corpusButtons[] = $this->renderFileButton( + null, pht('View Raw LFS Pointer')); $corpus = id(new PHUIObjectBoxView()) ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addClass('diffusion-mobile-view') ->setCollapsed(true); if ($messages) { @@ -2012,6 +1935,7 @@ final class DiffusionBrowseController extends DiffusionController { return id(new PHUIObjectBoxView()) ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addClass('diffusion-mobile-view') ->setTable($history_table); } diff --git a/src/applications/diffusion/controller/DiffusionCloneController.php b/src/applications/diffusion/controller/DiffusionCloneController.php new file mode 100644 index 0000000000..8bc4faa1a2 --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionCloneController.php @@ -0,0 +1,122 @@ +getViewer(); + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $display_never = PhabricatorRepositoryURI::DISPLAY_NEVER; + $warning = null; + + $uris = $repository->getURIs(); + foreach ($uris as $uri) { + if ($uri->getIsDisabled()) { + continue; + } + + if ($uri->getEffectiveDisplayType() == $display_never) { + continue; + } + + if ($repository->isSVN()) { + $label = phutil_tag_div('diffusion-clone-label', pht('Checkout')); + } else { + $label = phutil_tag_div('diffusion-clone-label', pht('Clone')); + } + + $view->addProperty( + $label, + $this->renderCloneURI($repository, $uri)); + } + + if (!$view->hasAnyProperties()) { + $view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild(pht('Repository has no URIs set.')); + } + + $info = null; + + // Try to load alternatives. This may fail for repositories which have not + // cloned yet. If it does, just ignore it and continue. + try { + $alternatives = $drequest->getRefAlternatives(); + } catch (ConduitClientException $ex) { + $alternatives = array(); + } + + if ($alternatives) { + $message = array( + pht( + 'The ref "%s" is ambiguous in this repository.', + $drequest->getBranch()), + ' ', + phutil_tag( + 'a', + array( + 'href' => $drequest->generateURI( + array( + 'action' => 'refs', + )), + ), + pht('View Alternatives')), + ); + + $messages = array($message); + + $warning = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors(array($message)); + } + + $cancel_uri = $drequest->generateURI( + array( + 'action' => 'branch', + 'path' => '/', + )); + + return $this->newDialog() + ->setTitle(pht('Clone Repository')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->addCancelButton($cancel_uri, pht('Close')) + ->appendChild(array($view, $warning)); + } + + private function renderCloneURI( + PhabricatorRepository $repository, + PhabricatorRepositoryURI $uri) { + + if ($repository->isSVN()) { + $display = csprintf( + 'svn checkout %R %R', + (string)$uri->getDisplayURI(), + $repository->getCloneName()); + } else { + $display = csprintf('%R', (string)$uri->getDisplayURI()); + } + + $display = (string)$display; + $viewer = $this->getViewer(); + + return id(new DiffusionCloneURIView()) + ->setViewer($viewer) + ->setRepository($repository) + ->setRepositoryURI($uri) + ->setDisplayURI($display); + } + +} diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 10677b762f..554ad64e5e 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -720,8 +720,6 @@ final class DiffusionCommitController extends DiffusionController { $request = $this->getRequest(); $viewer = $request->getUser(); - Javelin::initBehavior('differential-keyboard-navigation'); - // TODO: This is pretty awkward, unify the CSS between Diffusion and // Differential better. require_celerity_resource('differential-core-view-css'); diff --git a/src/applications/diffusion/controller/DiffusionCompareController.php b/src/applications/diffusion/controller/DiffusionCompareController.php index a3104e6da4..2361c066dd 100644 --- a/src/applications/diffusion/controller/DiffusionCompareController.php +++ b/src/applications/diffusion/controller/DiffusionCompareController.php @@ -15,6 +15,7 @@ final class DiffusionCompareController extends DiffusionController { $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); + require_celerity_resource('diffusion-css'); if (!$repository->supportsBranchComparison()) { return $this->newDialog() @@ -315,6 +316,7 @@ final class DiffusionCompareController extends DiffusionController { ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($history_table) + ->addClass('diffusion-mobile-view') ->setPager($pager); } diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index a018eb3dbb..7ae67c2b5d 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -44,6 +44,7 @@ abstract class DiffusionController extends PhabricatorController { private function loadContext(array $options) { $request = $this->getRequest(); $viewer = $this->getViewer(); + require_celerity_resource('diffusion-repository-css'); $identifier = $this->getRepositoryIdentifierFromRequest($request); @@ -122,10 +123,10 @@ abstract class DiffusionController extends PhabricatorController { private function buildCrumbList(array $spec = array()) { $spec = $spec + array( - 'commit' => null, - 'tags' => null, - 'branches' => null, - 'view' => null, + 'commit' => null, + 'tags' => null, + 'branches' => null, + 'view' => null, ); $crumb_list = array(); @@ -147,7 +148,7 @@ abstract class DiffusionController extends PhabricatorController { if (!$spec['commit'] && !$spec['tags'] && !$spec['branches']) { $branch_name = $drequest->getBranch(); - if ($branch_name) { + if (strlen($branch_name)) { $repository_name .= ' ('.$branch_name.')'; } } @@ -204,6 +205,9 @@ abstract class DiffusionController extends PhabricatorController { case 'history': $view_name = pht('History'); break; + case 'graph': + $view_name = pht('Graph'); + break; case 'browse': $view_name = pht('Browse'); break; @@ -311,7 +315,7 @@ abstract class DiffusionController extends PhabricatorController { protected function renderStatusMessage($title, $body) { return id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setTitle($title) ->setFlush(true) ->appendChild($body); @@ -332,7 +336,40 @@ abstract class DiffusionController extends PhabricatorController { $tag = id(new PHUITagView()) ->setName($commit) - ->setShade('indigo') + ->setColor(PHUITagView::COLOR_INDIGO) + ->setBorder(PHUITagView::BORDER_NONE) + ->setType(PHUITagView::TYPE_SHADE); + + return $tag; + } + + protected function renderBranchTag(DiffusionRequest $drequest) { + $branch = $drequest->getBranch(); + $branch = id(new PhutilUTF8StringTruncator()) + ->setMaximumGlyphs(24) + ->truncateString($branch); + + $tag = id(new PHUITagView()) + ->setName($branch) + ->setColor(PHUITagView::COLOR_INDIGO) + ->setBorder(PHUITagView::BORDER_NONE) + ->setType(PHUITagView::TYPE_OUTLINE) + ->addClass('diffusion-header-branch-tag'); + + return $tag; + } + + protected function renderSymbolicCommit(DiffusionRequest $drequest) { + $symbolic_tag = $drequest->getSymbolicCommit(); + $symbolic_tag = id(new PhutilUTF8StringTruncator()) + ->setMaximumGlyphs(24) + ->truncateString($symbolic_tag); + + $tag = id(new PHUITagView()) + ->setName($symbolic_tag) + ->setIcon('fa-tag') + ->setColor(PHUITagView::COLOR_INDIGO) + ->setBorder(PHUITagView::BORDER_NONE) ->setType(PHUITagView::TYPE_SHADE); return $tag; @@ -405,4 +442,126 @@ abstract class DiffusionController extends PhabricatorController { ->setContent($readme_corpus); } + protected function renderSearchForm($path = '/') { + $drequest = $this->getDiffusionRequest(); + $viewer = $this->getViewer(); + switch ($drequest->getRepository()->getVersionControlSystem()) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + return null; + } + + $search_term = $this->getRequest()->getStr('grep'); + require_celerity_resource('diffusion-icons-css'); + require_celerity_resource('diffusion-css'); + + $href = $drequest->generateURI(array( + 'action' => 'browse', + 'path' => $path, + )); + + $bar = javelin_tag( + 'input', + array( + 'type' => 'text', + 'id' => 'diffusion-search-input', + 'name' => 'grep', + 'class' => 'diffusion-search-input', + 'sigil' => 'diffusion-search-input', + 'placeholder' => pht('Pattern Search'), + 'value' => $search_term, + )); + + $form = phabricator_form( + $viewer, + array( + 'method' => 'GET', + 'action' => $href, + 'sigil' => 'diffusion-search-form', + 'class' => 'diffusion-search-form', + 'id' => 'diffusion-search-form', + ), + array( + $bar, + )); + + $form_view = phutil_tag( + 'div', + array( + 'class' => 'diffusion-search-form-view', + ), + $form); + + return $form_view; + } + + protected function buildTabsView($key) { + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $view = new PHUIListView(); + + $view->addMenuItem( + id(new PHUIListItemView()) + ->setKey('code') + ->setName(pht('Code')) + ->setIcon('fa-code') + ->setHref($drequest->generateURI( + array( + 'action' => 'branch', + 'path' => '/', + ))) + ->setSelected($key == 'code')); + + if (!$repository->isSVN()) { + $view->addMenuItem( + id(new PHUIListItemView()) + ->setKey('branch') + ->setName(pht('Branches')) + ->setIcon('fa-code-fork') + ->setHref($drequest->generateURI( + array( + 'action' => 'branches', + ))) + ->setSelected($key == 'branch')); + } + + if (!$repository->isSVN()) { + $view->addMenuItem( + id(new PHUIListItemView()) + ->setKey('tags') + ->setName(pht('Tags')) + ->setIcon('fa-tags') + ->setHref($drequest->generateURI( + array( + 'action' => 'tags', + ))) + ->setSelected($key == 'tags')); + } + + $view->addMenuItem( + id(new PHUIListItemView()) + ->setKey('history') + ->setName(pht('History')) + ->setIcon('fa-history') + ->setHref($drequest->generateURI( + array( + 'action' => 'history', + ))) + ->setSelected($key == 'history')); + + $view->addMenuItem( + id(new PHUIListItemView()) + ->setKey('graph') + ->setName(pht('Graph')) + ->setIcon('fa-code-fork') + ->setHref($drequest->generateURI( + array( + 'action' => 'graph', + ))) + ->setSelected($key == 'graph')); + + return $view; + + } + } diff --git a/src/applications/diffusion/controller/DiffusionGraphController.php b/src/applications/diffusion/controller/DiffusionGraphController.php new file mode 100644 index 0000000000..096204ac6d --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionGraphController.php @@ -0,0 +1,111 @@ +loadDiffusionContext(); + if ($response) { + return $response; + } + require_celerity_resource('diffusion-css'); + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); + + $params = array( + 'commit' => $drequest->getCommit(), + 'path' => $drequest->getPath(), + 'offset' => $pager->getOffset(), + 'limit' => $pager->getPageSize() + 1, + ); + + $history_results = $this->callConduitWithDiffusionRequest( + 'diffusion.historyquery', + $params); + $history = DiffusionPathChange::newFromConduit( + $history_results['pathChanges']); + + $history = $pager->sliceResults($history); + + $graph = id(new DiffusionHistoryTableView()) + ->setViewer($viewer) + ->setDiffusionRequest($drequest) + ->setHistory($history); + + $graph->loadRevisions(); + $show_graph = !strlen($drequest->getPath()); + if ($show_graph) { + $graph->setParents($history_results['parents']); + $graph->setIsHead(!$pager->getOffset()); + $graph->setIsTail(!$pager->getHasMorePages()); + } + + $header = $this->buildHeader($drequest); + + $crumbs = $this->buildCrumbs( + array( + 'branch' => true, + 'path' => true, + 'view' => 'graph', + )); + $crumbs->setBorder(true); + + $title = array( + pht('Graph'), + $repository->getDisplayName(), + ); + + $graph_view = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('History Graph')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($graph) + ->addClass('diffusion-mobile-view') + ->setPager($pager); + + $tabs = $this->buildTabsView('graph'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setTabs($tabs) + ->setFooter($graph_view); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } + + private function buildHeader(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); + $repository = $drequest->getRepository(); + + $no_path = !strlen($drequest->getPath()); + if ($no_path) { + $header_text = pht('Graph'); + } else { + $header_text = $this->renderPathLinks($drequest, $mode = 'history'); + } + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setHeader($header_text) + ->setHeaderIcon('fa-code-fork'); + + if (!$repository->isSVN()) { + $branch_tag = $this->renderBranchTag($drequest); + $header->addTag($branch_tag); + } + + return $header; + + } + +} diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index 1a29a4263a..f50e73295f 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -11,6 +11,7 @@ final class DiffusionHistoryController extends DiffusionController { if ($response) { return $response; } + require_celerity_resource('diffusion-css'); $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); @@ -26,11 +27,6 @@ final class DiffusionHistoryController extends DiffusionController { 'limit' => $pager->getPageSize() + 1, ); - if (!$request->getBool('copies')) { - $params['needDirectChanges'] = true; - $params['needChildChanges'] = true; - } - $history_results = $this->callConduitWithDiffusionRequest( 'diffusion.historyquery', $params); @@ -39,27 +35,12 @@ final class DiffusionHistoryController extends DiffusionController { $history = $pager->sliceResults($history); - $show_graph = !strlen($drequest->getPath()); - $history_table = id(new DiffusionHistoryTableView()) - ->setUser($request->getUser()) + $history_list = id(new DiffusionHistoryListView()) + ->setViewer($viewer) ->setDiffusionRequest($drequest) ->setHistory($history); - $history_table->loadRevisions(); - - if ($show_graph) { - $history_table->setParents($history_results['parents']); - $history_table->setIsHead(!$pager->getOffset()); - $history_table->setIsTail(!$pager->getHasMorePages()); - } - - $history_header = $this->buildHistoryHeader($drequest); - $history_panel = id(new PHUIObjectBoxView()) - ->setHeader($history_header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($history_table) - ->setPager($pager); - + $history_list->loadRevisions(); $header = $this->buildHeader($drequest); $crumbs = $this->buildCrumbs( @@ -70,83 +51,60 @@ final class DiffusionHistoryController extends DiffusionController { )); $crumbs->setBorder(true); + $title = array( + pht('History'), + $repository->getDisplayName(), + ); + + $pager = id(new PHUIBoxView()) + ->addClass('mlb') + ->appendChild($pager); + + $tabs = $this->buildTabsView('history'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setTabs($tabs) ->setFooter(array( - $history_panel, + $history_list, + $pager, )); return $this->newPage() - ->setTitle( - array( - pht('History'), - $repository->getDisplayName(), - )) + ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild( - array( - $view, - )); + ->appendChild($view) + ->addClass('diffusion-history-view'); } private function buildHeader(DiffusionRequest $drequest) { $viewer = $this->getViewer(); + $repository = $drequest->getRepository(); - $tag = $this->renderCommitHashTag($drequest); + $no_path = !strlen($drequest->getPath()); + if ($no_path) { + $header_text = pht('History'); + } else { + $header_text = $this->renderPathLinks($drequest, $mode = 'history'); + } $header = id(new PHUIHeaderView()) ->setUser($viewer) - ->setPolicyObject($drequest->getRepository()) - ->addTag($tag) - ->setHeader($this->renderPathLinks($drequest, $mode = 'history')) + ->setHeader($header_text) ->setHeaderIcon('fa-clock-o'); - return $header; - - } - - private function buildHistoryHeader(DiffusionRequest $drequest) { - $viewer = $this->getViewer(); - - $browse_uri = $drequest->generateURI( - array( - 'action' => 'browse', - )); - - $browse_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Browse')) - ->setHref($browse_uri) - ->setIcon('fa-files-o'); - - // TODO: Sometimes we do have a change view, we need to look at the most - // recent history entry to figure it out. - - $request = $this->getRequest(); - if ($request->getBool('copies')) { - $branch_name = pht('Hide Copies/Branches'); - $branch_uri = $request->getRequestURI() - ->alter('offset', null) - ->alter('copies', null); - } else { - $branch_name = pht('Show Copies/Branches'); - $branch_uri = $request->getRequestURI() - ->alter('offset', null) - ->alter('copies', true); + if (!$repository->isSVN()) { + $branch_tag = $this->renderBranchTag($drequest); + $header->addTag($branch_tag); } - $branch_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText($branch_name) - ->setIcon('fa-code-fork') - ->setHref($branch_uri); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('History')) - ->addActionLink($browse_button) - ->addActionLink($branch_button); + if ($drequest->getSymbolicCommit()) { + $symbolic_tag = $this->renderSymbolicCommit($drequest); + $header->addTag($symbolic_tag); + } return $header; + } } diff --git a/src/applications/diffusion/controller/DiffusionLastModifiedController.php b/src/applications/diffusion/controller/DiffusionLastModifiedController.php index faf9457c15..945f8a58b5 100644 --- a/src/applications/diffusion/controller/DiffusionLastModifiedController.php +++ b/src/applications/diffusion/controller/DiffusionLastModifiedController.php @@ -132,7 +132,11 @@ final class DiffusionLastModifiedController extends DiffusionController { } } - $details = AphrontTableView::renderSingleDisplayLine($data->getSummary()); + $details = DiffusionView::linkDetail( + $drequest->getRepository(), + $commit->getCommitIdentifier(), + $data->getSummary()); + $details = AphrontTableView::renderSingleDisplayLine($details); } else { $author = ''; $details = ''; diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php index 980dc8d27c..af3ed571ce 100644 --- a/src/applications/diffusion/controller/DiffusionLintController.php +++ b/src/applications/diffusion/controller/DiffusionLintController.php @@ -167,6 +167,7 @@ final class DiffusionLintController extends DiffusionController { 'path' => true, 'view' => 'lint', )); + $crumbs->setBorder(true); if ($drequest) { $title[] = $drequest->getRepository()->getDisplayName(); @@ -178,7 +179,7 @@ final class DiffusionLintController extends DiffusionController { $branch = $drequest->loadBranch(); $header = id(new PHUIHeaderView()) - ->setHeader($this->renderPathLinks($drequest, 'lint')) + ->setHeader(pht('Lint: %s', $this->renderPathLinks($drequest, 'lint'))) ->setUser($viewer) ->setHeaderIcon('fa-code'); $actions = $this->buildActionView($drequest); @@ -465,6 +466,7 @@ final class DiffusionLintController extends DiffusionController { 'path' => true, 'view' => 'lint', )); + $crumbs->setBorder(true); $header = id(new PHUIHeaderView()) ->setHeader(pht('Lint: %s', $drequest->getRepository()->getDisplayName())) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 91b33bcaaa..f64b72e1f8 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -4,7 +4,7 @@ final class DiffusionRepositoryController extends DiffusionController { private $historyFuture; private $browseFuture; - private $tagFuture; + private $branchButton = null; private $branchFuture; public function shouldAllowPublic() { @@ -17,6 +17,8 @@ final class DiffusionRepositoryController extends DiffusionController { return $response; } + require_celerity_resource('diffusion-css'); + $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); @@ -25,8 +27,7 @@ final class DiffusionRepositoryController extends DiffusionController { $crumbs->setBorder(true); $header = $this->buildHeaderView($repository); - $curtain = $this->buildCurtain($repository); - $property_table = $this->buildPropertiesTable($repository); + $actions = $this->buildActionList($repository); $description = $this->buildDescriptionView($repository); $locate_file = $this->buildLocateFile(); @@ -50,15 +51,25 @@ final class DiffusionRepositoryController extends DiffusionController { // This is a valid branch, so we necessarily have some content. $page_has_content = true; } else { - $empty_title = pht('No Such Branch'); - $empty_message = pht( - 'There is no branch named "%s" in this repository.', - $drequest->getBranch()); + $default = $repository->getDefaultBranch(); + if ($default != $drequest->getBranch()) { + $empty_title = pht('No Such Branch'); + $empty_message = pht( + 'There is no branch named "%s" in this repository.', + $drequest->getBranch()); + } else { + $empty_title = pht('No Default Branch'); + $empty_message = pht( + 'This repository is configured with default branch "%s" but '. + 'there is no branch with that name in this repository.', + $default); + } } } // If we didn't find any branches, check if there are any commits at all. // This can tailor the message for empty repositories. + $any_commit = null; if (!$page_has_content) { $any_commit = id(new DiffusionCommitQuery()) ->setViewer($viewer) @@ -78,21 +89,61 @@ final class DiffusionRepositoryController extends DiffusionController { if ($page_has_content) { $content = $this->buildNormalContent($drequest); } else { + // If we have a commit somewhere, find branches. + // TODO: Evan will replace + // $this->buildNormalContent($drequest); $content = id(new PHUIInfoView()) ->setTitle($empty_title) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors(array($empty_message)); } + $tabs = $this->buildTabsView('code'); + + $clone_uri = $drequest->generateURI( + array( + 'action' => 'clone', + )); + + if ($repository->isSVN()) { + $clone_text = pht('Checkout'); + } else { + $clone_text = pht('Clone'); + } + + $actions_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Actions')) + ->setIcon('fa-bars') + ->addClass('mmr') + ->setColor(PHUIButtonView::GREY) + ->setDropdown(true) + ->setDropdownMenu($actions); + + $clone_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText($clone_text) + ->setColor(PHUIButtonView::GREEN) + ->setIcon('fa-download') + ->setWorkflow(true) + ->setHref($clone_uri); + + $bar = id(new PHUILeftRightView()) + ->setLeft($locate_file) + ->setRight(array($this->branchButton, $actions_button, $clone_button)) + ->addClass('diffusion-action-bar'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setCurtain($curtain) - ->setMainColumn(array( - $property_table, + ->setFooter(array( + $bar, $description, - $locate_file, - )) - ->setFooter($content); + $content, + )); + + if ($page_has_content) { + $view->setTabs($tabs); + } return $this->newPage() ->setTitle( @@ -114,6 +165,7 @@ final class DiffusionRepositoryController extends DiffusionController { $commit = $drequest->getCommit(); $path = $drequest->getPath(); + $futures = array(); $this->historyFuture = $this->callConduitMethod( 'diffusion.historyquery', array( @@ -122,6 +174,7 @@ final class DiffusionRepositoryController extends DiffusionController { 'offset' => 0, 'limit' => 15, )); + $futures[] = $this->historyFuture; $browse_pager = id(new PHUIPagerView()) ->readFromRequest($request); @@ -133,17 +186,7 @@ final class DiffusionRepositoryController extends DiffusionController { 'path' => $path, 'limit' => $browse_pager->getPageSize() + 1, )); - - if ($this->needTagFuture()) { - $tag_limit = $this->getTagLimit(); - $this->tagFuture = $this->callConduitMethod( - 'diffusion.tagsquery', - array( - // On the home page, we want to find tags on any branch. - 'commit' => null, - 'limit' => $tag_limit + 1, - )); - } + $futures[] = $this->browseFuture; if ($this->needBranchFuture()) { $branch_limit = $this->getBranchLimit(); @@ -153,14 +196,9 @@ final class DiffusionRepositoryController extends DiffusionController { 'closed' => false, 'limit' => $branch_limit + 1, )); + $futures[] = $this->branchFuture; } - $futures = array( - $this->historyFuture, - $this->browseFuture, - $this->tagFuture, - $this->branchFuture, - ); $futures = array_filter($futures); $futures = new FutureIterator($futures); foreach ($futures as $future) { @@ -241,18 +279,14 @@ final class DiffusionRepositoryController extends DiffusionController { $history, $history_exception); - try { - $content[] = $this->buildTagListTable($drequest); - } catch (Exception $ex) { - if (!$repository->isImporting()) { - $content[] = $this->renderStatusMessage( - pht('Unable to Load Tags'), - $ex->getMessage()); - } + if ($readme) { + $content[] = $readme; } + try { - $content[] = $this->buildBranchListTable($drequest); + $branch_button = $this->buildBranchList($drequest); + $this->branchButton = $branch_button; } catch (Exception $ex) { if (!$repository->isImporting()) { $content[] = $this->renderStatusMessage( @@ -261,20 +295,23 @@ final class DiffusionRepositoryController extends DiffusionController { } } - if ($readme) { - $content[] = $readme; - } - return $content; } private function buildHeaderView(PhabricatorRepository $repository) { $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $search = $this->renderSearchForm(); + $header = id(new PHUIHeaderView()) ->setHeader($repository->getName()) ->setUser($viewer) ->setPolicyObject($repository) - ->setHeaderIcon('fa-code'); + ->setProfileHeader(true) + ->setImage($repository->getProfileImageURI()) + ->setImageEditURL('/diffusion/picture/'.$repository->getID().'/') + ->addActionItem($search) + ->addClass('diffusion-profile-header'); if (!$repository->isTracked()) { $header->setStatus('fa-ban', 'dark', pht('Inactive')); @@ -289,16 +326,26 @@ final class DiffusionRepositoryController extends DiffusionController { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } + if (!$repository->isSVN()) { + $default = $repository->getDefaultBranch(); + if ($default != $drequest->getBranch()) { + $branch_tag = $this->renderBranchTag($drequest); + $header->addTag($branch_tag); + } + } + return $header; } - private function buildCurtain(PhabricatorRepository $repository) { + private function buildActionList(PhabricatorRepository $repository) { $viewer = $this->getViewer(); $edit_uri = $repository->getPathURI('manage/'); - $curtain = $this->newCurtainView($repository); + $action_view = id(new PhabricatorActionListView()) + ->setUser($viewer) + ->setObject($repository); - $curtain->addAction( + $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('Manage Repository')) ->setIcon('fa-cogs') @@ -308,14 +355,14 @@ final class DiffusionRepositoryController extends DiffusionController { $push_uri = $this->getApplicationURI( 'pushlog/?repositories='.$repository->getMonogram()); - $curtain->addAction( + $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('View Push Logs')) ->setIcon('fa-list-alt') ->setHref($push_uri)); } - return $curtain; + return $action_view; } private function buildDescriptionView(PhabricatorRepository $repository) { @@ -328,205 +375,12 @@ final class DiffusionRepositoryController extends DiffusionController { $description = new PHUIRemarkupView($viewer, $description); $view->addTextContent($description); return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Description')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($view); + ->appendChild($view) + ->addClass('diffusion-profile-description'); } return null; } - private function buildPropertiesTable(PhabricatorRepository $repository) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer); - - $display_never = PhabricatorRepositoryURI::DISPLAY_NEVER; - - $uris = $repository->getURIs(); - foreach ($uris as $uri) { - if ($uri->getIsDisabled()) { - continue; - } - - if ($uri->getEffectiveDisplayType() == $display_never) { - continue; - } - - if ($repository->isSVN()) { - $label = phutil_tag_div('diffusion-clone-label', pht('Checkout')); - } else { - $label = phutil_tag_div('diffusion-clone-label', pht('Clone')); - } - - $view->addProperty( - $label, - $this->renderCloneURI($repository, $uri)); - } - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($view); - - $info = null; - $drequest = $this->getDiffusionRequest(); - - // Try to load alternatives. This may fail for repositories which have not - // cloned yet. If it does, just ignore it and continue. - try { - $alternatives = $drequest->getRefAlternatives(); - } catch (ConduitClientException $ex) { - $alternatives = array(); - } - - if ($alternatives) { - $message = array( - pht( - 'The ref "%s" is ambiguous in this repository.', - $drequest->getBranch()), - ' ', - phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI( - array( - 'action' => 'refs', - )), - ), - pht('View Alternatives')), - ); - - $messages = array($message); - - $info = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING) - ->setErrors(array($message)); - - $box->setInfoView($info); - } - - - return $box; - } - - private function buildBranchListTable(DiffusionRequest $drequest) { - $viewer = $this->getViewer(); - - if (!$this->needBranchFuture()) { - return null; - } - - $branches = $this->branchFuture->resolve(); - if (!$branches) { - return null; - } - - $limit = $this->getBranchLimit(); - $more_branches = (count($branches) > $limit); - $branches = array_slice($branches, 0, $limit); - - $branches = DiffusionRepositoryRef::loadAllFromDictionaries($branches); - - $commits = id(new DiffusionCommitQuery()) - ->setViewer($viewer) - ->withIdentifiers(mpull($branches, 'getCommitIdentifier')) - ->withRepository($drequest->getRepository()) - ->execute(); - - $table = id(new DiffusionBranchTableView()) - ->setUser($viewer) - ->setDiffusionRequest($drequest) - ->setBranches($branches) - ->setCommits($commits); - - $panel = id(new PHUIObjectBoxView()) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); - $header = new PHUIHeaderView(); - $header->setHeader(pht('Branches')); - - if ($more_branches) { - $header->setSubheader(pht('Showing %d branches.', $limit)); - } - - $button = new PHUIButtonView(); - $button->setText(pht('Show All')); - $button->setTag('a'); - $button->setIcon('fa-code-fork'); - $button->setHref($drequest->generateURI( - array( - 'action' => 'branches', - ))); - - $header->addActionLink($button); - $panel->setHeader($header); - $panel->setTable($table); - - return $panel; - } - - private function buildTagListTable(DiffusionRequest $drequest) { - $viewer = $this->getViewer(); - $repository = $drequest->getRepository(); - - if (!$this->needTagFuture()) { - return null; - } - - $tags = $this->tagFuture->resolve(); - $tags = DiffusionRepositoryTag::newFromConduit($tags); - if (!$tags) { - return null; - } - - $tag_limit = $this->getTagLimit(); - $more_tags = (count($tags) > $tag_limit); - $tags = array_slice($tags, 0, $tag_limit); - - $commits = id(new DiffusionCommitQuery()) - ->setViewer($viewer) - ->withIdentifiers(mpull($tags, 'getCommitIdentifier')) - ->withRepository($repository) - ->needCommitData(true) - ->execute(); - - $view = id(new DiffusionTagListView()) - ->setUser($viewer) - ->setDiffusionRequest($drequest) - ->setTags($tags) - ->setCommits($commits); - - $phids = $view->getRequiredHandlePHIDs(); - $handles = $this->loadViewerHandles($phids); - $view->setHandles($handles); - - $panel = new PHUIObjectBoxView(); - $header = new PHUIHeaderView(); - $header->setHeader(pht('Tags')); - - if ($more_tags) { - $header->setSubheader( - pht('Showing the %d most recent tags.', $tag_limit)); - } - - $button = new PHUIButtonView(); - $button->setText(pht('Show All Tags')); - $button->setTag('a'); - $button->setIcon('fa-tag'); - $button->setHref($drequest->generateURI( - array( - 'action' => 'tags', - ))); - - $header->addActionLink($button); - - $panel->setHeader($header); - $panel->setTable($view); - $panel->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); - - return $panel; - } - private function buildHistoryTable( $history_results, $history, @@ -565,36 +419,85 @@ final class DiffusionRepositoryController extends DiffusionController { $history_table->setIsHead(true); - $icon = id(new PHUIIconView()) - ->setIcon('fa-list-alt'); - - $button = id(new PHUIButtonView()) - ->setText(pht('View History')) - ->setHref($drequest->generateURI( - array( - 'action' => 'history', - ))) - ->setTag('a') - ->setIcon($icon); - $panel = id(new PHUIObjectBoxView()) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addClass('diffusion-mobile-view'); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Recent Commits')) - ->addActionLink($button); + ->setHeader(pht('Recent Commits')); $panel->setHeader($header); $panel->setTable($history_table); return $panel; } + private function buildBranchList(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); + + if (!$this->needBranchFuture()) { + return null; + } + + $branches = $this->branchFuture->resolve(); + if (!$branches) { + return null; + } + + $limit = $this->getBranchLimit(); + $more_branches = (count($branches) > $limit); + $branches = array_slice($branches, 0, $limit); + + $branches = DiffusionRepositoryRef::loadAllFromDictionaries($branches); + + $actions = id(new PhabricatorActionListView()) + ->setViewer($viewer); + + foreach ($branches as $branch) { + $branch_uri = $drequest->generateURI( + array( + 'action' => 'browse', + 'branch' => $branch->getShortname(), + )); + $actions->addAction( + id(new PhabricatorActionView()) + ->setName($branch->getShortname()) + ->setIcon('fa-code-fork') + ->setHref($branch_uri)); + } + + if ($more_branches) { + $more_uri = $drequest->generateURI( + array( + 'action' => 'branches', + )); + $actions->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + $actions->addAction( + id(new PhabricatorActionView()) + ->setName(pht('See More Branches')) + ->setIcon('fa-external-link') + ->setHref($more_uri)); + } + + $button = id(new PHUIButtonView()) + ->setText(pht('Branch: %s', $drequest->getBranch())) + ->setTag('a') + ->addClass('mmr') + ->setIcon('fa-code-fork') + ->setColor(PHUIButtonView::GREY) + ->setDropdown(true) + ->setDropdownMenu($actions); + + return $button; + } + private function buildLocateFile() { $request = $this->getRequest(); $viewer = $request->getUser(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $locate_panel = null; + $form_box = null; if ($repository->canUsePathTree()) { Javelin::initBehavior( 'diffusion-locate-file', @@ -617,15 +520,12 @@ final class DiffusionRepositoryController extends DiffusionController { id(new AphrontFormTypeaheadControl()) ->setHardpointID('locate-control') ->setID('locate-input') - ->setLabel(pht('Locate File'))); + ->setPlaceholder(pht('Locate File'))); $form_box = id(new PHUIBoxView()) - ->appendChild($form->buildLayoutView()); - $locate_panel = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Locate File')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($form_box); + ->appendChild($form->buildLayoutView()) + ->addClass('diffusion-profile-locate'); } - return $locate_panel; + return $form_box; } private function buildBrowseTable( @@ -664,72 +564,28 @@ final class DiffusionRepositoryController extends DiffusionController { } $browse_uri = $drequest->generateURI(array('action' => 'browse')); - - $browse_panel = id(new PHUIObjectBoxView()) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); - $header = id(new PHUIHeaderView()) - ->setHeader($repository->getName()); - - $icon = id(new PHUIIconView()) - ->setIcon('fa-folder-open'); - - $button = new PHUIButtonView(); - $button->setText(pht('Browse Repository')); - $button->setTag('a'); - $button->setIcon($icon); - $button->setHref($browse_uri); - - $header->addActionLink($button); - $browse_panel->setHeader($header); - $browse_panel->setTable($browse_table); - $pager->setURI($browse_uri, 'offset'); - if ($pager->willShowPagingControls()) { - $browse_panel->setPager($pager); + $repository_name = $repository->getName(); + $branch_name = $drequest->getBranch(); + if (strlen($branch_name)) { + $repository_name .= ' ('.$branch_name.')'; } - return $browse_panel; - } + $header = phutil_tag( + 'a', + array( + 'href' => $browse_uri, + 'class' => 'diffusion-view-browse-header', + ), + $repository_name); - private function renderCloneURI( - PhabricatorRepository $repository, - PhabricatorRepositoryURI $uri) { - - if ($repository->isSVN()) { - $display = csprintf( - 'svn checkout %R %R', - (string)$uri->getDisplayURI(), - $repository->getCloneName()); - } else { - $display = csprintf('%R', (string)$uri->getDisplayURI()); - } - - $display = (string)$display; - $viewer = $this->getViewer(); - - return id(new DiffusionCloneURIView()) - ->setViewer($viewer) - ->setRepository($repository) - ->setRepositoryURI($uri) - ->setDisplayURI($display); - } - - private function needTagFuture() { - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - // No tags in SVN. - return false; - } - - return true; - } - - private function getTagLimit() { - return 15; + return id(new PHUIObjectBoxView()) + ->setHeaderText($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($browse_table) + ->addClass('diffusion-mobile-view') + ->setPager($pager); } private function needBranchFuture() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php index d3801fa206..bf3f389806 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php @@ -37,9 +37,6 @@ final class DiffusionRepositoryEditController $crumbs->setBorder(true); $title = pht('Choose Repository Type'); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Create Repository')) - ->setHeaderIcon('fa-plus-square'); $layout = id(new AphrontMultiColumnView()) ->setFluidLayout(true); @@ -47,8 +44,10 @@ final class DiffusionRepositoryEditController $create_uri = $request->getRequestURI(); foreach ($vcs_types as $vcs_key => $vcs_type) { + $image = idx($vcs_type, 'image'); + $image = PhabricatorFile::loadBuiltin($viewer, $image); $action = id(new PHUIActionPanelView()) - ->setIcon(idx($vcs_type, 'icon')) + ->setImage($image->getBestURI()) ->setHeader(idx($vcs_type, 'create.header')) ->setHref($create_uri->alter('vcs', $vcs_key)) ->setSubheader(idx($vcs_type, 'create.subheader')); @@ -62,9 +61,12 @@ final class DiffusionRepositoryEditController $observe_href = PhabricatorEnv::getDoclink( 'Diffusion User Guide: Existing Repositories'); + require_celerity_resource('diffusion-css'); + + $image = PhabricatorFile::loadBuiltin($viewer, 'repo/repo.png'); $hints->addColumn( id(new PHUIActionPanelView()) - ->setIcon('fa-book') + ->setImage($image->getBestURI()) ->setHeader(pht('Import or Observe an Existing Repository')) ->setHref($observe_href) ->setSubheader( @@ -72,12 +74,15 @@ final class DiffusionRepositoryEditController 'Review the documentation describing how to import or observe an '. 'existing repository.'))); + $layout = id(new PHUIBoxView()) + ->addClass('diffusion-create-repo') + ->appendChild($layout); + $view = id(new PHUITwoColumnView()) - ->setHeader($header) + ->setFixed(true) ->setFooter( array( $layout, - phutil_tag('br'), $hints, )); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php index 4cb6ffda67..dd95654302 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php @@ -13,7 +13,7 @@ final class DiffusionRepositoryEditUpdateController $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $panel_uri = id(new DiffusionRepositoryStatusManagementPanel()) + $panel_uri = id(new DiffusionRepositoryBasicsManagementPanel()) ->setRepository($repository) ->getPanelURI(); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryManageController.php b/src/applications/diffusion/controller/DiffusionRepositoryManageController.php index 8b0740c540..b04e72960b 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryManageController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryManageController.php @@ -22,4 +22,20 @@ abstract class DiffusionRepositoryManageController return $crumbs; } + public function newBox($title, $content, $action = null) { + $header = id(new PHUIHeaderView()) + ->setHeader($title); + + if ($action) { + $header->addActionItem($action); + } + + $view = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($content) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG); + + return $view; + } + } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php b/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php index 22c03145e0..7871c5192a 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php @@ -53,6 +53,10 @@ final class DiffusionRepositoryManagePanelsController $panel = $panels[$selected]; + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($panel->getManagementPanelLabel()); + $crumbs->setBorder(true); + $content = $panel->buildManagementPanelContent(); $title = array( @@ -60,41 +64,14 @@ final class DiffusionRepositoryManagePanelsController $repository->getDisplayName(), ); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($panel->getManagementPanelLabel()); - $crumbs->setBorder(true); - - $header_text = pht( - '%s: %s', - $repository->getDisplayName(), - $panel->getManagementPanelLabel()); - - $header = id(new PHUIHeaderView()) - ->setHeader($header_text) - ->setHeaderIcon('fa-pencil'); - if ($repository->isTracked()) { - $header->setStatus('fa-check', 'bluegrey', pht('Active')); - } else { - $header->setStatus('fa-ban', 'dark', pht('Inactive')); - } - - $header->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('View Repository')) - ->setHref($repository->getURI()) - ->setIcon('fa-code')); + $header = $this->buildHeaderView($repository->getDisplayName()); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setNavigation($nav) + ->setFixed(true) ->setMainColumn($content); - $curtain = $panel->buildManagementPanelCurtain(); - if ($curtain) { - $view->setCurtain($curtain); - } - return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) @@ -133,6 +110,46 @@ final class DiffusionRepositoryManagePanelsController return $nav; } + public function buildHeaderView($title) { + $viewer = $this->getViewer(); + + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setProfileHeader(true) + ->setHref($repository->getURI()) + ->setImage($repository->getProfileImageURI()); + + if ($repository->isTracked()) { + $header->setStatus('fa-check', 'bluegrey', pht('Active')); + } else { + $header->setStatus('fa-ban', 'dark', pht('Inactive')); + } + + $doc_href = PhabricatorEnv::getDoclink( + 'Diffusion User Guide: Managing Repositories'); + + $header->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('View Repository')) + ->setHref($repository->getURI()) + ->setIcon('fa-code') + ->setColor(PHUIButtonView::GREY)); + + $header->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-book') + ->setHref($doc_href) + ->setText(pht('Help')) + ->setColor(PHUIButtonView::GREY)); + + return $header; + } + public function newTimeline(PhabricatorRepository $repository) { $timeline = $this->buildTransactionTimeline( $repository, diff --git a/src/applications/diffusion/controller/DiffusionRepositoryProfilePictureController.php b/src/applications/diffusion/controller/DiffusionRepositoryProfilePictureController.php new file mode 100644 index 0000000000..cd4e3148c7 --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionRepositoryProfilePictureController.php @@ -0,0 +1,249 @@ +getViewer(); + $id = $request->getURIData('id'); + + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->needProfileImage(true) + ->needURIs(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + if (!$repository) { + return new Aphront404Response(); + } + + $supported_formats = PhabricatorFile::getTransformableImageFormats(); + $e_file = true; + $errors = array(); + $done_uri = $repository->getURI(); + + if ($request->isFormPost()) { + $phid = $request->getStr('phid'); + $is_default = false; + if ($phid == PhabricatorPHIDConstants::PHID_VOID) { + $phid = null; + $is_default = true; + } else if ($phid) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($phid)) + ->executeOne(); + } else { + if ($request->getFileExists('picture')) { + $file = PhabricatorFile::newFromPHPUpload( + $_FILES['picture'], + array( + 'authorPHID' => $viewer->getPHID(), + 'canCDN' => true, + )); + } else { + $e_file = pht('Required'); + $errors[] = pht( + 'You must choose a file when uploading a new profile picture.'); + } + } + + if (!$errors && !$is_default) { + if (!$file->isTransformableImage()) { + $e_file = pht('Not Supported'); + $errors[] = pht( + 'This server only supports these image formats: %s.', + implode(', ', $supported_formats)); + } else { + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE); + $xformed = $xform->executeTransform($file); + } + } + + if (!$errors) { + if ($is_default) { + $repository->setProfileImagePHID(null); + } else { + $repository->setProfileImagePHID($xformed->getPHID()); + $xformed->attachToObject($repository->getPHID()); + } + $repository->save(); + return id(new AphrontRedirectResponse())->setURI($done_uri); + } + } + + $title = pht('Edit Picture'); + + $form = id(new PHUIFormLayoutView()) + ->setUser($viewer); + + $default_image = PhabricatorFile::loadBuiltin( + $viewer, 'repo/code.png'); + + $images = array(); + + $current = $repository->getProfileImagePHID(); + $has_current = false; + if ($current) { + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($current)) + ->execute(); + if ($files) { + $file = head($files); + if ($file->isTransformableImage()) { + $has_current = true; + $images[$current] = array( + 'uri' => $file->getBestURI(), + 'tip' => pht('Current Picture'), + ); + } + } + } + + $builtins = array( + 'repo/repo-git.png', + 'repo/repo-svn.png', + 'repo/repo-hg.png', + 'repo/building.png', + 'repo/cloud.png', + 'repo/commit.png', + 'repo/database.png', + 'repo/desktop.png', + 'repo/gears.png', + 'repo/globe.png', + 'repo/locked.png', + 'repo/microchip.png', + 'repo/mobile.png', + 'repo/repo.png', + 'repo/servers.png', + ); + foreach ($builtins as $builtin) { + $file = PhabricatorFile::loadBuiltin($viewer, $builtin); + $images[$file->getPHID()] = array( + 'uri' => $file->getBestURI(), + 'tip' => pht('Builtin Image'), + ); + } + + $images[PhabricatorPHIDConstants::PHID_VOID] = array( + 'uri' => $default_image->getBestURI(), + 'tip' => pht('Default Picture'), + ); + + require_celerity_resource('people-profile-css'); + Javelin::initBehavior('phabricator-tooltips', array()); + + $buttons = array(); + foreach ($images as $phid => $spec) { + $style = null; + if (isset($spec['style'])) { + $style = $spec['style']; + } + $button = javelin_tag( + 'button', + array( + 'class' => 'button-grey profile-image-button', + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $spec['tip'], + 'size' => 300, + ), + ), + phutil_tag( + 'img', + array( + 'height' => 50, + 'width' => 50, + 'src' => $spec['uri'], + ))); + + $button = array( + phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => 'phid', + 'value' => $phid, + )), + $button, + ); + + $button = phabricator_form( + $viewer, + array( + 'class' => 'profile-image-form', + 'method' => 'POST', + ), + $button); + + $buttons[] = $button; + } + + if ($has_current) { + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Current Picture')) + ->setValue(array_shift($buttons))); + } + + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Use Picture')) + ->setValue($buttons)); + + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form); + + $upload_form = id(new AphrontFormView()) + ->setUser($viewer) + ->setEncType('multipart/form-data') + ->appendChild( + id(new AphrontFormFileControl()) + ->setName('picture') + ->setLabel(pht('Upload Picture')) + ->setError($e_file) + ->setCaption( + pht('Supported formats: %s', implode(', ', $supported_formats)))) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($done_uri) + ->setValue(pht('Upload Picture'))); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit Repository Picture')) + ->setHeaderIcon('fa-camera-retro'); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($repository->getName(), $repository->getURI()); + $crumbs->addTextCrumb(pht('Edit Picture')); + $crumbs->setBorder(true); + + $upload_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Upload New Picture')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($upload_form); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + $upload_box, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } +} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php b/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php index 61335d20d0..91ffbb473f 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php @@ -23,12 +23,29 @@ final class DiffusionRepositoryURIViewController return new Aphront404Response(); } + // For display, reload the URI by loading it through the repository. This + // may adjust builtin URIs for repository configuration, so we may end up + // with a different view of builtin URIs than we'd see if we loaded them + // directly from the database. See T12884. + $repository_with_uris = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->needURIs(true) + ->execute(); + + $repository_uris = $repository->getURIs(); + $repository_uris = mpull($repository_uris, null, 'getID'); + $uri = idx($repository_uris, $uri->getID()); + if (!$uri) { + return new Aphront404Response(); + } + $title = array( pht('URI'), $repository->getDisplayName(), ); $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setBorder(true); $crumbs->addTextCrumb( $repository->getDisplayName(), $repository->getURI()); diff --git a/src/applications/diffusion/controller/DiffusionTagListController.php b/src/applications/diffusion/controller/DiffusionTagListController.php index df3b356f5d..15b8dc93ae 100644 --- a/src/applications/diffusion/controller/DiffusionTagListController.php +++ b/src/applications/diffusion/controller/DiffusionTagListController.php @@ -11,6 +11,7 @@ final class DiffusionTagListController extends DiffusionController { if ($response) { return $response; } + require_celerity_resource('diffusion-css'); $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); @@ -50,6 +51,11 @@ final class DiffusionTagListController extends DiffusionController { ->setHeader(pht('Tags')) ->setHeaderIcon('fa-tags'); + if (!$repository->isSVN()) { + $branch_tag = $this->renderBranchTag($drequest); + $header->addTag($branch_tag); + } + if (!$tags) { $content = $this->renderStatusMessage( pht('No Tags'), @@ -64,17 +70,21 @@ final class DiffusionTagListController extends DiffusionController { ->needCommitData(true) ->execute(); - $view = id(new DiffusionTagListView()) + $tag_list = id(new DiffusionTagListView()) ->setTags($tags) ->setUser($viewer) ->setCommits($commits) ->setDiffusionRequest($drequest); - $phids = $view->getRequiredHandlePHIDs(); + $phids = $tag_list->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); - $view->setHandles($handles); + $tag_list->setHandles($handles); - $content = $view; + $content = id(new PHUIObjectBoxView()) + ->setHeaderText($repository->getDisplayName()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($tag_list) + ->setPager($pager); } $crumbs = $this->buildCrumbs( @@ -84,17 +94,12 @@ final class DiffusionTagListController extends DiffusionController { )); $crumbs->setBorder(true); - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($repository->getDisplayName()) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($view) - ->setPager($pager); + $tabs = $this->buildTabsView('tags'); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFooter(array( - $box, - )); + ->setTabs($tabs) + ->setFooter($content); return $this->newPage() ->setTitle( @@ -103,7 +108,8 @@ final class DiffusionTagListController extends DiffusionController { $repository->getDisplayName(), )) ->setCrumbs($crumbs) - ->appendChild($view); + ->appendChild($view) + ->addClass('diffusion-history-view'); } } diff --git a/src/applications/diffusion/herald/DiffusionCommitRevisionAcceptedHeraldField.php b/src/applications/diffusion/herald/DiffusionCommitRevisionAcceptedHeraldField.php index 810b58f2a1..a125b580eb 100644 --- a/src/applications/diffusion/herald/DiffusionCommitRevisionAcceptedHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionCommitRevisionAcceptedHeraldField.php @@ -19,36 +19,15 @@ final class DiffusionCommitRevisionAcceptedHeraldField return null; } - $status = $revision->getStatus(); - - switch ($status) { - case ArcanistDifferentialRevisionStatus::ACCEPTED: - return $revision->getPHID(); - case ArcanistDifferentialRevisionStatus::CLOSED: - if ($revision->hasRevisionProperty( - DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED)) { - - if ($revision->getProperty( - DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED)) { - return $revision->getPHID(); - } else { - return null; - } - } else { - // continue on to old-style precommitRevisionStatus - break; - } - default: - return null; + if ($revision->isAccepted()) { + return $revision->getPHID(); } - $data = $object->getCommitData(); - $status = $data->getCommitDetail('precommitRevisionStatus'); - - switch ($status) { - case ArcanistDifferentialRevisionStatus::ACCEPTED: - case ArcanistDifferentialRevisionStatus::CLOSED: + $was_accepted = DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED; + if ($revision->isPublished()) { + if ($revision->getProperty($was_accepted)) { return $revision->getPHID(); + } } return null; diff --git a/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptedHeraldField.php b/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptedHeraldField.php index e19878e627..63dac6baa6 100644 --- a/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptedHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptedHeraldField.php @@ -15,21 +15,19 @@ final class DiffusionPreCommitContentRevisionAcceptedHeraldField public function getHeraldFieldValue($object) { $revision = $this->getAdapter()->getRevision(); - if (!$revision) { return null; } - switch ($revision->getStatus()) { - case ArcanistDifferentialRevisionStatus::ACCEPTED: - return $revision->getPHID(); - case ArcanistDifferentialRevisionStatus::CLOSED: - if ($revision->getProperty( - DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED)) { + if ($revision->isAccepted()) { + return $revision->getPHID(); + } - return $revision->getPHID(); - } - break; + $was_accepted = DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED; + if ($revision->isPublished()) { + if ($revision->getProperty($was_accepted)) { + return $revision->getPHID(); + } } return null; diff --git a/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php index 1f47a68de3..a5805fd0cc 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryActionsManagementPanel.php @@ -14,20 +14,7 @@ final class DiffusionRepositoryActionsManagementPanel } public function getManagementPanelIcon() { - $repository = $this->getRepository(); - - $has_any = - $repository->getDetail('herald-disabled') || - $repository->getDetail('disable-autoclose'); - - // NOTE: Any value here really means something is disabled, so try to - // hint that a little bit with the icon. - - if ($has_any) { - return 'fa-comment-o'; - } else { - return 'fa-commenting grey'; - } + return 'fa-flash'; } protected function getEditEngineFieldKeys() { @@ -37,34 +24,12 @@ final class DiffusionRepositoryActionsManagementPanel ); } - protected function buildManagementPanelActions() { - $repository = $this->getRepository(); - $viewer = $this->getViewer(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $actions_uri = $this->getEditPageURI(); - - return array( - id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Actions')) - ->setHref($actions_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), - ); - } - public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $notify = $repository->getDetail('herald-disabled') ? pht('Off') @@ -78,7 +43,22 @@ final class DiffusionRepositoryActionsManagementPanel $autoclose = phutil_tag('em', array(), $autoclose); $view->addProperty(pht('Autoclose'), $autoclose); - return $this->newBox(pht('Actions'), $view); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $actions_uri = $this->getEditPageURI(); + + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-pencil') + ->setText(pht('Edit')) + ->setHref($actions_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit); + + return $this->newBox(pht('Actions'), $view, array($button)); } } diff --git a/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php index 14182a0444..22afd5a53b 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php @@ -27,62 +27,24 @@ final class DiffusionRepositoryAutomationManagementPanel public function getManagementPanelIcon() { $repository = $this->getRepository(); - if (!$repository->canPerformAutomation()) { - return 'fa-truck grey'; - } - $blueprint_phids = $repository->getAutomationBlueprintPHIDs(); - if (!$blueprint_phids) { - return 'fa-truck grey'; - } $is_authorized = DrydockAuthorizationQuery::isFullyAuthorized( $repository->getPHID(), $blueprint_phids); if (!$is_authorized) { - return 'fa-exclamation-triangle yellow'; + return 'fa-exclamation-triangle'; } return 'fa-truck'; } - protected function buildManagementPanelActions() { - $repository = $this->getRepository(); - $viewer = $this->getViewer(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $can_test = $can_edit && $repository->canPerformAutomation(); - - $automation_uri = $this->getEditPageURI(); - $test_uri = $repository->getPathURI('edit/testautomation/'); - - return array( - id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Automation')) - ->setHref($automation_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), - id(new PhabricatorActionView()) - ->setIcon('fa-gamepad') - ->setName(pht('Test Configuration')) - ->setWorkflow(true) - ->setDisabled(!$can_test) - ->setHref($test_uri), - ); - } - public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $blueprint_phids = $repository->getAutomationBlueprintPHIDs(); if (!$blueprint_phids) { @@ -96,7 +58,33 @@ final class DiffusionRepositoryAutomationManagementPanel $view->addProperty(pht('Automation'), $blueprint_view); - return $this->newBox(pht('Automation'), $view); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $can_test = $can_edit && $repository->canPerformAutomation(); + + $automation_uri = $this->getEditPageURI(); + $test_uri = $repository->getPathURI('edit/testautomation/'); + + $edit = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-pencil') + ->setText(pht('Edit')) + ->setHref($automation_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit); + + $test = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-gamepad') + ->setText(pht('Test Config')) + ->setWorkflow(true) + ->setDisabled(!$can_test) + ->setHref($test_uri); + + return $this->newBox(pht('Automation'), $view, array($edit, $test)); } } diff --git a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php index 45c2ca142a..6e527e81b5 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php @@ -14,13 +14,7 @@ final class DiffusionRepositoryBasicsManagementPanel } public function getManagementPanelIcon() { - $repository = $this->getRepository(); - - if (!$repository->isTracked()) { - return 'fa-ban indigo'; - } else { - return 'fa-code'; - } + return 'fa-code'; } protected function getEditEngineFieldKeys() { @@ -33,9 +27,11 @@ final class DiffusionRepositoryBasicsManagementPanel ); } - protected function buildManagementPanelActions() { + private function buildActionMenu() { $repository = $this->getRepository(); $viewer = $this->getViewer(); + $action_list = id(new PhabricatorActionListView()) + ->setViewer($viewer); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -49,65 +45,79 @@ final class DiffusionRepositoryBasicsManagementPanel $dangerous_uri = $repository->getPathURI('edit/dangerous/'); if ($repository->isTracked()) { - $activate_icon = 'fa-pause'; $activate_label = pht('Deactivate Repository'); } else { - $activate_icon = 'fa-play'; $activate_label = pht('Activate Repository'); } $should_dangerous = $repository->shouldAllowDangerousChanges(); if ($should_dangerous) { - $dangerous_icon = 'fa-shield'; $dangerous_name = pht('Prevent Dangerous Changes'); $can_dangerous = $can_edit; } else { - $dangerous_icon = 'fa-bullseye'; $dangerous_name = pht('Allow Dangerous Changes'); $can_dangerous = ($can_edit && $repository->canAllowDangerousChanges()); } - return array( + $action_list->addAction( id(new PhabricatorActionView()) - ->setIcon('fa-pencil') ->setName(pht('Edit Basic Information')) ->setHref($edit_uri) ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), + ->setWorkflow(!$can_edit)); + + $action_list->addAction( id(new PhabricatorActionView()) - ->setIcon('fa-text-width') ->setName(pht('Edit Text Encoding')) ->setHref($encoding_uri) ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), + ->setWorkflow(!$can_edit)); + + $action_list->addAction( id(new PhabricatorActionView()) - ->setIcon($dangerous_icon) ->setName($dangerous_name) ->setHref($dangerous_uri) ->setDisabled(!$can_dangerous) - ->setWorkflow(true), + ->setWorkflow(true)); + + $action_list->addAction( id(new PhabricatorActionView()) ->setHref($activate_uri) - ->setIcon($activate_icon) ->setName($activate_label) ->setDisabled(!$can_edit) - ->setWorkflow(true), + ->setWorkflow(true)); + + $action_list->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + + $action_list->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete Repository')) - ->setIcon('fa-times') ->setHref($delete_uri) + ->setColor(PhabricatorActionView::RED) ->setDisabled(true) - ->setWorkflow(true), - ); + ->setWorkflow(true)); + + return $action_list; } public function buildManagementPanelContent() { - $result = array(); - $basics = $this->newBox(pht('Repository Basics'), $this->buildBasics()); + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Actions')) + ->setHref('#') + ->setIcon('fa-bars') + ->addClass('phui-mobile-menu') + ->setDropdownMenu($this->buildActionMenu()); + + $basics = $this->buildBasics(); + $basics = $this->newBox(pht('Properties'), $basics, array($button)); $repository = $this->getRepository(); $is_new = $repository->isNewlyInitialized(); + $info_view = null; if ($is_new) { $messages = array(); @@ -131,18 +141,15 @@ final class DiffusionRepositoryBasicsManagementPanel $info_view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setErrors($messages); - - $basics->setInfoView($info_view); } - $result[] = $basics; - $description = $this->buildDescription(); if ($description) { - $result[] = $this->newBox(pht('Description'), $description); + $description = $this->newBox(pht('Description'), $description); } + $status = $this->buildStatus(); - return $result; + return array($info_view, $basics, $description, $status); } private function buildBasics() { @@ -150,8 +157,7 @@ final class DiffusionRepositoryBasicsManagementPanel $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $name = $repository->getName(); $view->addProperty(pht('Name'), $name); @@ -205,7 +211,7 @@ final class DiffusionRepositoryBasicsManagementPanel $view = id(new PHUIPropertyListView()) ->setViewer($viewer); if (!strlen($description)) { - $description = phutil_tag('em', array(), pht('No description provided.')); + return null; } else { $description = new PHUIRemarkupView($viewer, $description); } @@ -214,4 +220,471 @@ final class DiffusionRepositoryBasicsManagementPanel return $view; } + private function buildStatus() { + $repository = $this->getRepository(); + $viewer = $this->getViewer(); + $update_uri = $repository->getPathURI('edit/update/'); + + $view = id(new PHUIPropertyListView()) + ->setViewer($viewer); + + $view->addProperty( + pht('Update Frequency'), + $this->buildRepositoryUpdateInterval($repository)); + + $messages = $this->loadStatusMessages($repository); + + $status = $this->buildRepositoryStatus($repository, $messages); + $raw_error = $this->buildRepositoryRawError($repository, $messages); + + $view->addProperty(pht('Status'), $status); + if ($raw_error) { + $view->addSectionHeader(pht('Raw Error')); + $view->addTextContent($raw_error); + } + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-refresh') + ->setText(pht('Update Now')) + ->setWorkflow(true) + ->setDisabled(!$can_edit) + ->setHref($update_uri); + + return $this->newBox(pht('Status'), $view, array($button)); + } + + private function buildRepositoryUpdateInterval( + PhabricatorRepository $repository) { + + $smart_wait = $repository->loadUpdateInterval(); + + $doc_href = PhabricatorEnv::getDoclink( + 'Diffusion User Guide: Repository Updates'); + + return array( + phutil_format_relative_time_detailed($smart_wait), + " \xC2\xB7 ", + phutil_tag( + 'a', + array( + 'href' => $doc_href, + 'target' => '_blank', + ), + pht('Learn More')), + ); + } + + private function buildRepositoryStatus( + PhabricatorRepository $repository, + array $messages) { + + $viewer = $this->getViewer(); + $is_cluster = $repository->getAlmanacServicePHID(); + + $view = new PHUIStatusListView(); + + if ($repository->isTracked()) { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') + ->setTarget(pht('Repository Active'))); + } else { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'bluegrey') + ->setTarget(pht('Repository Inactive')) + ->setNote( + pht('Activate this repository to begin or resume import.'))); + return $view; + } + + $binaries = array(); + $svnlook_check = false; + switch ($repository->getVersionControlSystem()) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + $binaries[] = 'git'; + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + $binaries[] = 'svn'; + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: + $binaries[] = 'hg'; + break; + } + + if ($repository->isHosted()) { + $proto_https = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTPS; + $proto_http = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTP; + $can_http = $repository->canServeProtocol($proto_http, false) || + $repository->canServeProtocol($proto_https, false); + + if ($can_http) { + switch ($repository->getVersionControlSystem()) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + $binaries[] = 'git-http-backend'; + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + $binaries[] = 'svnserve'; + $binaries[] = 'svnadmin'; + $binaries[] = 'svnlook'; + $svnlook_check = true; + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: + $binaries[] = 'hg'; + break; + } + } + + + $proto_ssh = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH; + $can_ssh = $repository->canServeProtocol($proto_ssh, false); + + if ($can_ssh) { + switch ($repository->getVersionControlSystem()) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + $binaries[] = 'git-receive-pack'; + $binaries[] = 'git-upload-pack'; + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + $binaries[] = 'svnserve'; + $binaries[] = 'svnadmin'; + $binaries[] = 'svnlook'; + $svnlook_check = true; + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: + $binaries[] = 'hg'; + break; + } + } + } + + $binaries = array_unique($binaries); + if (!$is_cluster) { + // We're only checking for binaries if we aren't running with a cluster + // configuration. In theory, we could check for binaries on the + // repository host machine, but we'd need to make this more complicated + // to do that. + + foreach ($binaries as $binary) { + $where = Filesystem::resolveBinary($binary); + if (!$where) { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') + ->setTarget( + pht('Missing Binary %s', phutil_tag('tt', array(), $binary))) + ->setNote(pht( + "Unable to find this binary in the webserver's PATH. You may ". + "need to configure %s.", + $this->getEnvConfigLink()))); + } else { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') + ->setTarget( + pht('Found Binary %s', phutil_tag('tt', array(), $binary))) + ->setNote(phutil_tag('tt', array(), $where))); + } + } + + // This gets checked generically above. However, for svn commit hooks, we + // need this to be in environment.append-paths because subversion strips + // PATH. + if ($svnlook_check) { + $where = Filesystem::resolveBinary('svnlook'); + if ($where) { + $path = substr($where, 0, strlen($where) - strlen('svnlook')); + $dirs = PhabricatorEnv::getEnvConfig('environment.append-paths'); + $in_path = false; + foreach ($dirs as $dir) { + if (Filesystem::isDescendant($path, $dir)) { + $in_path = true; + break; + } + } + if (!$in_path) { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') + ->setTarget( + pht('Missing Binary %s', phutil_tag('tt', array(), $binary))) + ->setNote(pht( + 'Unable to find this binary in `%s`. '. + 'You need to configure %s and include %s.', + 'environment.append-paths', + $this->getEnvConfigLink(), + $path))); + } + } + } + } + + $doc_href = PhabricatorEnv::getDoclink('Managing Daemons with phd'); + + $daemon_instructions = pht( + 'Use %s to start daemons. See %s.', + phutil_tag('tt', array(), 'bin/phd start'), + phutil_tag( + 'a', + array( + 'href' => $doc_href, + ), + pht('Managing Daemons with phd'))); + + + $pull_daemon = id(new PhabricatorDaemonLogQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) + ->withDaemonClasses(array('PhabricatorRepositoryPullLocalDaemon')) + ->setLimit(1) + ->execute(); + + if ($pull_daemon) { + + // TODO: In a cluster environment, we need a daemon on this repository's + // host, specifically, and we aren't checking for that right now. This + // is a reasonable proxy for things being more-or-less correctly set up, + // though. + + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') + ->setTarget(pht('Pull Daemon Running'))); + } else { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') + ->setTarget(pht('Pull Daemon Not Running')) + ->setNote($daemon_instructions)); + } + + + $task_daemon = id(new PhabricatorDaemonLogQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) + ->withDaemonClasses(array('PhabricatorTaskmasterDaemon')) + ->setLimit(1) + ->execute(); + if ($task_daemon) { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') + ->setTarget(pht('Task Daemon Running'))); + } else { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') + ->setTarget(pht('Task Daemon Not Running')) + ->setNote($daemon_instructions)); + } + + + if ($is_cluster) { + // Just omit this status check for now in cluster environments. We + // could make a service call and pull it from the repository host + // eventually. + } else if ($repository->usesLocalWorkingCopy()) { + $local_parent = dirname($repository->getLocalPath()); + if (Filesystem::pathExists($local_parent)) { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') + ->setTarget(pht('Storage Directory OK')) + ->setNote(phutil_tag('tt', array(), $local_parent))); + } else { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') + ->setTarget(pht('No Storage Directory')) + ->setNote( + pht( + 'Storage directory %s does not exist, or is not readable by '. + 'the webserver. Create this directory or make it readable.', + phutil_tag('tt', array(), $local_parent)))); + return $view; + } + + $local_path = $repository->getLocalPath(); + $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_INIT); + if ($message) { + switch ($message->getStatusCode()) { + case PhabricatorRepositoryStatusMessage::CODE_ERROR: + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') + ->setTarget(pht('Initialization Error')) + ->setNote($message->getParameter('message'))); + return $view; + case PhabricatorRepositoryStatusMessage::CODE_OKAY: + if (Filesystem::pathExists($local_path)) { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') + ->setTarget(pht('Working Copy OK')) + ->setNote(phutil_tag('tt', array(), $local_path))); + } else { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') + ->setTarget(pht('Working Copy Error')) + ->setNote( + pht( + 'Working copy %s has been deleted, or is not '. + 'readable by the webserver. Make this directory '. + 'readable. If it has been deleted, the daemons should '. + 'restore it automatically.', + phutil_tag('tt', array(), $local_path)))); + return $view; + } + break; + default: + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green') + ->setTarget(pht('Initializing Working Copy')) + ->setNote(pht('Daemons are initializing the working copy.'))); + return $view; + } + } else { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange') + ->setTarget(pht('No Working Copy Yet')) + ->setNote( + pht('Waiting for daemons to build a working copy.'))); + return $view; + } + } + + $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH); + if ($message) { + switch ($message->getStatusCode()) { + case PhabricatorRepositoryStatusMessage::CODE_ERROR: + $message = $message->getParameter('message'); + + $suggestion = null; + if (preg_match('/Permission denied \(publickey\)./', $message)) { + $suggestion = pht( + 'Public Key Error: This error usually indicates that the '. + 'keypair you have configured does not have permission to '. + 'access the repository.'); + } + + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') + ->setTarget(pht('Update Error')) + ->setNote($suggestion)); + return $view; + case PhabricatorRepositoryStatusMessage::CODE_OKAY: + $ago = (PhabricatorTime::getNow() - $message->getEpoch()); + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') + ->setTarget(pht('Updates OK')) + ->setNote( + pht( + 'Last updated %s (%s ago).', + phabricator_datetime($message->getEpoch(), $viewer), + phutil_format_relative_time_detailed($ago)))); + break; + } + } else { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange') + ->setTarget(pht('Waiting For Update')) + ->setNote( + pht('Waiting for daemons to read updates.'))); + } + + if ($repository->isImporting()) { + $ratio = $repository->loadImportProgress(); + $percentage = sprintf('%.2f%%', 100 * $ratio); + + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green') + ->setTarget(pht('Importing')) + ->setNote( + pht('%s Complete', $percentage))); + } else { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') + ->setTarget(pht('Fully Imported'))); + } + + if (idx($messages, PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE)) { + $view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_UP, 'indigo') + ->setTarget(pht('Prioritized')) + ->setNote(pht('This repository will be updated soon!'))); + } + + return $view; + } + + private function buildRepositoryRawError( + PhabricatorRepository $repository, + array $messages) { + $viewer = $this->getViewer(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $raw_error = null; + + $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH); + if ($message) { + switch ($message->getStatusCode()) { + case PhabricatorRepositoryStatusMessage::CODE_ERROR: + $raw_error = $message->getParameter('message'); + break; + } + } + + if ($raw_error !== null) { + if (!$can_edit) { + $raw_message = pht( + 'You must be able to edit a repository to see raw error messages '. + 'because they sometimes disclose sensitive information.'); + $raw_message = phutil_tag('em', array(), $raw_message); + } else { + $raw_message = phutil_escape_html_newlines($raw_error); + } + } else { + $raw_message = null; + } + + return $raw_message; + } + + private function loadStatusMessages(PhabricatorRepository $repository) { + $messages = id(new PhabricatorRepositoryStatusMessage()) + ->loadAllWhere('repositoryID = %d', $repository->getID()); + $messages = mpull($messages, null, 'getStatusType'); + + return $messages; + } + + private function getEnvConfigLink() { + $config_href = '/config/edit/environment.append-paths/'; + return phutil_tag( + 'a', + array( + 'href' => $config_href, + ), + 'environment.append-paths'); + } + } diff --git a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php index b3e7926cfc..7c7f4bcf63 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php @@ -19,18 +19,7 @@ final class DiffusionRepositoryBranchesManagementPanel } public function getManagementPanelIcon() { - $repository = $this->getRepository(); - - $has_any = - $repository->getDetail('default-branch') || - $repository->getDetail('branch-filter') || - $repository->getDetail('close-commits-filter'); - - if ($has_any) { - return 'fa-code-fork'; - } else { - return 'fa-code-fork grey'; - } + return 'fa-code-fork'; } protected function getEditEngineFieldKeys() { @@ -41,34 +30,13 @@ final class DiffusionRepositoryBranchesManagementPanel ); } - protected function buildManagementPanelActions() { - $repository = $this->getRepository(); - $viewer = $this->getViewer(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $branches_uri = $this->getEditPageURI(); - - return array( - id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Branches')) - ->setHref($branches_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), - ); - } - public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); + $content = array(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $default_branch = nonempty( $repository->getHumanReadableDetail('default-branch'), @@ -84,13 +52,123 @@ final class DiffusionRepositoryBranchesManagementPanel $repository->getHumanReadableDetail('close-commits-filter', array()), phutil_tag('em', array(), pht('Autoclose On All Branches'))); + $autoclose_disabled = false; if ($repository->getDetail('disable-autoclose')) { - $autoclose_only = phutil_tag('em', array(), pht('Disabled')); + $autoclose_disabled = true; + $autoclose_only = + phutil_tag('em', array(), pht('Autoclose has been disabled')); } $view->addProperty(pht('Autoclose Only'), $autoclose_only); - return $this->newBox(pht('Branches'), $view); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $branches_uri = $this->getEditPageURI(); + + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-pencil') + ->setText(pht('Edit')) + ->setHref($branches_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit); + + $content[] = $this->newBox(pht('Branches'), $view, array($button)); + + // Branch Autoclose Table + if (!$repository->isImporting()) { + $request = $this->getRequest(); + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); + + $params = array( + 'offset' => $pager->getOffset(), + 'limit' => $pager->getPageSize() + 1, + 'repository' => $repository->getID(), + ); + + $branches = id(new ConduitCall('diffusion.branchquery', $params)) + ->setUser($viewer) + ->execute(); + $branches = DiffusionRepositoryRef::loadAllFromDictionaries($branches); + $branches = $pager->sliceResults($branches); + $can_close_branches = ($repository->isHg()); + + $rows = array(); + foreach ($branches as $branch) { + $branch_name = $branch->getShortName(); + $tracking = $repository->shouldTrackBranch($branch_name); + $autoclosing = $repository->shouldAutocloseBranch($branch_name); + + $default = $repository->getDefaultBranch(); + $icon = null; + if ($default == $branch->getShortName()) { + $icon = id(new PHUIIconView()) + ->setIcon('fa-code-fork'); + } + + $fields = $branch->getRawFields(); + $closed = idx($fields, 'closed'); + if ($closed) { + $status = pht('Closed'); + } else { + $status = pht('Open'); + } + + if ($autoclose_disabled) { + $autoclose_status = pht('Disabled (Repository)'); + } else { + $autoclose_status = pht('Off'); + } + + $rows[] = array( + $icon, + $branch_name, + $status, + $tracking ? pht('Tracking') : pht('Off'), + $autoclosing ? pht('Autoclose On') : $autoclose_status, + ); + } + $branch_table = new AphrontTableView($rows); + $branch_table->setHeaders( + array( + '', + pht('Branch'), + pht('Status'), + pht('Track'), + pht('Autoclose'), + )); + $branch_table->setColumnClasses( + array( + '', + 'pri', + 'narrow', + 'narrow', + 'wide', + )); + $branch_table->setColumnVisibility( + array( + true, + true, + $can_close_branches, + true, + true, + )); + + $box = $this->newBox(pht('Branch Status'), $branch_table); + $box->setPager($pager); + $content[] = $box; + } else { + $content[] = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild(pht('Branch status in unavailable while the repository '. + 'is still importing.')); + } + + return $content; } } diff --git a/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php deleted file mode 100644 index ca2c577b38..0000000000 --- a/src/applications/diffusion/management/DiffusionRepositoryDocumentationManagementPanel.php +++ /dev/null @@ -1,29 +0,0 @@ -newTimeline(); } - } diff --git a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php index 34047cdb89..282fa1b7b1 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryManagementPanel.php @@ -25,6 +25,10 @@ abstract class DiffusionRepositoryManagementPanel return $this->repository; } + final public function getRequest() { + return $this->controller->getRequest(); + } + final public function setController(PhabricatorController $controller) { $this->controller = $controller; return $this; @@ -51,43 +55,6 @@ abstract class DiffusionRepositoryManagementPanel return true; } - final protected function newActions() { - $actions = $this->buildManagementPanelActions(); - if (!$actions) { - return null; - } - - $viewer = $this->getViewer(); - - $action_list = id(new PhabricatorActionListView()) - ->setViewer($viewer); - - foreach ($actions as $action) { - $action_list->addAction($action); - } - - return $action_list; - } - - public function buildManagementPanelCurtain() { - // TODO: Delete or fix this, curtains always render in the left gutter - // at the moment. - return null; - - $actions = $this->newActions(); - if (!$actions) { - return null; - } - - $viewer = $this->getViewer(); - - $curtain = id(new PHUICurtainView()) - ->setViewer($viewer) - ->setActionList($actions); - - return $curtain; - } - public static function getAllPanels() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) @@ -96,11 +63,20 @@ abstract class DiffusionRepositoryManagementPanel ->execute(); } - final protected function newBox($header_text, $body) { - return id(new PHUIObjectBoxView()) - ->setHeaderText($header_text) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + final protected function newBox($header_text, $body, $button = array()) { + $header = id(new PHUIHeaderView()) + ->setHeader($header_text); + + foreach ($button as $link) { + $header->addActionLink($link); + } + + $view = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->appendChild($body); + + return $view; } final protected function newTimeline() { diff --git a/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php index 2c77da8518..3a8e054ce0 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php @@ -14,35 +14,7 @@ final class DiffusionRepositoryPoliciesManagementPanel } public function getManagementPanelIcon() { - $viewer = $this->getViewer(); - $repository = $this->getRepository(); - - $can_view = PhabricatorPolicyCapability::CAN_VIEW; - $can_edit = PhabricatorPolicyCapability::CAN_EDIT; - $can_push = DiffusionPushCapability::CAPABILITY; - - $actual_values = array( - 'spacePHID' => $repository->getSpacePHID(), - 'view' => $repository->getPolicy($can_view), - 'edit' => $repository->getPolicy($can_edit), - 'push' => $repository->getPolicy($can_push), - ); - - $default = PhabricatorRepository::initializeNewRepository( - $viewer); - - $default_values = array( - 'spacePHID' => $default->getSpacePHID(), - 'view' => $default->getPolicy($can_view), - 'edit' => $default->getPolicy($can_edit), - 'push' => $default->getPolicy($can_push), - ); - - if ($actual_values === $default_values) { - return 'fa-lock grey'; - } else { - return 'fa-lock'; - } + return 'fa-lock'; } protected function getEditEngineFieldKeys() { @@ -54,34 +26,12 @@ final class DiffusionRepositoryPoliciesManagementPanel ); } - protected function buildManagementPanelActions() { - $repository = $this->getRepository(); - $viewer = $this->getViewer(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $edit_uri = $this->getEditPageURI(); - - return array( - id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Policies')) - ->setHref($edit_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), - ); - } - public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $viewer, @@ -106,7 +56,22 @@ final class DiffusionRepositoryPoliciesManagementPanel $pushable = $descriptions[DiffusionPushCapability::CAPABILITY]; $view->addProperty(pht('Pushable By'), $pushable); - return $this->newBox(pht('Policies'), $view); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $edit_uri = $this->getEditPageURI(); + + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-pencil') + ->setText(pht('Edit')) + ->setHref($edit_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit); + + return $this->newBox(pht('Policies'), $view, array($button)); } } diff --git a/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php index 79b716e82a..ebd970f032 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php @@ -20,15 +20,7 @@ final class DiffusionRepositoryStagingManagementPanel public function getManagementPanelIcon() { - $repository = $this->getRepository(); - - $staging_uri = $repository->getStagingURI(); - - if ($staging_uri) { - return 'fa-upload'; - } else { - return 'fa-upload grey'; - } + return 'fa-upload'; } protected function getEditEngineFieldKeys() { @@ -37,34 +29,12 @@ final class DiffusionRepositoryStagingManagementPanel ); } - protected function buildManagementPanelActions() { - $repository = $this->getRepository(); - $viewer = $this->getViewer(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $staging_uri = $this->getEditPageURI(); - - return array( - id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Staging')) - ->setHref($staging_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), - ); - } - public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $staging_uri = $repository->getStagingURI(); if (!$staging_uri) { @@ -73,7 +43,22 @@ final class DiffusionRepositoryStagingManagementPanel $view->addProperty(pht('Staging Area URI'), $staging_uri); - return $this->newBox(pht('Staging Area'), $view); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $staging_uri = $this->getEditPageURI(); + + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-pencil') + ->setText(pht('Edit')) + ->setHref($staging_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit); + + return $this->newBox(pht('Staging Area'), $view, array($button)); } } diff --git a/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php deleted file mode 100644 index 324338443d..0000000000 --- a/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php +++ /dev/null @@ -1,506 +0,0 @@ -getRepository(); - - // TODO: We could try to show a warning icon in more cases, but just - // raise in the most serious cases for now. - $messages = $this->loadStatusMessages($repository); - - $raw_error = $this->buildRepositoryRawError($repository, $messages); - if ($raw_error) { - return 'fa-exclamation-triangle red'; - } - - return 'fa-check grey'; - } - - protected function buildManagementPanelActions() { - $repository = $this->getRepository(); - $viewer = $this->getViewer(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $update_uri = $repository->getPathURI('edit/update/'); - - return array( - id(new PhabricatorActionView()) - ->setIcon('fa-refresh') - ->setName(pht('Update Now')) - ->setWorkflow(true) - ->setDisabled(!$can_edit) - ->setHref($update_uri), - ); - } - - public function buildManagementPanelContent() { - $repository = $this->getRepository(); - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); - - $view->addProperty( - pht('Update Frequency'), - $this->buildRepositoryUpdateInterval($repository)); - - $messages = $this->loadStatusMessages($repository); - - $status = $this->buildRepositoryStatus($repository, $messages); - $raw_error = $this->buildRepositoryRawError($repository, $messages); - - $view->addProperty(pht('Status'), $status); - if ($raw_error) { - $view->addSectionHeader(pht('Raw Error')); - $view->addTextContent($raw_error); - } - - return $this->newBox(pht('Status'), $view); - } - - private function buildRepositoryUpdateInterval( - PhabricatorRepository $repository) { - - $smart_wait = $repository->loadUpdateInterval(); - - $doc_href = PhabricatorEnv::getDoclink( - 'Diffusion User Guide: Repository Updates'); - - return array( - phutil_format_relative_time_detailed($smart_wait), - " \xC2\xB7 ", - phutil_tag( - 'a', - array( - 'href' => $doc_href, - 'target' => '_blank', - ), - pht('Learn More')), - ); - } - - private function buildRepositoryStatus( - PhabricatorRepository $repository, - array $messages) { - - $viewer = $this->getViewer(); - $is_cluster = $repository->getAlmanacServicePHID(); - - $view = new PHUIStatusListView(); - - if ($repository->isTracked()) { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Repository Active'))); - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'bluegrey') - ->setTarget(pht('Repository Inactive')) - ->setNote( - pht('Activate this repository to begin or resume import.'))); - return $view; - } - - $binaries = array(); - $svnlook_check = false; - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $binaries[] = 'git'; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $binaries[] = 'svn'; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $binaries[] = 'hg'; - break; - } - - if ($repository->isHosted()) { - $proto_https = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTPS; - $proto_http = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTP; - $can_http = $repository->canServeProtocol($proto_http, false) || - $repository->canServeProtocol($proto_https, false); - - if ($can_http) { - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $binaries[] = 'git-http-backend'; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $binaries[] = 'svnserve'; - $binaries[] = 'svnadmin'; - $binaries[] = 'svnlook'; - $svnlook_check = true; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $binaries[] = 'hg'; - break; - } - } - - - $proto_ssh = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH; - $can_ssh = $repository->canServeProtocol($proto_ssh, false); - - if ($can_ssh) { - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $binaries[] = 'git-receive-pack'; - $binaries[] = 'git-upload-pack'; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $binaries[] = 'svnserve'; - $binaries[] = 'svnadmin'; - $binaries[] = 'svnlook'; - $svnlook_check = true; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $binaries[] = 'hg'; - break; - } - } - } - - $binaries = array_unique($binaries); - if (!$is_cluster) { - // We're only checking for binaries if we aren't running with a cluster - // configuration. In theory, we could check for binaries on the - // repository host machine, but we'd need to make this more complicated - // to do that. - - foreach ($binaries as $binary) { - $where = Filesystem::resolveBinary($binary); - if (!$where) { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget( - pht('Missing Binary %s', phutil_tag('tt', array(), $binary))) - ->setNote(pht( - "Unable to find this binary in the webserver's PATH. You may ". - "need to configure %s.", - $this->getEnvConfigLink()))); - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget( - pht('Found Binary %s', phutil_tag('tt', array(), $binary))) - ->setNote(phutil_tag('tt', array(), $where))); - } - } - - // This gets checked generically above. However, for svn commit hooks, we - // need this to be in environment.append-paths because subversion strips - // PATH. - if ($svnlook_check) { - $where = Filesystem::resolveBinary('svnlook'); - if ($where) { - $path = substr($where, 0, strlen($where) - strlen('svnlook')); - $dirs = PhabricatorEnv::getEnvConfig('environment.append-paths'); - $in_path = false; - foreach ($dirs as $dir) { - if (Filesystem::isDescendant($path, $dir)) { - $in_path = true; - break; - } - } - if (!$in_path) { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget( - pht('Missing Binary %s', phutil_tag('tt', array(), $binary))) - ->setNote(pht( - 'Unable to find this binary in `%s`. '. - 'You need to configure %s and include %s.', - 'environment.append-paths', - $this->getEnvConfigLink(), - $path))); - } - } - } - } - - $doc_href = PhabricatorEnv::getDoclink('Managing Daemons with phd'); - - $daemon_instructions = pht( - 'Use %s to start daemons. See %s.', - phutil_tag('tt', array(), 'bin/phd start'), - phutil_tag( - 'a', - array( - 'href' => $doc_href, - ), - pht('Managing Daemons with phd'))); - - - $pull_daemon = id(new PhabricatorDaemonLogQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) - ->withDaemonClasses(array('PhabricatorRepositoryPullLocalDaemon')) - ->setLimit(1) - ->execute(); - - if ($pull_daemon) { - - // TODO: In a cluster environment, we need a daemon on this repository's - // host, specifically, and we aren't checking for that right now. This - // is a reasonable proxy for things being more-or-less correctly set up, - // though. - - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Pull Daemon Running'))); - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget(pht('Pull Daemon Not Running')) - ->setNote($daemon_instructions)); - } - - - $task_daemon = id(new PhabricatorDaemonLogQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) - ->withDaemonClasses(array('PhabricatorTaskmasterDaemon')) - ->setLimit(1) - ->execute(); - if ($task_daemon) { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Task Daemon Running'))); - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget(pht('Task Daemon Not Running')) - ->setNote($daemon_instructions)); - } - - - if ($is_cluster) { - // Just omit this status check for now in cluster environments. We - // could make a service call and pull it from the repository host - // eventually. - } else if ($repository->usesLocalWorkingCopy()) { - $local_parent = dirname($repository->getLocalPath()); - if (Filesystem::pathExists($local_parent)) { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Storage Directory OK')) - ->setNote(phutil_tag('tt', array(), $local_parent))); - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget(pht('No Storage Directory')) - ->setNote( - pht( - 'Storage directory %s does not exist, or is not readable by '. - 'the webserver. Create this directory or make it readable.', - phutil_tag('tt', array(), $local_parent)))); - return $view; - } - - $local_path = $repository->getLocalPath(); - $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_INIT); - if ($message) { - switch ($message->getStatusCode()) { - case PhabricatorRepositoryStatusMessage::CODE_ERROR: - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget(pht('Initialization Error')) - ->setNote($message->getParameter('message'))); - return $view; - case PhabricatorRepositoryStatusMessage::CODE_OKAY: - if (Filesystem::pathExists($local_path)) { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Working Copy OK')) - ->setNote(phutil_tag('tt', array(), $local_path))); - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget(pht('Working Copy Error')) - ->setNote( - pht( - 'Working copy %s has been deleted, or is not '. - 'readable by the webserver. Make this directory '. - 'readable. If it has been deleted, the daemons should '. - 'restore it automatically.', - phutil_tag('tt', array(), $local_path)))); - return $view; - } - break; - default: - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green') - ->setTarget(pht('Initializing Working Copy')) - ->setNote(pht('Daemons are initializing the working copy.'))); - return $view; - } - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange') - ->setTarget(pht('No Working Copy Yet')) - ->setNote( - pht('Waiting for daemons to build a working copy.'))); - return $view; - } - } - - $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH); - if ($message) { - switch ($message->getStatusCode()) { - case PhabricatorRepositoryStatusMessage::CODE_ERROR: - $message = $message->getParameter('message'); - - $suggestion = null; - if (preg_match('/Permission denied \(publickey\)./', $message)) { - $suggestion = pht( - 'Public Key Error: This error usually indicates that the '. - 'keypair you have configured does not have permission to '. - 'access the repository.'); - } - - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget(pht('Update Error')) - ->setNote($suggestion)); - return $view; - case PhabricatorRepositoryStatusMessage::CODE_OKAY: - $ago = (PhabricatorTime::getNow() - $message->getEpoch()); - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Updates OK')) - ->setNote( - pht( - 'Last updated %s (%s ago).', - phabricator_datetime($message->getEpoch(), $viewer), - phutil_format_relative_time_detailed($ago)))); - break; - } - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange') - ->setTarget(pht('Waiting For Update')) - ->setNote( - pht('Waiting for daemons to read updates.'))); - } - - if ($repository->isImporting()) { - $ratio = $repository->loadImportProgress(); - $percentage = sprintf('%.2f%%', 100 * $ratio); - - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green') - ->setTarget(pht('Importing')) - ->setNote( - pht('%s Complete', $percentage))); - } else { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Fully Imported'))); - } - - if (idx($messages, PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE)) { - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_UP, 'indigo') - ->setTarget(pht('Prioritized')) - ->setNote(pht('This repository will be updated soon!'))); - } - - return $view; - } - - private function buildRepositoryRawError( - PhabricatorRepository $repository, - array $messages) { - $viewer = $this->getViewer(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $raw_error = null; - - $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH); - if ($message) { - switch ($message->getStatusCode()) { - case PhabricatorRepositoryStatusMessage::CODE_ERROR: - $raw_error = $message->getParameter('message'); - break; - } - } - - if ($raw_error !== null) { - if (!$can_edit) { - $raw_message = pht( - 'You must be able to edit a repository to see raw error messages '. - 'because they sometimes disclose sensitive information.'); - $raw_message = phutil_tag('em', array(), $raw_message); - } else { - $raw_message = phutil_escape_html_newlines($raw_error); - } - } else { - $raw_message = null; - } - - return $raw_message; - } - - private function loadStatusMessages(PhabricatorRepository $repository) { - $messages = id(new PhabricatorRepositoryStatusMessage()) - ->loadAllWhere('repositoryID = %d', $repository->getID()); - $messages = mpull($messages, null, 'getStatusType'); - - return $messages; - } - - private function getEnvConfigLink() { - $config_href = '/config/edit/environment.append-paths/'; - return phutil_tag( - 'a', - array( - 'href' => $config_href, - ), - 'environment.append-paths'); - } - -} diff --git a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php index 9cf210632d..a39dfaa632 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php @@ -14,15 +14,7 @@ final class DiffusionRepositoryStorageManagementPanel } public function getManagementPanelIcon() { - $repository = $this->getRepository(); - - if ($repository->getAlmanacServicePHID()) { - return 'fa-sitemap'; - } else if ($repository->isHosted()) { - return 'fa-folder'; - } else { - return 'fa-download'; - } + return 'fa-database'; } public function buildManagementPanelContent() { @@ -55,13 +47,15 @@ final class DiffusionRepositoryStorageManagementPanel $view->addProperty(pht('Storage Path'), $storage_path); $view->addProperty(pht('Storage Cluster'), $storage_service); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Storage')); + $doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories'); - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($view); + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-book') + ->setHref($doc_href) + ->setText(pht('Help')); + + return $this->newBox(pht('Storage'), $view, array($button)); } private function buildClusterStatusPanel() { @@ -231,21 +225,7 @@ final class DiffusionRepositoryStorageManagementPanel 'date', )); - $doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories'); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Cluster Status')) - ->addActionLink( - id(new PHUIButtonView()) - ->setIcon('fa-book') - ->setHref($doc_href) - ->setTag('a') - ->setText(pht('Documentation'))); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); + return $this->newBox(pht('Cluster Status'), $table); } } diff --git a/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php index c6b236d5b9..cd6e2c5be0 100644 --- a/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php @@ -19,15 +19,7 @@ final class DiffusionRepositorySubversionManagementPanel } public function getManagementPanelIcon() { - $repository = $this->getRepository(); - - $has_any = (bool)$repository->getDetail('svn-subpath'); - - if ($has_any) { - return 'fa-database'; - } else { - return 'fa-database grey'; - } + return 'fa-folder'; } protected function getEditEngineFieldKeys() { @@ -36,10 +28,18 @@ final class DiffusionRepositorySubversionManagementPanel ); } - protected function buildManagementPanelActions() { + public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); + $view = id(new PHUIPropertyListView()) + ->setViewer($viewer); + + $default_branch = nonempty( + $repository->getHumanReadableDetail('svn-subpath'), + phutil_tag('em', array(), pht('Import Entire Repository'))); + $view->addProperty(pht('Import Only'), $default_branch); + $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $repository, @@ -47,31 +47,15 @@ final class DiffusionRepositorySubversionManagementPanel $subversion_uri = $this->getEditPageURI(); - return array( - id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Properties')) - ->setHref($subversion_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), - ); - } + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-pencil') + ->setText(pht('Edit')) + ->setHref($subversion_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit); - public function buildManagementPanelContent() { - $repository = $this->getRepository(); - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); - - $default_branch = nonempty( - $repository->getHumanReadableDetail('svn-subpath'), - phutil_tag('em', array(), pht('Import Entire Repository'))); - $view->addProperty(pht('Import Only'), $default_branch); - - - return $this->newBox(pht('Subversion'), $view); + return $this->newBox(pht('Subversion'), $view, array($button)); } } diff --git a/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php index 996473f4f1..f64fbf8d6e 100644 --- a/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php @@ -14,17 +14,7 @@ final class DiffusionRepositorySymbolsManagementPanel } public function getManagementPanelIcon() { - $repository = $this->getRepository(); - - $has_any = - $repository->getSymbolLanguages() || - $repository->getSymbolSources(); - - if ($has_any) { - return 'fa-link'; - } else { - return 'fa-link grey'; - } + return 'fa-bullseye'; } protected function getEditEngineFieldKeys() { @@ -34,34 +24,12 @@ final class DiffusionRepositorySymbolsManagementPanel ); } - protected function buildManagementPanelActions() { - $repository = $this->getRepository(); - $viewer = $this->getViewer(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $symbols_uri = $this->getEditPageURI(); - - return array( - id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Symbols')) - ->setHref($symbols_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit), - ); - } - public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setViewer($viewer) - ->setActionList($this->newActions()); + ->setViewer($viewer); $languages = $repository->getSymbolLanguages(); if ($languages) { @@ -79,7 +47,22 @@ final class DiffusionRepositorySymbolsManagementPanel } $view->addProperty(pht('Uses Symbols From'), $sources); - return $this->newBox(pht('Symbols'), $view); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $symbols_uri = $this->getEditPageURI(); + + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-pencil') + ->setText(pht('Edit')) + ->setHref($symbols_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit); + + return $this->newBox(pht('Symbols'), $view, array($button)); } } diff --git a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php index 333c2f03c8..220bf4e2c1 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php @@ -10,7 +10,7 @@ final class DiffusionRepositoryURIsManagementPanel } public function getManagementPanelIcon() { - return 'fa-cogs'; + return 'fa-globe'; } public function getManagementPanelOrder() { @@ -95,24 +95,6 @@ final class DiffusionRepositoryURIsManagementPanel null, )); - $doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: URIs'); - $add_href = $repository->getPathURI('uri/edit/'); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Repository URIs')) - ->addActionLink( - id(new PHUIButtonView()) - ->setIcon('fa-plus') - ->setHref($add_href) - ->setTag('a') - ->setText(pht('Add New URI'))) - ->addActionLink( - id(new PHUIButtonView()) - ->setIcon('fa-book') - ->setHref($doc_href) - ->setTag('a') - ->setText(pht('Documentation'))); - $is_new = $repository->isNewlyInitialized(); $messages = array(); @@ -123,11 +105,7 @@ final class DiffusionRepositoryURIsManagementPanel $host_message = pht('Phabricator is hosting this repository.'); } - $messages[] = array( - id(new PHUIIconView())->setIcon('fa-folder'), - ' ', - $host_message, - ); + $messages[] = $host_message; } else { if ($is_new) { $observe_message = pht( @@ -137,22 +115,37 @@ final class DiffusionRepositoryURIsManagementPanel 'This repository is hosted remotely. Phabricator is observing it.'); } - $messages[] = array( - id(new PHUIIconView())->setIcon('fa-download'), - ' ', - $observe_message, - ); + $messages[] = $observe_message; } $info_view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setErrors($messages); - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setInfoView($info_view) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: URIs'); + $add_href = $repository->getPathURI('uri/edit/'); + + $add = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-plus') + ->setHref($add_href) + ->setDisabled(!$can_edit) + ->setText(pht('New URI')); + + $help = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-book') + ->setHref($doc_href) + ->setText(pht('Help')); + + $box = $this->newBox(pht('Repository URIs'), $table, array($add, $help)); + + return array($box, $info_view); } } diff --git a/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php b/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php index 4c84d430cf..ccfa0df4a4 100644 --- a/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php +++ b/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php @@ -233,6 +233,7 @@ final class DiffusionSetPasswordSettingsPanel extends PhabricatorSettingsPanel { $object_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form) ->setFormErrors($errors); @@ -258,6 +259,7 @@ final class DiffusionSetPasswordSettingsPanel extends PhabricatorSettingsPanel { $remove_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Remove VCS Password')) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($remove_form); $saved = null; diff --git a/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php b/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php index a16416ae1d..168b18caa5 100644 --- a/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php @@ -26,9 +26,7 @@ final class DiffusionGitCommandEngine $env['HOME'] = PhabricatorEnv::getEmptyCWD(); - if ($this->isAnySSHProtocol()) { - $env['GIT_SSH'] = $this->getSSHWrapper(); - } + $env['GIT_SSH'] = $this->getSSHWrapper(); if ($this->isAnyHTTPProtocol()) { $uri = $this->getURI(); diff --git a/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php b/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php index 49c618b085..9499b8f5f1 100644 --- a/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php @@ -11,12 +11,14 @@ final class DiffusionMercurialCommandEngine protected function newFormattedCommand($pattern, array $argv) { $args = array(); - if ($this->isAnySSHProtocol()) { - $pattern = "hg --config ui.ssh=%s {$pattern}"; - $args[] = $this->getSSHWrapper(); - } else { - $pattern = "hg {$pattern}"; - } + // NOTE: Here, and in Git and Subversion, we override the SSH command even + // if the repository does not use an SSH remote, since our SSH wrapper + // defuses an attack against older versions of Mercurial, Git and + // Subversion (see T12961) and it's possible to execute this attack + // in indirect ways, like by using an SSH subrepo inside an HTTP repo. + + $pattern = "hg --config ui.ssh=%s {$pattern}"; + $args[] = $this->getSSHWrapper(); return array($pattern, array_merge($args, $argv)); } diff --git a/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php b/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php index 11e864e74e..32a73867e5 100644 --- a/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php +++ b/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php @@ -105,14 +105,14 @@ final class DiffusionMercurialWireProtocol extends Phobject { * disallow the client from knowing we speak bundle2 by removing it * from the capabilities listing. * - * The format of the capabilties string is: "a space separated list + * The format of the capabilities string is: "a space separated list * of strings representing what commands the server supports" * @link https://www.mercurial-scm.org/wiki/CommandServer#Protocol * * @param string $capabilities - The string of capabilities to * strip the bundle2 capability from. This is expected to be * the space-separated list of strings resulting from the - * querying the 'capabilties' command. + * querying the 'capabilities' command. * * @return string The resulting space-separated list of capabilities * which no longer contains the 'bundle2' capability. This is meant diff --git a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php index a1a22670f9..e822bb76b0 100644 --- a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php +++ b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php @@ -58,7 +58,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { * @task sync */ public function synchronizeWorkingCopyAfterCreation() { - if (!$this->shouldEnableSynchronization()) { + if (!$this->shouldEnableSynchronization(false)) { return; } @@ -86,7 +86,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { * @task sync */ public function synchronizeWorkingCopyAfterHostingChange() { - if (!$this->shouldEnableSynchronization()) { + if (!$this->shouldEnableSynchronization(false)) { return; } @@ -133,7 +133,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { * @task sync */ public function synchronizeWorkingCopyBeforeRead() { - if (!$this->shouldEnableSynchronization()) { + if (!$this->shouldEnableSynchronization(true)) { return; } @@ -288,7 +288,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { * @task sync */ public function synchronizeWorkingCopyBeforeWrite() { - if (!$this->shouldEnableSynchronization()) { + if (!$this->shouldEnableSynchronization(true)) { return; } @@ -382,7 +382,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { public function synchronizeWorkingCopyAfterDiscovery($new_version) { - if (!$this->shouldEnableSynchronization()) { + if (!$this->shouldEnableSynchronization(true)) { return; } @@ -426,7 +426,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { * @task sync */ public function synchronizeWorkingCopyAfterWrite() { - if (!$this->shouldEnableSynchronization()) { + if (!$this->shouldEnableSynchronization(true)) { return; } @@ -551,7 +551,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { /** * @task internal */ - private function shouldEnableSynchronization() { + private function shouldEnableSynchronization($require_device) { $repository = $this->getRepository(); $service_phid = $repository->getAlmanacServicePHID(); @@ -563,9 +563,11 @@ final class DiffusionRepositoryClusterEngine extends Phobject { return false; } - $device = AlmanacKeys::getLiveDevice(); - if (!$device) { - return false; + if ($require_device) { + $device = AlmanacKeys::getLiveDevice(); + if (!$device) { + return false; + } } return true; diff --git a/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php b/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php index d8e9e6ff17..75b8785a6e 100644 --- a/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php @@ -44,9 +44,7 @@ final class DiffusionSubversionCommandEngine protected function newCustomEnvironment() { $env = array(); - if ($this->isAnySSHProtocol()) { - $env['SVN_SSH'] = $this->getSSHWrapper(); - } + $env['SVN_SSH'] = $this->getSSHWrapper(); return $env; } diff --git a/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php b/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php index 76d9ad9170..1df3ea3522 100644 --- a/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php +++ b/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php @@ -26,7 +26,7 @@ final class DiffusionCommandEngineTestCase extends PhabricatorTestCase { )); $this->assertCommandEngineFormat( - 'hg xyz', + (string)csprintf('hg --config ui.ssh=%s xyz', $ssh_wrapper), array( 'LANG' => 'en_US.UTF-8', 'HGPLAIN' => '1', @@ -102,7 +102,7 @@ final class DiffusionCommandEngineTestCase extends PhabricatorTestCase { )); $this->assertCommandEngineFormat( - 'hg xyz', + (string)csprintf('hg --config ui.ssh=%s xyz', $ssh_wrapper), array( 'LANG' => 'en_US.UTF-8', 'HGPLAIN' => '1', diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php index ab8f5e3e2e..a0548d5178 100644 --- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php +++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php @@ -122,9 +122,8 @@ final class DiffusionLowLevelCommitFieldsQuery $revisions = array_reverse($revisions); // Try to find an accepted revision first. - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; foreach ($revisions as $revision) { - if ($revision->getStatus() == $status_accepted) { + if ($revision->isAccepted()) { return $revision; } } diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php index 12b9e661d7..f4e27db670 100644 --- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php +++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php @@ -24,11 +24,12 @@ final class DiffusionLowLevelMercurialPathsQuery $path = $this->path; $commit = $this->commit; - $hg_paths_command = 'locate --print0 --rev %s -I %s'; - $hg_version = PhabricatorRepositoryVersion::getMercurialVersion(); - if (PhabricatorRepositoryVersion::isMercurialFilesCommandAvailable( - $hg_version)) { + $has_files = PhutilBinaryAnalyzer::getForBinary('hg') + ->isMercurialFilesCommandAvailable(); + if ($has_files) { $hg_paths_command = 'files --print0 --rev %s -I %s'; + } else { + $hg_paths_command = 'locate --print0 --rev %s -I %s'; } $match_against = trim($path, '/'); diff --git a/src/applications/diffusion/query/lowlevel/__tests__/DiffusionLowLevelMercurialPathsQueryTests.php b/src/applications/diffusion/query/lowlevel/__tests__/DiffusionLowLevelMercurialPathsQueryTests.php deleted file mode 100644 index 075ca2f1a5..0000000000 --- a/src/applications/diffusion/query/lowlevel/__tests__/DiffusionLowLevelMercurialPathsQueryTests.php +++ /dev/null @@ -1,31 +0,0 @@ - pht('Versions which should not use `files`'), - 'versions' => array('2.6.2', '2.9', '3.1'), - 'match' => false, - ), - - array( - 'name' => pht('Versions which should use `files`'), - 'versions' => array('3.2', '3.3', '3.5.2'), - 'match' => true, - ), - ); - - foreach ($cases as $case) { - foreach ($case['versions'] as $version) { - $actual = PhabricatorRepositoryVersion - ::isMercurialFilesCommandAvailable($version); - $expect = $case['match']; - $this->assertEqual($expect, $actual, $case['name']); - } - } - } - -} diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php index ab08025349..1a46d43adf 100644 --- a/src/applications/diffusion/request/DiffusionRequest.php +++ b/src/applications/diffusion/request/DiffusionRequest.php @@ -143,6 +143,7 @@ abstract class DiffusionRequest extends Phobject { $query = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) ->withIdentifiers(array($identifier)) + ->needProfileImage(true) ->needURIs(true); if ($need_edit) { diff --git a/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php b/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php index 0b3f191563..e0662b1e1a 100644 --- a/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php +++ b/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php @@ -54,7 +54,7 @@ final class DiffusionRepositoryDatasource $parts[] = $monogram; } - $name = implode(' ', $parts); + $name = implode("\n", $parts); $vcs = $repository->getVersionControlSystem(); $vcs_type = PhabricatorRepositoryType::getNameForRepositoryType($vcs); diff --git a/src/applications/diffusion/view/DiffusionBranchListView.php b/src/applications/diffusion/view/DiffusionBranchListView.php new file mode 100644 index 0000000000..a2ba34893e --- /dev/null +++ b/src/applications/diffusion/view/DiffusionBranchListView.php @@ -0,0 +1,142 @@ +branches = $branches; + return $this; + } + + public function setCommits(array $commits) { + assert_instances_of($commits, 'PhabricatorRepositoryCommit'); + $this->commits = mpull($commits, null, 'getCommitIdentifier'); + return $this; + } + + public function render() { + $drequest = $this->getDiffusionRequest(); + $current_branch = $drequest->getBranch(); + $repository = $drequest->getRepository(); + $commits = $this->commits; + $viewer = $this->getUser(); + require_celerity_resource('diffusion-css'); + + $buildables = $this->loadBuildables($commits); + $have_builds = false; + + $can_close_branches = ($repository->isHg()); + + Javelin::initBehavior('phabricator-tooltips'); + + $doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: Autoclose'); + $list = id(new PHUIObjectItemListView()) + ->setFlush(true) + ->addClass('diffusion-history-list') + ->addClass('diffusion-branch-list'); + + foreach ($this->branches as $branch) { + $build_view = null; + $button_bar = new PHUIButtonBarView(); + $commit = idx($commits, $branch->getCommitIdentifier()); + if ($commit) { + $details = $commit->getSummary(); + $datetime = phabricator_datetime($commit->getEpoch(), $viewer); + + $buildable = idx($buildables, $commit->getPHID()); + if ($buildable) { + $build_view = $this->renderBuildable($buildable, 'button'); + } + } else { + $datetime = null; + $details = null; + } + + if ($repository->supportsBranchComparison()) { + $compare_uri = $drequest->generateURI( + array( + 'action' => 'compare', + 'head' => $branch->getShortName(), + )); + $can_compare = ($branch->getShortName() != $current_branch); + if ($can_compare) { + $button_bar->addButton( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-balance-scale') + ->setToolTip(pht('Compare')) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) + ->setWorkflow(true) + ->setHref($compare_uri)); + } + } + + $browse_href = $drequest->generateURI( + array( + 'action' => 'browse', + 'branch' => $branch->getShortName(), + )); + + $button_bar->addButton( + id(new PHUIButtonView()) + ->setIcon('fa-code') + ->setHref($browse_href) + ->setTag('a') + ->setTooltip(pht('Browse')) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE)); + + $commit_link = $repository->getCommitURI( + $branch->getCommitIdentifier()); + + $commit_name = $repository->formatCommitName( + $branch->getCommitIdentifier(), $local = true); + + $commit_tag = id(new PHUITagView()) + ->setName($commit_name) + ->setHref($commit_link) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_INDIGO) + ->setBorder(PHUITagView::BORDER_NONE) + ->setSlimShady(true); + $subhead = array($commit_tag, ' ', $details); + + $item = id(new PHUIObjectItemView()) + ->setHeader($branch->getShortName()) + ->setHref($drequest->generateURI( + array( + 'action' => 'history', + 'branch' => $branch->getShortName(), + ))) + ->setSubhead($subhead) + ->setSideColumn(array( + $build_view, + $button_bar, + )); + + if ($branch->getShortName() == $repository->getDefaultBranch()) { + $item->setStatusIcon('fa-code-fork', pht('Default Branch')); + } + $item->addAttribute(array($datetime)); + + if ($can_close_branches) { + $fields = $branch->getRawFields(); + $closed = idx($fields, 'closed'); + if ($closed) { + $status = pht('Branch Closed'); + $item->setDisabled(true); + } else { + $status = pht('Branch Open'); + } + $item->addAttribute($status); + } + + $list->addItem($item); + + } + return $list; + + } +} diff --git a/src/applications/diffusion/view/DiffusionBrowseTableView.php b/src/applications/diffusion/view/DiffusionBrowseTableView.php index ffc5ce61ed..ffbfe8986f 100644 --- a/src/applications/diffusion/view/DiffusionBrowseTableView.php +++ b/src/applications/diffusion/view/DiffusionBrowseTableView.php @@ -20,6 +20,7 @@ final class DiffusionBrowseTableView extends DiffusionView { public function render() { $request = $this->getDiffusionRequest(); $repository = $request->getRepository(); + require_celerity_resource('diffusion-css'); $base_path = trim($request->getPath(), '/'); if ($base_path) { @@ -74,7 +75,6 @@ final class DiffusionBrowseTableView extends DiffusionView { $dict = array( 'lint' => celerity_generate_unique_node_id(), - 'commit' => celerity_generate_unique_node_id(), 'date' => celerity_generate_unique_node_id(), 'author' => celerity_generate_unique_node_id(), 'details' => celerity_generate_unique_node_id(), @@ -86,13 +86,13 @@ final class DiffusionBrowseTableView extends DiffusionView { } $rows[] = array( - $history_link, $browse_link, idx($dict, 'lint'), - $dict['commit'], $dict['details'], $dict['date'], + $history_link, ); + } if ($need_pull) { @@ -113,27 +113,16 @@ final class DiffusionBrowseTableView extends DiffusionView { $lint = $request->getLint(); $view = new AphrontTableView($rows); - $view->setHeaders( - array( - null, - pht('Path'), - ($lint ? $lint : pht('Lint')), - pht('Modified'), - pht('Details'), - pht('Committed'), - )); $view->setColumnClasses( array( - 'nudgeright', '', '', - '', - 'wide', + 'wide commit-detail', 'right', + 'right narrow', )); $view->setColumnVisibility( array( - true, true, $show_lint, true, @@ -143,16 +132,15 @@ final class DiffusionBrowseTableView extends DiffusionView { $view->setDeviceVisibility( array( - true, true, false, false, - true, + false, false, )); - return $view->render(); + return phutil_tag_div('diffusion-browse-table', $view->render()); } } diff --git a/src/applications/diffusion/view/DiffusionCommitListView.php b/src/applications/diffusion/view/DiffusionCommitListView.php new file mode 100644 index 0000000000..d4f20d0014 --- /dev/null +++ b/src/applications/diffusion/view/DiffusionCommitListView.php @@ -0,0 +1,177 @@ +noDataString = $no_data_string; + return $this; + } + + public function setHeader($header) { + $this->header = $header; + return $this; + } + + public function setCommits(array $commits) { + assert_instances_of($commits, 'PhabricatorRepositoryCommit'); + $this->commits = mpull($commits, null, 'getPHID'); + return $this; + } + + public function getCommits() { + return $this->commits; + } + + public function setHandles(array $handles) { + assert_instances_of($handles, 'PhabricatorObjectHandle'); + $this->handles = $handles; + return $this; + } + + private function getRequiredHandlePHIDs() { + $phids = array(); + foreach ($this->history as $item) { + $data = $item->getCommitData(); + if ($data) { + if ($data->getCommitDetail('authorPHID')) { + $phids[$data->getCommitDetail('authorPHID')] = true; + } + if ($data->getCommitDetail('committerPHID')) { + $phids[$data->getCommitDetail('committerPHID')] = true; + } + } + } + return array_keys($phids); + } + + private function getCommitDescription($phid) { + if ($this->commits === null) { + return pht('(Unknown Commit)'); + } + + $commit = idx($this->commits, $phid); + if (!$commit) { + return pht('(Unknown Commit)'); + } + + $summary = $commit->getCommitData()->getSummary(); + if (strlen($summary)) { + return $summary; + } + + // No summary, so either this is still importing or just has an empty + // commit message. + + if (!$commit->isImported()) { + return pht('(Importing Commit...)'); + } else { + return pht('(Untitled Commit)'); + } + } + + public function render() { + require_celerity_resource('diffusion-css'); + return $this->buildList(); + } + + public function buildList() { + $viewer = $this->getViewer(); + $rowc = array(); + + $phids = array(); + foreach ($this->getCommits() as $commit) { + $phids[] = $commit->getPHID(); + + $author_phid = $commit->getAuthorPHID(); + if ($author_phid) { + $phids[] = $author_phid; + } + } + + $handles = $viewer->loadHandles($phids); + + $cur_date = 0; + $view = array(); + foreach ($this->commits as $commit) { + $new_date = phabricator_date($commit->getEpoch(), $viewer); + if ($cur_date !== $new_date) { + $date = ucfirst( + phabricator_relative_date($commit->getEpoch(), $viewer)); + $header = id(new PHUIHeaderView()) + ->setHeader($date); + $list = id(new PHUIObjectItemListView()) + ->setFlush(true) + ->addClass('diffusion-history-list'); + + $view[] = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); + } + + $commit_phid = $commit->getPHID(); + $commit_handle = $handles[$commit_phid]; + $committed = null; + + $commit_name = $commit_handle->getName(); + $commit_link = $commit_handle->getURI(); + $commit_desc = $this->getCommitDescription($commit_phid); + $committed = phabricator_datetime($commit->getEpoch(), $viewer); + + $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); + $engine->setConfig('viewer', $viewer); + $commit_data = $commit->getCommitData(); + $message = $commit_data->getCommitMessage(); + $message = $engine->markupText($message); + $message = phutil_tag_div( + 'diffusion-history-message phabricator-remarkup', $message); + + $author_phid = $commit->getAuthorPHID(); + if ($author_phid) { + $author_name = $handles[$author_phid]->renderLink(); + $author_image_uri = $handles[$author_phid]->getImageURI(); + } else { + $author_name = $commit->getCommitData()->getAuthorName(); + $author_image_uri = + celerity_get_resource_uri('/rsrc/image/people/user0.png'); + } + + $commit_tag = id(new PHUITagView()) + ->setName($commit_name) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_INDIGO) + ->setBorder(PHUITagView::BORDER_NONE) + ->setSlimShady(true); + + $item = id(new PHUIObjectItemView()) + ->setHeader($commit_desc) + ->setHref($commit_link) + ->setDisabled($commit->isUnreachable()) + ->setDescription($message) + ->setImageURI($author_image_uri) + ->addByline(pht('Author: %s', $author_name)) + ->addIcon('none', $committed) + ->addAttribute($commit_tag); + + $list->addItem($item); + $cur_date = $new_date; + } + + if (!$view) { + $list = id(new PHUIObjectItemListView()) + ->setFlush(true) + ->setNoDataString($this->noDataString); + + $view = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Recent Commits')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); + } + + return $view; + } + +} diff --git a/src/applications/diffusion/view/DiffusionHistoryListView.php b/src/applications/diffusion/view/DiffusionHistoryListView.php new file mode 100644 index 0000000000..62ba7b70c2 --- /dev/null +++ b/src/applications/diffusion/view/DiffusionHistoryListView.php @@ -0,0 +1,171 @@ +getDiffusionRequest(); + $viewer = $this->getUser(); + $repository = $drequest->getRepository(); + + require_celerity_resource('diffusion-css'); + Javelin::initBehavior('phabricator-tooltips'); + + $buildables = $this->loadBuildables( + mpull($this->getHistory(), 'getCommit')); + + $show_revisions = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorDifferentialApplication', + $viewer); + + $handles = $viewer->loadHandles($this->getRequiredHandlePHIDs()); + + $show_builds = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorHarbormasterApplication', + $this->getUser()); + + $cur_date = null; + $view = array(); + foreach ($this->getHistory() as $history) { + $epoch = $history->getEpoch(); + $new_date = phabricator_date($history->getEpoch(), $viewer); + if ($cur_date !== $new_date) { + $date = ucfirst( + phabricator_relative_date($history->getEpoch(), $viewer)); + $header = id(new PHUIHeaderView()) + ->setHeader($date); + $list = id(new PHUIObjectItemListView()) + ->setFlush(true) + ->addClass('diffusion-history-list'); + + $view[] = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addClass('diffusion-mobile-view') + ->setObjectList($list); + } + + if ($epoch) { + $committed = $viewer->formatShortDateTime($epoch); + } else { + $committed = null; + } + + $data = $history->getCommitData(); + $author_phid = $committer = $committer_phid = null; + if ($data) { + $author_phid = $data->getCommitDetail('authorPHID'); + $committer_phid = $data->getCommitDetail('committerPHID'); + $committer = $data->getCommitDetail('committer'); + } + + if ($author_phid && isset($handles[$author_phid])) { + $author_name = $handles[$author_phid]->renderLink(); + $author_image = $handles[$author_phid]->getImageURI(); + } else { + $author_name = self::renderName($history->getAuthorName()); + $author_image = + celerity_get_resource_uri('/rsrc/image/people/user0.png'); + } + + $different_committer = false; + if ($committer_phid) { + $different_committer = ($committer_phid != $author_phid); + } else if ($committer != '') { + $different_committer = ($committer != $history->getAuthorName()); + } + if ($different_committer) { + if ($committer_phid && isset($handles[$committer_phid])) { + $committer = $handles[$committer_phid]->renderLink(); + } else { + $committer = self::renderName($committer); + } + $author_name = hsprintf('%s / %s', $author_name, $committer); + } + + // We can show details once the message and change have been imported. + $partial_import = PhabricatorRepositoryCommit::IMPORTED_MESSAGE | + PhabricatorRepositoryCommit::IMPORTED_CHANGE; + + $commit = $history->getCommit(); + if ($commit && $commit->isPartiallyImported($partial_import) && $data) { + $commit_desc = $history->getSummary(); + } else { + $commit_desc = phutil_tag('em', array(), pht("Importing\xE2\x80\xA6")); + } + + $browse_button = $this->linkBrowse( + $history->getPath(), + array( + 'commit' => $history->getCommitIdentifier(), + 'branch' => $drequest->getBranch(), + 'type' => $history->getFileType(), + ), + true); + + $diff_tag = null; + if ($show_revisions && $commit) { + $d_id = idx($this->getRevisions(), $commit->getPHID()); + if ($d_id) { + $diff_tag = id(new PHUITagView()) + ->setName('D'.$d_id) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_BLUE) + ->setHref('/D'.$d_id) + ->setBorder(PHUITagView::BORDER_NONE) + ->setSlimShady(true); + } + } + + $build_view = null; + if ($show_builds) { + $buildable = idx($buildables, $commit->getPHID()); + if ($buildable !== null) { + $build_view = $this->renderBuildable($buildable, 'button'); + } + } + + $message = null; + $commit_link = $repository->getCommitURI( + $history->getCommitIdentifier()); + + $commit_name = $repository->formatCommitName( + $history->getCommitIdentifier(), $local = true); + + $committed = phabricator_datetime($commit->getEpoch(), $viewer); + $author_name = phutil_tag( + 'strong', + array( + 'class' => 'diffusion-history-author-name', + ), + $author_name); + $authored = pht('%s on %s.', $author_name, $committed); + + $commit_tag = id(new PHUITagView()) + ->setName($commit_name) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_INDIGO) + ->setBorder(PHUITagView::BORDER_NONE) + ->setSlimShady(true); + + $item = id(new PHUIObjectItemView()) + ->setHeader($commit_desc) + ->setHref($commit_link) + ->setDisabled($commit->isUnreachable()) + ->setDescription($message) + ->setImageURI($author_image) + ->addAttribute(array($commit_tag, ' ', $diff_tag)) // For Copy Pasta + ->addAttribute($authored) + ->setSideColumn(array( + $build_view, + $browse_button, + )); + + $list->addItem($item); + $cur_date = $new_date; + } + + + return $view; + } + +} diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php index bb3aee37da..3885bbf47c 100644 --- a/src/applications/diffusion/view/DiffusionHistoryTableView.php +++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php @@ -1,87 +1,14 @@ history = $history; - return $this; - } - - public function loadRevisions() { - $commit_phids = array(); - foreach ($this->history as $item) { - if ($item->getCommit()) { - $commit_phids[] = $item->getCommit()->getPHID(); - } - } - - // TODO: Get rid of this. - $this->revisions = id(new DifferentialRevision()) - ->loadIDsByCommitPHIDs($commit_phids); - return $this; - } - - public function setHandles(array $handles) { - assert_instances_of($handles, 'PhabricatorObjectHandle'); - $this->handles = $handles; - return $this; - } - - private function getRequiredHandlePHIDs() { - $phids = array(); - foreach ($this->history as $item) { - $data = $item->getCommitData(); - if ($data) { - if ($data->getCommitDetail('authorPHID')) { - $phids[$data->getCommitDetail('authorPHID')] = true; - } - if ($data->getCommitDetail('committerPHID')) { - $phids[$data->getCommitDetail('committerPHID')] = true; - } - } - } - return array_keys($phids); - } - - public function setParents(array $parents) { - $this->parents = $parents; - return $this; - } - - public function setIsHead($is_head) { - $this->isHead = $is_head; - return $this; - } - - public function setIsTail($is_tail) { - $this->isTail = $is_tail; - return $this; - } - - public function setFilterParents($filter_parents) { - $this->filterParents = $filter_parents; - return $this; - } - - public function getFilterParents() { - return $this->filterParents; - } +final class DiffusionHistoryTableView extends DiffusionHistoryView { public function render() { $drequest = $this->getDiffusionRequest(); $viewer = $this->getUser(); - $buildables = $this->loadBuildables(mpull($this->history, 'getCommit')); + $buildables = $this->loadBuildables( + mpull($this->getHistory(), 'getCommit')); $has_any_build = false; $show_revisions = PhabricatorApplication::isClassInstalledForViewer( @@ -91,14 +18,14 @@ final class DiffusionHistoryTableView extends DiffusionView { $handles = $viewer->loadHandles($this->getRequiredHandlePHIDs()); $graph = null; - if ($this->parents) { - $parents = $this->parents; + if ($this->getParents()) { + $parents = $this->getParents(); // If we're filtering parents, remove relationships which point to // commits that are not part of the visible graph. Otherwise, we get // a big tree of nonsense when viewing release branches like "stable" // versus "master". - if ($this->filterParents) { + if ($this->getFilterParents()) { foreach ($parents as $key => $nodes) { foreach ($nodes as $nkey => $node) { if (empty($parents[$node])) { @@ -109,8 +36,8 @@ final class DiffusionHistoryTableView extends DiffusionView { } $graph = id(new PHUIDiffGraphView()) - ->setIsHead($this->isHead) - ->setIsTail($this->isTail) + ->setIsHead($this->getIsHead()) + ->setIsTail($this->getIsTail()) ->renderGraph($parents); } @@ -120,7 +47,7 @@ final class DiffusionHistoryTableView extends DiffusionView { $rows = array(); $ii = 0; - foreach ($this->history as $history) { + foreach ($this->getHistory() as $history) { $epoch = $history->getEpoch(); if ($epoch) { @@ -209,7 +136,7 @@ final class DiffusionHistoryTableView extends DiffusionView { $build, $audit_view, ($commit ? - self::linkRevision(idx($this->revisions, $commit->getPHID())) : + self::linkRevision(idx($this->getRevisions(), $commit->getPHID())) : null), $author, $summary, diff --git a/src/applications/diffusion/view/DiffusionHistoryView.php b/src/applications/diffusion/view/DiffusionHistoryView.php new file mode 100644 index 0000000000..56a0f3b75f --- /dev/null +++ b/src/applications/diffusion/view/DiffusionHistoryView.php @@ -0,0 +1,101 @@ +history = $history; + return $this; + } + + public function getHistory() { + return $this->history; + } + + public function loadRevisions() { + $commit_phids = array(); + foreach ($this->history as $item) { + if ($item->getCommit()) { + $commit_phids[] = $item->getCommit()->getPHID(); + } + } + + // TODO: Get rid of this. + $this->revisions = id(new DifferentialRevision()) + ->loadIDsByCommitPHIDs($commit_phids); + return $this; + } + + public function getRevisions() { + return $this->revisions; + } + + public function setHandles(array $handles) { + assert_instances_of($handles, 'PhabricatorObjectHandle'); + $this->handles = $handles; + return $this; + } + + public function getRequiredHandlePHIDs() { + $phids = array(); + foreach ($this->history as $item) { + $data = $item->getCommitData(); + if ($data) { + if ($data->getCommitDetail('authorPHID')) { + $phids[$data->getCommitDetail('authorPHID')] = true; + } + if ($data->getCommitDetail('committerPHID')) { + $phids[$data->getCommitDetail('committerPHID')] = true; + } + } + } + return array_keys($phids); + } + + public function setParents(array $parents) { + $this->parents = $parents; + return $this; + } + + public function getParents() { + return $this->parents; + } + + public function setIsHead($is_head) { + $this->isHead = $is_head; + return $this; + } + + public function getIsHead() { + return $this->isHead; + } + + public function setIsTail($is_tail) { + $this->isTail = $is_tail; + return $this; + } + + public function getIsTail() { + return $this->isTail; + } + + public function setFilterParents($filter_parents) { + $this->filterParents = $filter_parents; + return $this; + } + + public function getFilterParents() { + return $this->filterParents; + } + + public function render() {} + +} diff --git a/src/applications/diffusion/view/DiffusionPatternSearchView.php b/src/applications/diffusion/view/DiffusionPatternSearchView.php new file mode 100644 index 0000000000..a679b51400 --- /dev/null +++ b/src/applications/diffusion/view/DiffusionPatternSearchView.php @@ -0,0 +1,124 @@ +path = $path; + return $this; + } + + public function setMatches(array $matches) { + $this->matches = $matches; + return $this; + } + + public function setPattern($pattern) { + $this->pattern = $pattern; + return $this; + } + + public function render() { + $drequest = $this->getDiffusionRequest(); + $path = $this->path; + $pattern = $this->pattern; + $rows = array(); + + foreach ($this->matches as $result) { + list($line, $string) = $result; + + $matches = null; + $count = @preg_match_all( + '('.$pattern.')u', + $string, + $matches, + PREG_OFFSET_CAPTURE); + + if (!$count) { + $output = ltrim($string); + } else { + $output = array(); + $cursor = 0; + $length = strlen($string); + foreach ($matches[0] as $match) { + $offset = $match[1]; + if ($cursor != $offset) { + $output[] = array( + 'text' => substr($string, $cursor, $offset), + 'highlight' => false, + ); + } + $output[] = array( + 'text' => $match[0], + 'highlight' => true, + ); + $cursor = $offset + strlen($match[0]); + } + if ($cursor != $length) { + $output[] = array( + 'text' => substr($string, $cursor), + 'highlight' => false, + ); + } + + if ($output) { + $output[0]['text'] = ltrim($output[0]['text']); + } + + foreach ($output as $key => $segment) { + if ($segment['highlight']) { + $output[$key] = phutil_tag('strong', array(), $segment['text']); + } else { + $output[$key] = $segment['text']; + } + } + } + + $string = phutil_tag( + 'pre', + array('class' => 'PhabricatorMonospaced phui-source-fragment'), + $output); + + $href = $drequest->generateURI(array( + 'action' => 'browse', + 'path' => $path, + 'line' => $line, + )); + + $rows[] = array( + phutil_tag('a', array('href' => $href), $line), + $string, + ); + } + + $path_title = Filesystem::readablePath($this->path, $drequest->getPath()); + + $href = $drequest->generateURI(array( + 'action' => 'browse', + 'path' => $path_title, + )); + + $title = phutil_tag('a', array('href' => $href), $path_title); + + + $table = id(new AphrontTableView($rows)) + ->setClassName('remarkup-code') + ->setHeaders(array(pht('Line'), pht('String'))) + ->setColumnClasses(array('n', 'wide')); + + $header = id(new PHUIHeaderView()) + ->setHeader($title); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + + return $box->render(); + } + + +} diff --git a/src/applications/diffusion/view/DiffusionReadmeView.php b/src/applications/diffusion/view/DiffusionReadmeView.php index b475fa88ac..693333249d 100644 --- a/src/applications/diffusion/view/DiffusionReadmeView.php +++ b/src/applications/diffusion/view/DiffusionReadmeView.php @@ -98,9 +98,14 @@ final class DiffusionReadmeView extends DiffusionView { ->setFluid(true) ->appendChild($readme_content); + $header = id(new PHUIHeaderView()) + ->setHeader($readme_name) + ->addClass('diffusion-panel-header-view'); + return id(new PHUIObjectBoxView()) - ->setHeaderText($readme_name) + ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addClass('diffusion-mobile-view') ->appendChild($document) ->addClass('diffusion-readme-view'); } diff --git a/src/applications/diffusion/view/DiffusionTagListView.php b/src/applications/diffusion/view/DiffusionTagListView.php index df59925522..0f8cbeae71 100644 --- a/src/applications/diffusion/view/DiffusionTagListView.php +++ b/src/applications/diffusion/view/DiffusionTagListView.php @@ -29,44 +29,33 @@ final class DiffusionTagListView extends DiffusionView { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $viewer = $this->getViewer(); + require_celerity_resource('diffusion-css'); $buildables = $this->loadBuildables($this->commits); - $has_builds = false; - $rows = array(); + $list = id(new PHUIObjectItemListView()) + ->setFlush(true) + ->addClass('diffusion-history-list'); foreach ($this->tags as $tag) { $commit = idx($this->commits, $tag->getCommitIdentifier()); + $button_bar = new PHUIButtonBarView(); - $tag_link = phutil_tag( - 'a', + $tag_href = $drequest->generateURI( array( - 'href' => $drequest->generateURI( - array( - 'action' => 'browse', - 'commit' => $tag->getName(), - )), - ), - $tag->getName()); + 'action' => 'history', + 'commit' => $tag->getName(), + )); - $commit_link = phutil_tag( - 'a', + $commit_href = $drequest->generateURI( array( - 'href' => $drequest->generateURI( - array( - 'action' => 'commit', - 'commit' => $tag->getCommitIdentifier(), - )), - ), - $repository->formatCommitName( - $tag->getCommitIdentifier())); + 'action' => 'commit', + 'commit' => $tag->getCommitIdentifier(), + )); - $author = null; - if ($commit && $commit->getAuthorPHID()) { - $author = $this->handles[$commit->getAuthorPHID()]->renderLink(); - } else if ($commit && $commit->getCommitData()) { - $author = self::renderName($commit->getCommitData()->getAuthorName()); + if ($commit) { + $author = $this->renderAuthor($tag, $commit); } else { - $author = self::renderName($tag->getAuthor()); + $author = null; } $description = null; @@ -83,58 +72,98 @@ final class DiffusionTagListView extends DiffusionView { } } - $build = null; + $build_view = null; if ($commit) { $buildable = idx($buildables, $commit->getPHID()); if ($buildable) { - $build = $this->renderBuildable($buildable); - $has_builds = true; + $build_view = $this->renderBuildable($buildable, 'button'); } } - $history = $this->linkTagHistory($tag->getName()); + if ($repository->supportsBranchComparison()) { + $compare_uri = $drequest->generateURI( + array( + 'action' => 'compare', + 'head' => $tag->getName(), + )); - $rows[] = array( - $history, - $tag_link, - $commit_link, - $build, - $author, - $description, - $viewer->formatShortDateTime($tag->getEpoch()), - ); - } + $button_bar->addButton( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-balance-scale') + ->setToolTip(pht('Compare')) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) + ->setWorkflow(true) + ->setHref($compare_uri)); + } - $table = id(new AphrontTableView($rows)) - ->setHeaders( + $commit_name = $repository->formatCommitName( + $tag->getCommitIdentifier(), $local = true); + + $browse_href = $drequest->generateURI( array( - null, - pht('Tag'), - pht('Commit'), - null, - pht('Author'), - pht('Description'), - pht('Created'), - )) - ->setColumnClasses( - array( - 'nudgeright', - 'pri', - '', - '', - '', - 'wide', - 'right', - )) - ->setColumnVisibility( - array( - true, - true, - true, - $has_builds, + 'action' => 'browse', + 'commit' => $tag->getName(), )); - return $table->render(); + $button_bar->addButton( + id(new PHUIButtonView()) + ->setTooltip(pht('Browse')) + ->setIcon('fa-code') + ->setHref($browse_href) + ->setTag('a') + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE)); + + $commit_tag = id(new PHUITagView()) + ->setName($commit_name) + ->setHref($commit_href) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_INDIGO) + ->setBorder(PHUITagView::BORDER_NONE) + ->setSlimShady(true); + + $item = id(new PHUIObjectItemView()) + ->setHeader($tag->getName()) + ->setHref($tag_href) + ->addAttribute(array($commit_tag)) + ->addAttribute($description) + ->setSideColumn(array( + $build_view, + $button_bar, + )); + + if ($author) { + $item->addAttribute($author); + } + + $list->addItem($item); + } + + return $list; + } + + private function renderAuthor( + DiffusionRepositoryTag $tag, + PhabricatorRepositoryCommit $commit) { + $viewer = $this->getViewer(); + + if ($commit->getAuthorPHID()) { + $author = $this->handles[$commit->getAuthorPHID()]->renderLink(); + } else if ($commit->getCommitData()) { + $author = self::renderName($commit->getCommitData()->getAuthorName()); + } else { + $author = self::renderName($tag->getAuthor()); + } + + $committed = phabricator_datetime($commit->getEpoch(), $viewer); + $author_name = phutil_tag( + 'strong', + array( + 'class' => 'diffusion-history-author-name', + ), + $author); + + return pht('%s on %s.', $author_name, $committed); } } diff --git a/src/applications/diffusion/view/DiffusionTagTableView.php b/src/applications/diffusion/view/DiffusionTagTableView.php new file mode 100644 index 0000000000..59a06353ab --- /dev/null +++ b/src/applications/diffusion/view/DiffusionTagTableView.php @@ -0,0 +1,140 @@ +tags = $tags; + return $this; + } + + public function setCommits(array $commits) { + $this->commits = mpull($commits, null, 'getCommitIdentifier'); + return $this; + } + + public function setHandles(array $handles) { + $this->handles = $handles; + return $this; + } + + public function getRequiredHandlePHIDs() { + return array_filter(mpull($this->commits, 'getAuthorPHID')); + } + + public function render() { + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $viewer = $this->getViewer(); + + $buildables = $this->loadBuildables($this->commits); + $has_builds = false; + + $rows = array(); + foreach ($this->tags as $tag) { + $commit = idx($this->commits, $tag->getCommitIdentifier()); + + $tag_link = phutil_tag( + 'a', + array( + 'href' => $drequest->generateURI( + array( + 'action' => 'browse', + 'commit' => $tag->getName(), + )), + ), + $tag->getName()); + + $commit_link = phutil_tag( + 'a', + array( + 'href' => $drequest->generateURI( + array( + 'action' => 'commit', + 'commit' => $tag->getCommitIdentifier(), + )), + ), + $repository->formatCommitName( + $tag->getCommitIdentifier())); + + $author = null; + if ($commit && $commit->getAuthorPHID()) { + $author = $this->handles[$commit->getAuthorPHID()]->renderLink(); + } else if ($commit && $commit->getCommitData()) { + $author = self::renderName($commit->getCommitData()->getAuthorName()); + } else { + $author = self::renderName($tag->getAuthor()); + } + + $description = null; + if ($tag->getType() == 'git/tag') { + // In Git, a tag may be a "real" tag, or just a reference to a commit. + // If it's a real tag, use the message on the tag, since this may be + // unique data which isn't otherwise available. + $description = $tag->getDescription(); + } else { + if ($commit) { + $description = $commit->getSummary(); + } else { + $description = $tag->getDescription(); + } + } + + $build = null; + if ($commit) { + $buildable = idx($buildables, $commit->getPHID()); + if ($buildable) { + $build = $this->renderBuildable($buildable); + $has_builds = true; + } + } + + $history = $this->linkTagHistory($tag->getName()); + + $rows[] = array( + $history, + $tag_link, + $commit_link, + $build, + $author, + $description, + $viewer->formatShortDateTime($tag->getEpoch()), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + null, + pht('Tag'), + pht('Commit'), + null, + pht('Author'), + pht('Description'), + pht('Created'), + )) + ->setColumnClasses( + array( + 'nudgeright', + 'pri', + '', + '', + '', + 'wide', + 'right', + )) + ->setColumnVisibility( + array( + true, + true, + true, + $has_builds, + )); + + return $table->render(); + } + +} diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index 5aa1fad33c..eb43e49f3c 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -58,7 +58,10 @@ abstract class DiffusionView extends AphrontView { id(new PHUIIconView())->setIcon('fa-history bluegrey')); } - final public function linkBrowse($path, array $details = array()) { + final public function linkBrowse( + $path, + array $details = array(), + $button = false) { require_celerity_resource('diffusion-icons-css'); Javelin::initBehavior('phabricator-tooltips'); @@ -111,6 +114,15 @@ abstract class DiffusionView extends AphrontView { ); } + if ($button) { + return id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-code') + ->setHref($href) + ->setToolTip(pht('Browse')) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE); + } + return javelin_tag( 'a', array( @@ -144,6 +156,19 @@ abstract class DiffusionView extends AphrontView { $commit_name); } + final public static function linkDetail( + PhabricatorRepository $repository, + $commit, + $detail) { + + return phutil_tag( + 'a', + array( + 'href' => $repository->getCommitURI($commit), + ), + $detail); + } + final public static function linkRevision($id) { if (!$id) { return null; @@ -168,7 +193,7 @@ abstract class DiffusionView extends AphrontView { 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $email->getAddress(), - 'align' => 'E', + 'align' => 'S', 'size' => 'auto', ), ), @@ -177,30 +202,37 @@ abstract class DiffusionView extends AphrontView { return hsprintf('%s', $name); } - final protected function renderBuildable(HarbormasterBuildable $buildable) { + final protected function renderBuildable( + HarbormasterBuildable $buildable, + $type = null) { $status = $buildable->getBuildableStatus(); + Javelin::initBehavior('phabricator-tooltips'); $icon = HarbormasterBuildable::getBuildableStatusIcon($status); $color = HarbormasterBuildable::getBuildableStatusColor($status); $name = HarbormasterBuildable::getBuildableStatusName($status); - $icon_view = id(new PHUIIconView()) - ->setIcon($icon.' '.$color); + if ($type == 'button') { + return id(new PHUIButtonView()) + ->setTag('a') + ->setText($name) + ->setIcon($icon) + ->setColor($color) + ->setHref('/'.$buildable->getMonogram()) + ->addClass('mmr') + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) + ->addClass('diffusion-list-build-status'); + } - $tooltip_view = javelin_tag( - 'span', - array( - 'sigil' => 'has-tooltip', - 'meta' => array('tip' => $name), - ), - $icon_view); + return id(new PHUIIconView()) + ->setIcon($icon.' '.$color) + ->addSigil('has-tooltip') + ->setHref('/'.$buildable->getMonogram()) + ->setMetadata( + array( + 'tip' => $name, + )); - Javelin::initBehavior('phabricator-tooltips'); - - return phutil_tag( - 'a', - array('href' => '/'.$buildable->getMonogram()), - $tooltip_view); } final protected function loadBuildables(array $commits) { diff --git a/src/applications/drydock/controller/DrydockBlueprintEditController.php b/src/applications/drydock/controller/DrydockBlueprintEditController.php index f25dc01bff..8f066c708b 100644 --- a/src/applications/drydock/controller/DrydockBlueprintEditController.php +++ b/src/applications/drydock/controller/DrydockBlueprintEditController.php @@ -74,6 +74,7 @@ final class DrydockBlueprintEditController extends DrydockBlueprintController { $title = pht('Create New Blueprint'); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('New Blueprint')); + $crumbs->setBorder(true); $form = id(new AphrontFormView()) ->setUser($viewer) @@ -86,12 +87,16 @@ final class DrydockBlueprintEditController extends DrydockBlueprintController { $box = id(new PHUIObjectBoxView()) ->setFormErrors($errors) ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setFooter($box); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($box); + ->appendChild($view); } } diff --git a/src/applications/drydock/controller/DrydockConsoleController.php b/src/applications/drydock/controller/DrydockConsoleController.php index ac524cb8a5..54adcf5ca5 100644 --- a/src/applications/drydock/controller/DrydockConsoleController.php +++ b/src/applications/drydock/controller/DrydockConsoleController.php @@ -26,7 +26,8 @@ final class DrydockConsoleController extends DrydockController { $viewer = $request->getViewer(); $menu = id(new PHUIObjectItemListView()) - ->setUser($viewer); + ->setUser($viewer) + ->setBig(true); $menu->addItem( id(new PHUIObjectItemView()) @@ -64,17 +65,15 @@ final class DrydockConsoleController extends DrydockController { $crumbs->addTextCrumb(pht('Console')); $crumbs->setBorder(true); - $box = id(new PHUIObjectBoxView()) - ->setObjectList($menu); - $title = pht('Drydock Console'); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-truck'); + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) + ->setObjectList($menu); $view = id(new PHUITwoColumnView()) - ->setHeader($header) + ->setFixed(true) ->setFooter($box); return $this->newPage() diff --git a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php index 4e306772a3..1ccc82eb7b 100644 --- a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php +++ b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php @@ -289,31 +289,30 @@ final class DrydockLandRepositoryOperation ); } - $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; - if ($revision->getStatus() != $status_accepted) { - switch ($revision->getStatus()) { - case ArcanistDifferentialRevisionStatus::CLOSED: - return array( - 'title' => pht('Revision Closed'), - 'body' => pht( - 'This revision has already been closed. Only open, accepted '. - 'revisions may land.'), - ); - case ArcanistDifferentialRevisionStatus::ABANDONED: - return array( - 'title' => pht('Revision Abandoned'), - 'body' => pht( - 'This revision has been abandoned. Only accepted revisions '. - 'may land.'), - ); - default: - return array( - 'title' => pht('Revision Not Accepted'), - 'body' => pht( - 'This revision is still under review. Only revisions which '. - 'have been accepted may land.'), - ); - } + if ($revision->isAccepted()) { + // We can land accepted revisions, so continue below. Otherwise, raise + // an error with tailored messaging for the most common cases. + } else if ($revision->isAbandoned()) { + return array( + 'title' => pht('Revision Abandoned'), + 'body' => pht( + 'This revision has been abandoned. Only accepted revisions '. + 'may land.'), + ); + } else if ($revision->isClosed()) { + return array( + 'title' => pht('Revision Closed'), + 'body' => pht( + 'This revision has already been closed. Only open, accepted '. + 'revisions may land.'), + ); + } else { + return array( + 'title' => pht('Revision Not Accepted'), + 'body' => pht( + 'This revision is still under review. Only revisions which '. + 'have been accepted may land.'), + ); } // Check for other operations. Eventually this should probably be more diff --git a/src/applications/feed/conduit/FeedQueryConduitAPIMethod.php b/src/applications/feed/conduit/FeedQueryConduitAPIMethod.php index 661b70a7ab..bddc4f5921 100644 --- a/src/applications/feed/conduit/FeedQueryConduitAPIMethod.php +++ b/src/applications/feed/conduit/FeedQueryConduitAPIMethod.php @@ -67,16 +67,16 @@ final class FeedQueryConduitAPIMethod extends FeedConduitAPIMethod { if (!$limit) { $limit = $this->getDefaultLimit(); } - $filter_phids = $request->getValue('filterPHIDs'); - if (!$filter_phids) { - $filter_phids = array(); - } $query = id(new PhabricatorFeedQuery()) ->setLimit($limit) - ->setFilterPHIDs($filter_phids) ->setViewer($user); + $filter_phids = $request->getValue('filterPHIDs'); + if ($filter_phids) { + $query->withFilterPHIDs($filter_phids); + } + $after = $request->getValue('after'); if (strlen($after)) { $query->setAfterID($after); diff --git a/src/applications/feed/query/PhabricatorFeedQuery.php b/src/applications/feed/query/PhabricatorFeedQuery.php index 13cfb266ed..ae1da3aba0 100644 --- a/src/applications/feed/query/PhabricatorFeedQuery.php +++ b/src/applications/feed/query/PhabricatorFeedQuery.php @@ -5,8 +5,10 @@ final class PhabricatorFeedQuery private $filterPHIDs; private $chronologicalKeys; + private $rangeMin; + private $rangeMax; - public function setFilterPHIDs(array $phids) { + public function withFilterPHIDs(array $phids) { $this->filterPHIDs = $phids; return $this; } @@ -16,50 +18,52 @@ final class PhabricatorFeedQuery return $this; } + public function withEpochInRange($range_min, $range_max) { + $this->rangeMin = $range_min; + $this->rangeMax = $range_max; + return $this; + } + + public function newResultObject() { + return new PhabricatorFeedStoryData(); + } + protected function loadPage() { - $story_table = new PhabricatorFeedStoryData(); - $conn = $story_table->establishConnection('r'); - - $data = queryfx_all( - $conn, - 'SELECT story.* FROM %T story %Q %Q %Q %Q %Q', - $story_table->getTableName(), - $this->buildJoinClause($conn), - $this->buildWhereClause($conn), - $this->buildGroupClause($conn), - $this->buildOrderClause($conn), - $this->buildLimitClause($conn)); - - return $data; + // NOTE: We return raw rows from this method, which is a little unusual. + return $this->loadStandardPageRows($this->newResultObject()); } protected function willFilterPage(array $data) { return PhabricatorFeedStory::loadAllFromRows($data, $this->getViewer()); } - protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + // NOTE: We perform this join unconditionally (even if we have no filter // PHIDs) to omit rows which have no story references. These story data // rows are notifications or realtime alerts. $ref_table = new PhabricatorFeedStoryReference(); - return qsprintf( - $conn_r, + $joins[] = qsprintf( + $conn, 'JOIN %T ref ON ref.chronologicalKey = story.chronologicalKey', $ref_table->getTableName()); + + return $joins; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->filterPHIDs) { + if ($this->filterPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'ref.objectPHID IN (%Ls)', $this->filterPHIDs); } - if ($this->chronologicalKeys) { + if ($this->chronologicalKeys !== null) { // NOTE: We want to use integers in the query so we can take advantage // of keys, but can't use %d on 32-bit systems. Make sure all the keys // are integers and then format them raw. @@ -73,21 +77,37 @@ final class PhabricatorFeedQuery } $where[] = qsprintf( - $conn_r, + $conn, 'ref.chronologicalKey IN (%Q)', implode(', ', $keys)); } - $where[] = $this->buildPagingClause($conn_r); + // NOTE: We may not have 64-bit PHP, so do the shifts in MySQL instead. + // From EXPLAIN, it appears like MySQL is smart enough to compute the + // result and make use of keys to execute the query. - return $this->formatWhereClause($where); + if ($this->rangeMin !== null) { + $where[] = qsprintf( + $conn, + 'ref.chronologicalKey >= (%d << 32)', + $this->rangeMin); + } + + if ($this->rangeMax !== null) { + $where[] = qsprintf( + $conn, + 'ref.chronologicalKey < (%d << 32)', + $this->rangeMax); + } + + return $where; } - protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { - if ($this->filterPHIDs) { - return qsprintf($conn_r, 'GROUP BY ref.chronologicalKey'); + protected function buildGroupClause(AphrontDatabaseConnection $conn) { + if ($this->filterPHIDs !== null) { + return qsprintf($conn, 'GROUP BY ref.chronologicalKey'); } else { - return qsprintf($conn_r, 'GROUP BY story.chronologicalKey'); + return qsprintf($conn, 'GROUP BY story.chronologicalKey'); } } @@ -95,6 +115,20 @@ final class PhabricatorFeedQuery return array('key'); } + public function getBuiltinOrders() { + return array( + 'newest' => array( + 'vector' => array('key'), + 'name' => pht('Creation (Newest First)'), + 'aliases' => array('created'), + ), + 'oldest' => array( + 'vector' => array('-key'), + 'name' => pht('Creation (Oldest First)'), + ), + ); + } + public function getOrderableColumns() { $table = ($this->filterPHIDs ? 'ref' : 'story'); return array( @@ -120,6 +154,10 @@ final class PhabricatorFeedQuery return $item['chronologicalKey']; } + protected function getPrimaryTableAlias() { + return 'story'; + } + public function getQueryApplicationClass() { return 'PhabricatorFeedApplication'; } diff --git a/src/applications/feed/query/PhabricatorFeedSearchEngine.php b/src/applications/feed/query/PhabricatorFeedSearchEngine.php index b8caf60ae7..d17c756524 100644 --- a/src/applications/feed/query/PhabricatorFeedSearchEngine.php +++ b/src/applications/feed/query/PhabricatorFeedSearchEngine.php @@ -11,87 +11,99 @@ final class PhabricatorFeedSearchEngine return 'PhabricatorFeedApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter( - 'userPHIDs', - $this->readUsersFromRequest($request, 'users')); - - $saved->setParameter( - 'projectPHIDs', - array_values($request->getArr('projectPHIDs'))); - - $saved->setParameter( - 'viewerProjects', - $request->getBool('viewerProjects')); - - return $saved; + public function newQuery() { + return new PhabricatorFeedQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new PhabricatorFeedQuery()); + protected function shouldShowOrderField() { + return false; + } + + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Include Users')) + ->setKey('userPHIDs'), + // NOTE: This query is not executed with EdgeLogic, so we can't use + // a fancy logical datasource. + id(new PhabricatorSearchDatasourceField()) + ->setDatasource(new PhabricatorProjectDatasource()) + ->setLabel(pht('Include Projects')) + ->setKey('projectPHIDs'), + id(new PhabricatorSearchDateControlField()) + ->setLabel(pht('Occurs After')) + ->setKey('rangeStart'), + id(new PhabricatorSearchDateControlField()) + ->setLabel(pht('Occurs Before')) + ->setKey('rangeEnd'), + + // NOTE: This is a legacy field retained only for backward + // compatibility. If the projects field used EdgeLogic, we could use + // `viewerprojects()` to execute an equivalent query. + id(new PhabricatorSearchCheckboxesField()) + ->setKey('viewerProjects') + ->setOptions( + array( + 'self' => pht('Include stories about projects I am a member of.'), + )), + ); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); $phids = array(); - - $user_phids = $saved->getParameter('userPHIDs'); - if ($user_phids) { - $phids[] = $user_phids; + if ($map['userPHIDs']) { + $phids += array_fuse($map['userPHIDs']); } - $proj_phids = $saved->getParameter('projectPHIDs'); - if ($proj_phids) { - $phids[] = $proj_phids; + if ($map['projectPHIDs']) { + $phids += array_fuse($map['projectPHIDs']); } - $viewer_projects = $saved->getParameter('viewerProjects'); + // NOTE: This value may be `true` for older saved queries, or + // `array('self')` for newer ones. + $viewer_projects = $map['viewerProjects']; if ($viewer_projects) { $viewer = $this->requireViewer(); $projects = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withMemberPHIDs(array($viewer->getPHID())) ->execute(); - $phids[] = mpull($projects, 'getPHID'); + $phids += array_fuse(mpull($projects, 'getPHID')); } - $phids = array_mergev($phids); if ($phids) { - $query->setFilterPHIDs($phids); + $query->withFilterPHIDs($phids); + } + + $range_min = $map['rangeStart']; + if ($range_min) { + $range_min = $range_min->getEpoch(); + } + + $range_max = $map['rangeEnd']; + if ($range_max) { + $range_max = $range_max->getEpoch(); + } + + if ($range_min && $range_max) { + if ($range_min > $range_max) { + throw new PhabricatorSearchConstraintException( + pht( + 'The specified "Occurs Before" date is earlier in time than the '. + 'specified "Occurs After" date, so this query can never match '. + 'any results.')); + } + } + + if ($range_min || $range_max) { + $query->withEpochInRange($range_min, $range_max); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) { - - $user_phids = $saved_query->getParameter('userPHIDs', array()); - $proj_phids = $saved_query->getParameter('projectPHIDs', array()); - $viewer_projects = $saved_query->getParameter('viewerProjects'); - - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('users') - ->setLabel(pht('Include Users')) - ->setValue($user_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorProjectDatasource()) - ->setName('projectPHIDs') - ->setLabel(pht('Include Projects')) - ->setValue($proj_phids)) - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->addCheckbox( - 'viewerProjects', - 1, - pht('Include stories about projects I am a member of.'), - $viewer_projects)); - } - protected function getURI($path) { return '/feed/'.$path; } @@ -117,7 +129,7 @@ final class PhabricatorFeedSearchEngine case 'all': return $query; case 'projects': - return $query->setParameter('viewerProjects', true); + return $query->setParameter('viewerProjects', array('self')); } return parent::buildSavedQueryFromBuiltin($query_key); diff --git a/src/applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php b/src/applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php index 4eb5d9d825..ac51e30386 100644 --- a/src/applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php +++ b/src/applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php @@ -56,4 +56,20 @@ final class PhabricatorFilesOnDiskBuiltinFile return $map; } + public function getProjectBuiltinFiles() { + $root = dirname(phutil_get_library_root('phabricator')); + $root = $root.'/resources/builtin/projects/'; + + $map = array(); + $list = id(new FileFinder($root)) + ->withType('f') + ->withFollowSymlinks(true) + ->find(); + + foreach ($list as $file) { + $map[$file] = $root.$file; + } + return $map; + } + } diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php index 930ec61483..b06e855617 100644 --- a/src/applications/files/controller/PhabricatorFileComposeController.php +++ b/src/applications/files/controller/PhabricatorFileComposeController.php @@ -48,7 +48,8 @@ final class PhabricatorFileComposeController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_IMAGE) + ->setTransactionType( + PhabricatorProjectImageTransaction::TRANSACTIONTYPE) ->setNewValue($file->getPHID()); $editor = id(new PhabricatorProjectTransactionEditor()) @@ -81,7 +82,7 @@ final class PhabricatorFileComposeController $buttons[] = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip compose-select-color', 'style' => 'margin: 0 8px 8px 0', 'meta' => array( @@ -101,7 +102,7 @@ final class PhabricatorFileComposeController $icons[] = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip compose-select-icon', 'style' => 'margin: 0 8px 8px 0', 'meta' => array( diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php index 061790aa31..f30421c132 100644 --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -44,7 +44,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { if ($ttl !== null) { $ttl_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade(PHUITagView::COLOR_YELLOW) + ->setColor(PHUITagView::COLOR_YELLOW) ->setName(pht('Temporary')); $header->addTag($ttl_tag); } @@ -53,7 +53,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { if ($partial) { $partial_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade(PHUITagView::COLOR_ORANGE) + ->setColor(PHUITagView::COLOR_ORANGE) ->setName(pht('Partial Upload')); $header->addTag($partial_tag); } diff --git a/src/applications/files/controller/PhabricatorFileLightboxController.php b/src/applications/files/controller/PhabricatorFileLightboxController.php index 11d4b95bc1..1f679d621b 100644 --- a/src/applications/files/controller/PhabricatorFileLightboxController.php +++ b/src/applications/files/controller/PhabricatorFileLightboxController.php @@ -76,7 +76,7 @@ final class PhabricatorFileLightboxController ->appendChild( id(new PHUIButtonView()) ->setTag('a') - ->setText(pht('Login to Comment')) + ->setText(pht('Log In to Comment')) ->setHref((string)$login_href)); } diff --git a/src/applications/files/controller/PhabricatorFileTransformListController.php b/src/applications/files/controller/PhabricatorFileTransformListController.php index 026f60320e..ab5322fc1a 100644 --- a/src/applications/files/controller/PhabricatorFileTransformListController.php +++ b/src/applications/files/controller/PhabricatorFileTransformListController.php @@ -68,7 +68,7 @@ final class PhabricatorFileTransformListController $view_link = phutil_tag( 'a', array( - 'class' => 'small grey button', + 'class' => 'small button button-grey', 'href' => $view_href, ), $view_text); diff --git a/src/applications/files/controller/PhabricatorFileUploadController.php b/src/applications/files/controller/PhabricatorFileUploadController.php index 3be0ca3968..c06e0e6d89 100644 --- a/src/applications/files/controller/PhabricatorFileUploadController.php +++ b/src/applications/files/controller/PhabricatorFileUploadController.php @@ -90,17 +90,12 @@ final class PhabricatorFileUploadController extends PhabricatorFileController { ->setShowIfSupportedID($support_id); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('File')) + ->setHeaderText($title) ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-upload'); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter(array( $form_box, $global_upload, diff --git a/src/applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php b/src/applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php deleted file mode 100644 index bf2c04a584..0000000000 --- a/src/applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php +++ /dev/null @@ -1,71 +0,0 @@ -setName('purge') - ->setSynopsis(pht('Delete files with missing data.')) - ->setArguments( - array( - array( - 'name' => 'all', - 'help' => pht('Update all files.'), - ), - array( - 'name' => 'dry-run', - 'help' => pht('Show what would be updated.'), - ), - array( - 'name' => 'names', - 'wildcard' => true, - ), - )); - } - - public function execute(PhutilArgumentParser $args) { - $console = PhutilConsole::getConsole(); - - $iterator = $this->buildIterator($args); - if (!$iterator) { - throw new PhutilArgumentUsageException( - pht( - 'Either specify a list of files to purge, or use `%s` '. - 'to purge all files.', - '--all')); - } - - $is_dry_run = $args->getArg('dry-run'); - - foreach ($iterator as $file) { - $fid = 'F'.$file->getID(); - - try { - $file->loadFileData(); - $okay = true; - } catch (Exception $ex) { - $okay = false; - } - - if ($okay) { - $console->writeOut( - "%s\n", - pht('%s: File data is OK, not purging.', $fid)); - } else { - if ($is_dry_run) { - $console->writeOut( - "%s\n", - pht('%s: Would purge (dry run).', $fid)); - } else { - $console->writeOut( - "%s\n", - pht('%s: Purging.', $fid)); - $file->delete(); - } - } - } - - return 0; - } -} diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php index a2de5ca6ab..071ee03991 100644 --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -18,6 +18,7 @@ final class PhabricatorFileQuery private $isDeleted; private $needTransforms; private $builtinKeys; + private $isBuiltin; public function withIDs(array $ids) { $this->ids = $ids; @@ -54,6 +55,11 @@ final class PhabricatorFileQuery return $this; } + public function withIsBuiltin($is_builtin) { + $this->isBuiltin = $is_builtin; + return $this; + } + /** * Select files which are transformations of some other file. For example, * you can use this query to find previously generated thumbnails of an image @@ -416,6 +422,18 @@ final class PhabricatorFileQuery $this->builtinKeys); } + if ($this->isBuiltin !== null) { + if ($this->isBuiltin) { + $where[] = qsprintf( + $conn, + 'builtinKey IS NOT NULL'); + } else { + $where[] = qsprintf( + $conn, + 'builtinKey IS NULL'); + } + } + return $where; } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 6861cb0924..7507060993 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -393,6 +393,7 @@ final class PhabricatorFile extends PhabricatorFileDAO $tmp = new TempFile(); Filesystem::writeFile($tmp, $data); $file->setMimeType(Filesystem::getMimeType($tmp)); + unset($tmp); } try { diff --git a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php index 936676b902..65dc0a12a2 100644 --- a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php +++ b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php @@ -94,11 +94,12 @@ final class PhabricatorFileTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue(pht('File Scramble Test Task')); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE) ->setNewValue('{'.$file->getMonogram().'}'); id(new ManiphestTransactionEditor()) diff --git a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php index cda07d590b..e3242af329 100644 --- a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php +++ b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php @@ -186,12 +186,18 @@ abstract class PhabricatorFileUploadSource $actual_length = strlen($data); $rope->removeBytesFromHead($actual_length); - $chunk_data = PhabricatorFile::newFromFileData( - $data, - array( - 'name' => $file->getMonogram().'.chunk-'.$offset, - 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, - )); + $params = array( + 'name' => $file->getMonogram().'.chunk-'.$offset, + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + ); + + // If this isn't the initial chunk, provide a dummy MIME type so we do not + // try to detect it. See T12857. + if ($offset > 0) { + $params['mime-type'] = 'application/octet-stream'; + } + + $chunk_data = PhabricatorFile::newFromFileData($data, $params); $chunk = PhabricatorFileChunk::initializeNewChunk( $file->getStorageHandle(), @@ -208,11 +214,17 @@ abstract class PhabricatorFileUploadSource } private function getNewFileParameters() { - return array( + $parameters = array( 'name' => $this->getName(), - 'ttl.relative' => $this->getRelativeTTL(), 'viewPolicy' => $this->getViewPolicy(), ); + + $ttl = $this->getRelativeTTL(); + if ($ttl !== null) { + $parameters['ttl.relative'] = $ttl; + } + + return $parameters; } private function getChunkEngine() { diff --git a/src/applications/fund/application/PhabricatorFundApplication.php b/src/applications/fund/application/PhabricatorFundApplication.php index 3a26e501e6..9f29f30986 100644 --- a/src/applications/fund/application/PhabricatorFundApplication.php +++ b/src/applications/fund/application/PhabricatorFundApplication.php @@ -42,8 +42,8 @@ final class PhabricatorFundApplication extends PhabricatorApplication { '/fund/' => array( '(?:query/(?P[^/]+)/)?' => 'FundInitiativeListController', 'create/' => 'FundInitiativeEditController', - 'comment/(?P[1-9]\d*)/' => 'FundInitiativeCommentController', - 'edit/(?:(?P\d+)/)?' => 'FundInitiativeEditController', + $this->getEditRoutePattern('edit/') + => 'FundInitiativeEditController', 'close/(?P\d+)/' => 'FundInitiativeCloseController', 'back/(?P\d+)/' => 'FundInitiativeBackController', 'backers/(?:(?P\d+)/)?(?:query/(?P[^/]+)/)?' diff --git a/src/applications/fund/controller/FundInitiativeBackController.php b/src/applications/fund/controller/FundInitiativeBackController.php index 414a5ebd64..17ead82ebf 100644 --- a/src/applications/fund/controller/FundInitiativeBackController.php +++ b/src/applications/fund/controller/FundInitiativeBackController.php @@ -96,7 +96,7 @@ final class FundInitiativeBackController $xactions = array(); $xactions[] = id(new FundBackerTransaction()) - ->setTransactionType(FundBackerTransaction::TYPE_STATUS) + ->setTransactionType(FundBackerStatusTransaction::TRANSACTIONTYPE) ->setNewValue(FundBacker::STATUS_IN_CART); $editor = id(new FundBackerEditor()) diff --git a/src/applications/fund/controller/FundInitiativeCloseController.php b/src/applications/fund/controller/FundInitiativeCloseController.php index 6adddb0d80..97c6de3d97 100644 --- a/src/applications/fund/controller/FundInitiativeCloseController.php +++ b/src/applications/fund/controller/FundInitiativeCloseController.php @@ -25,7 +25,7 @@ final class FundInitiativeCloseController $is_close = !$initiative->isClosed(); if ($request->isFormPost()) { - $type_status = FundInitiativeTransaction::TYPE_STATUS; + $type_status = FundInitiativeStatusTransaction::TRANSACTIONTYPE; if ($is_close) { $new_status = FundInitiative::STATUS_CLOSED; diff --git a/src/applications/fund/controller/FundInitiativeCommentController.php b/src/applications/fund/controller/FundInitiativeCommentController.php deleted file mode 100644 index 2c4998a94c..0000000000 --- a/src/applications/fund/controller/FundInitiativeCommentController.php +++ /dev/null @@ -1,63 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - if (!$request->isFormPost()) { - return new Aphront400Response(); - } - - $initiative = id(new FundInitiativeQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$initiative) { - return new Aphront404Response(); - } - - $is_preview = $request->isPreviewRequest(); - $draft = PhabricatorDraft::buildFromRequest($request); - - $view_uri = '/'.$initiative->getMonogram(); - - $xactions = array(); - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) - ->attachComment( - id(new FundInitiativeTransactionComment()) - ->setContent($request->getStr('comment'))); - - $editor = id(new FundInitiativeEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect($request->isContinueRequest()) - ->setContentSourceFromRequest($request) - ->setIsPreview($is_preview); - - try { - $xactions = $editor->applyTransactions($initiative, $xactions); - } catch (PhabricatorApplicationTransactionNoEffectException $ex) { - return id(new PhabricatorApplicationTransactionNoEffectResponse()) - ->setCancelURI($view_uri) - ->setException($ex); - } - - if ($draft) { - $draft->replaceOrDelete(); - } - - if ($request->isAjax() && $is_preview) { - return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($viewer) - ->setTransactions($xactions) - ->setIsPreview($is_preview); - } else { - return id(new AphrontRedirectResponse()) - ->setURI($view_uri); - } - } - -} diff --git a/src/applications/fund/controller/FundInitiativeEditController.php b/src/applications/fund/controller/FundInitiativeEditController.php index 6e092d1e05..10bf916913 100644 --- a/src/applications/fund/controller/FundInitiativeEditController.php +++ b/src/applications/fund/controller/FundInitiativeEditController.php @@ -1,256 +1,11 @@ getViewer(); - $id = $request->getURIData('id'); - - if ($id) { - $initiative = id(new FundInitiativeQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$initiative) { - return new Aphront404Response(); - } - $is_new = false; - } else { - $initiative = FundInitiative::initializeNewInitiative($viewer); - $is_new = true; - } - - if ($is_new) { - $title = pht('Create Initiative'); - $button_text = pht('Create Initiative'); - $cancel_uri = $this->getApplicationURI(); - $header_icon = 'fa-plus-square'; - } else { - $title = pht( - 'Edit Initiative: %s', - $initiative->getName()); - $button_text = pht('Save Changes'); - $cancel_uri = '/'.$initiative->getMonogram(); - $header_icon = 'fa-pencil'; - } - - $e_name = true; - $v_name = $initiative->getName(); - - $e_merchant = null; - $v_merchant = $initiative->getMerchantPHID(); - - $v_desc = $initiative->getDescription(); - $v_risk = $initiative->getRisks(); - - if ($is_new) { - $v_projects = array(); - } else { - $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( - $initiative->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $v_projects = array_reverse($v_projects); - } - - $validation_exception = null; - if ($request->isFormPost()) { - $v_name = $request->getStr('name'); - $v_desc = $request->getStr('description'); - $v_risk = $request->getStr('risks'); - $v_view = $request->getStr('viewPolicy'); - $v_edit = $request->getStr('editPolicy'); - $v_merchant = $request->getStr('merchantPHID'); - $v_projects = $request->getArr('projects'); - - $type_name = FundInitiativeTransaction::TYPE_NAME; - $type_desc = FundInitiativeTransaction::TYPE_DESCRIPTION; - $type_risk = FundInitiativeTransaction::TYPE_RISKS; - $type_merchant = FundInitiativeTransaction::TYPE_MERCHANT; - $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; - $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; - - $xactions = array(); - - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType($type_desc) - ->setNewValue($v_desc); - - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType($type_risk) - ->setNewValue($v_risk); - - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType($type_merchant) - ->setNewValue($v_merchant); - - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType($type_view) - ->setNewValue($v_view); - - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType($type_edit) - ->setNewValue($v_edit); - - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($v_projects))); - - $editor = id(new FundInitiativeEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $editor->applyTransactions($initiative, $xactions); - - return id(new AphrontRedirectResponse()) - ->setURI('/'.$initiative->getMonogram()); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_name = $ex->getShortMessage($type_name); - $e_merchant = $ex->getShortMessage($type_merchant); - - $initiative->setViewPolicy($v_view); - $initiative->setEditPolicy($v_edit); - } - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($initiative) - ->execute(); - - $merchants = id(new PhortuneMerchantQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->execute(); - - $merchant_options = array(); - foreach ($merchants as $merchant) { - $merchant_options[$merchant->getPHID()] = pht( - 'Merchant %d %s', - $merchant->getID(), - $merchant->getName()); - } - - if ($v_merchant && empty($merchant_options[$v_merchant])) { - $merchant_options = array( - $v_merchant => pht('(Restricted Merchant)'), - ) + $merchant_options; - } - - if (!$merchant_options) { - return $this->newDialog() - ->setTitle(pht('No Valid Phortune Merchant Accounts')) - ->appendParagraph( - pht( - 'You do not control any merchant accounts which can receive '. - 'payments from this initiative. When you create an initiative, '. - 'you need to specify a merchant account where funds will be paid '. - 'to.')) - ->appendParagraph( - pht( - 'Create a merchant account in the Phortune application before '. - 'creating an initiative in Fund.')) - ->addCancelButton($this->getApplicationURI()); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('name') - ->setLabel(pht('Name')) - ->setValue($v_name) - ->setError($e_name)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('merchantPHID') - ->setLabel(pht('Pay To Merchant')) - ->setValue($v_merchant) - ->setError($e_merchant) - ->setOptions($merchant_options)) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setName('description') - ->setLabel(pht('Description')) - ->setValue($v_desc)) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setName('risks') - ->setLabel(pht('Risks/Challenges')) - ->setValue($v_risk)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Tags')) - ->setName('projects') - ->setValue($v_projects) - ->setDatasource(new PhabricatorProjectDatasource())) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($initiative) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($initiative) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue($button_text) - ->addCancelButton($cancel_uri)); - - $crumbs = $this->buildApplicationCrumbs(); - if ($is_new) { - $crumbs->addTextCrumb(pht('Create Initiative')); - } else { - $crumbs->addTextCrumb( - $initiative->getMonogram(), - '/'.$initiative->getMonogram()); - $crumbs->addTextCrumb(pht('Edit')); - } - $crumbs->setBorder(true); - - $box = id(new PHUIObjectBoxView()) - ->setValidationException($validation_exception) - ->setHeaderText(pht('Initiative')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($form); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter($box); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); + return id(new FundInitiativeEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/fund/controller/FundInitiativeViewController.php b/src/applications/fund/controller/FundInitiativeViewController.php index 4960e7aa35..6d6a19643d 100644 --- a/src/applications/fund/controller/FundInitiativeViewController.php +++ b/src/applications/fund/controller/FundInitiativeViewController.php @@ -29,8 +29,8 @@ final class FundInitiativeViewController $initiative->getName()); if ($initiative->isClosed()) { - $status_icon = 'fa-times'; - $status_color = 'bluegrey'; + $status_icon = 'fa-ban'; + $status_color = 'indigo'; } else { $status_icon = 'fa-check'; $status_color = 'bluegrey'; @@ -52,15 +52,16 @@ final class FundInitiativeViewController $timeline = $this->buildTransactionTimeline( $initiative, new FundInitiativeTransactionQuery()); + $timeline->setQuoteRef($initiative->getMonogram()); - $add_comment = $this->buildCommentForm($initiative); + $comment_view = $this->buildCommentForm($initiative, $timeline); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) ->setMainColumn(array( $timeline, - $add_comment, + $comment_view, )) ->addPropertySection(pht('Details'), $details); @@ -164,26 +165,14 @@ final class FundInitiativeViewController return $curtain; } - private function buildCommentForm(FundInitiative $initiative) { + private function buildCommentForm(FundInitiative $initiative, $timeline) { $viewer = $this->getViewer(); + $box = id(new FundInitiativeEditEngine()) + ->setViewer($viewer) + ->buildEditEngineCommentView($initiative) + ->setTransactionTimeline($timeline); - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - - $add_comment_header = $is_serious - ? pht('Add Comment') - : pht('Add Liquidity'); - - $draft = PhabricatorDraft::newFromUserAndKey( - $viewer, $initiative->getPHID()); - - return id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setObjectPHID($initiative->getPHID()) - ->setDraft($draft) - ->setHeaderText($add_comment_header) - ->setAction( - $this->getApplicationURI('/comment/'.$initiative->getID().'/')) - ->setSubmitButtonName(pht('Add Comment')); + return $box; } diff --git a/src/applications/fund/editor/FundBackerEditor.php b/src/applications/fund/editor/FundBackerEditor.php index aabd1d8e60..403853863b 100644 --- a/src/applications/fund/editor/FundBackerEditor.php +++ b/src/applications/fund/editor/FundBackerEditor.php @@ -11,67 +11,4 @@ final class FundBackerEditor return pht('Fund Backing'); } - public function getTransactionTypes() { - $types = parent::getTransactionTypes(); - - $types[] = FundBackerTransaction::TYPE_STATUS; - $types[] = FundBackerTransaction::TYPE_REFUND; - - return $types; - } - - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case FundBackerTransaction::TYPE_STATUS: - return $object->getStatus(); - case FundBackerTransaction::TYPE_REFUND: - return null; - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case FundBackerTransaction::TYPE_STATUS: - case FundBackerTransaction::TYPE_REFUND: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case FundBackerTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - return; - case FundBackerTransaction::TYPE_REFUND: - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case FundBackerTransaction::TYPE_STATUS: - case FundBackerTransaction::TYPE_REFUND: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - } diff --git a/src/applications/fund/editor/FundInitiativeEditEngine.php b/src/applications/fund/editor/FundInitiativeEditEngine.php new file mode 100644 index 0000000000..201a814a0c --- /dev/null +++ b/src/applications/fund/editor/FundInitiativeEditEngine.php @@ -0,0 +1,152 @@ +getViewer()); + } + + protected function newObjectQuery() { + return new FundInitiativeQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Initiative'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Initiative: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return $object->getName(); + } + + protected function getObjectCreateShortText() { + return pht('Create Initiative'); + } + + protected function getObjectName() { + return pht('Initivative'); + } + + protected function getObjectCreateCancelURI($object) { + return $this->getApplication()->getApplicationURI('/'); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('edit/'); + } + + protected function getObjectViewURI($object) { + return $object->getViewURI(); + } + + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + FundCreateInitiativesCapability::CAPABILITY); + } + + protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + $v_merchant = $object->getMerchantPHID(); + + $merchants = id(new PhortuneMerchantQuery()) + ->setViewer($viewer) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + + $merchant_options = array(); + foreach ($merchants as $merchant) { + $merchant_options[$merchant->getPHID()] = pht( + 'Merchant %d %s', + $merchant->getID(), + $merchant->getName()); + } + + if ($v_merchant && empty($merchant_options[$v_merchant])) { + $merchant_options = array( + $v_merchant => pht('(Restricted Merchant)'), + ) + $merchant_options; + } + + $merchant_instructions = null; + if (!$merchant_options) { + $merchant_instructions = pht( + 'NOTE: You do not control any merchant accounts which can receive '. + 'payments from this initiative. When you create an initiative, '. + 'you need to specify a merchant account where funds will be paid '. + 'to. Create a merchant account in the Phortune application before '. + 'creating an initiative in Fund.'); + } + + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Initiative name.')) + ->setConduitTypeDescription(pht('New initiative name.')) + ->setTransactionType( + FundInitiativeNameTransaction::TRANSACTIONTYPE) + ->setValue($object->getName()) + ->setIsRequired(true), + id(new PhabricatorSelectEditField()) + ->setKey('merchantPHID') + ->setLabel(pht('Merchant')) + ->setDescription(pht('Merchant operating the initiative.')) + ->setConduitTypeDescription(pht('New initiative merchant.')) + ->setControlInstructions($merchant_instructions) + ->setValue($object->getMerchantPHID()) + ->setTransactionType( + FundInitiativeMerchantTransaction::TRANSACTIONTYPE) + ->setOptions($merchant_options) + ->setIsRequired(true), + id(new PhabricatorRemarkupEditField()) + ->setKey('description') + ->setLabel(pht('Description')) + ->setDescription(pht('Initiative long description.')) + ->setConduitTypeDescription(pht('New initiative description.')) + ->setTransactionType( + FundInitiativeDescriptionTransaction::TRANSACTIONTYPE) + ->setValue($object->getDescription()), + id(new PhabricatorRemarkupEditField()) + ->setKey('risks') + ->setLabel(pht('Risks/Challenges')) + ->setDescription(pht('Initiative risks and challenges.')) + ->setConduitTypeDescription(pht('Initiative risks and challenges.')) + ->setTransactionType( + FundInitiativeRisksTransaction::TRANSACTIONTYPE) + ->setValue($object->getRisks()), + + ); + + } + +} diff --git a/src/applications/fund/editor/FundInitiativeEditor.php b/src/applications/fund/editor/FundInitiativeEditor.php index 40fc6f0174..e5c372fd12 100644 --- a/src/applications/fund/editor/FundInitiativeEditor.php +++ b/src/applications/fund/editor/FundInitiativeEditor.php @@ -11,16 +11,16 @@ final class FundInitiativeEditor return pht('Fund Initiatives'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this initiative.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); - - $types[] = FundInitiativeTransaction::TYPE_NAME; - $types[] = FundInitiativeTransaction::TYPE_DESCRIPTION; - $types[] = FundInitiativeTransaction::TYPE_RISKS; - $types[] = FundInitiativeTransaction::TYPE_STATUS; - $types[] = FundInitiativeTransaction::TYPE_BACKER; - $types[] = FundInitiativeTransaction::TYPE_REFUND; - $types[] = FundInitiativeTransaction::TYPE_MERCHANT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_COMMENT; @@ -28,204 +28,6 @@ final class FundInitiativeEditor return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case FundInitiativeTransaction::TYPE_NAME: - return $object->getName(); - case FundInitiativeTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case FundInitiativeTransaction::TYPE_RISKS: - return $object->getRisks(); - case FundInitiativeTransaction::TYPE_STATUS: - return $object->getStatus(); - case FundInitiativeTransaction::TYPE_BACKER: - case FundInitiativeTransaction::TYPE_REFUND: - return null; - case FundInitiativeTransaction::TYPE_MERCHANT: - return $object->getMerchantPHID(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case FundInitiativeTransaction::TYPE_NAME: - case FundInitiativeTransaction::TYPE_DESCRIPTION: - case FundInitiativeTransaction::TYPE_RISKS: - case FundInitiativeTransaction::TYPE_STATUS: - case FundInitiativeTransaction::TYPE_BACKER: - case FundInitiativeTransaction::TYPE_REFUND: - case FundInitiativeTransaction::TYPE_MERCHANT: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $type = $xaction->getTransactionType(); - switch ($type) { - case FundInitiativeTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - case FundInitiativeTransaction::TYPE_DESCRIPTION: - $object->setDescription($xaction->getNewValue()); - return; - case FundInitiativeTransaction::TYPE_RISKS: - $object->setRisks($xaction->getNewValue()); - return; - case FundInitiativeTransaction::TYPE_MERCHANT: - $object->setMerchantPHID($xaction->getNewValue()); - return; - case FundInitiativeTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - return; - case FundInitiativeTransaction::TYPE_BACKER: - case FundInitiativeTransaction::TYPE_REFUND: - $amount = $xaction->getMetadataValue( - FundInitiativeTransaction::PROPERTY_AMOUNT); - $amount = PhortuneCurrency::newFromString($amount); - - if ($type == FundInitiativeTransaction::TYPE_REFUND) { - $total = $object->getTotalAsCurrency()->subtract($amount); - } else { - $total = $object->getTotalAsCurrency()->add($amount); - } - - $object->setTotalAsCurrency($total); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $type = $xaction->getTransactionType(); - switch ($type) { - case FundInitiativeTransaction::TYPE_NAME: - case FundInitiativeTransaction::TYPE_DESCRIPTION: - case FundInitiativeTransaction::TYPE_RISKS: - case FundInitiativeTransaction::TYPE_STATUS: - case FundInitiativeTransaction::TYPE_MERCHANT: - return; - case FundInitiativeTransaction::TYPE_BACKER: - case FundInitiativeTransaction::TYPE_REFUND: - $backer = id(new FundBackerQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs(array($xaction->getNewValue())) - ->executeOne(); - if (!$backer) { - throw new Exception(pht('Unable to load %s!', 'FundBacker')); - } - - $subx = array(); - - if ($type == FundInitiativeTransaction::TYPE_BACKER) { - $subx[] = id(new FundBackerTransaction()) - ->setTransactionType(FundBackerTransaction::TYPE_STATUS) - ->setNewValue(FundBacker::STATUS_PURCHASED); - } else { - $amount = $xaction->getMetadataValue( - FundInitiativeTransaction::PROPERTY_AMOUNT); - $subx[] = id(new FundBackerTransaction()) - ->setTransactionType(FundBackerTransaction::TYPE_STATUS) - ->setNewValue($amount); - } - - $editor = id(new FundBackerEditor()) - ->setActor($this->requireActor()) - ->setContentSource($this->getContentSource()) - ->setContinueOnMissingFields(true) - ->setContinueOnNoEffect(true); - - $editor->applyTransactions($backer, $subx); - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case FundInitiativeTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Initiative name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case FundInitiativeTransaction::TYPE_MERCHANT: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Payable merchant is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } else if ($xactions) { - $merchant_phid = last($xactions)->getNewValue(); - - // Make sure the actor has permission to edit the merchant they're - // selecting. You aren't allowed to send payments to an account you - // do not control. - $merchants = id(new PhortuneMerchantQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs(array($merchant_phid)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->execute(); - if (!$merchants) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'You must specify a merchant account you control as the '. - 'recipient of funds from this initiative.'), - last($xactions)); - $errors[] = $error; - } - } - break; - } - - return $errors; - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/fund/phortune/FundBackerProduct.php b/src/applications/fund/phortune/FundBackerProduct.php index 679a1a41d2..be0dbd9171 100644 --- a/src/applications/fund/phortune/FundBackerProduct.php +++ b/src/applications/fund/phortune/FundBackerProduct.php @@ -105,7 +105,7 @@ final class FundBackerProduct extends PhortuneProductImplementation { $xactions = array(); $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType(FundInitiativeTransaction::TYPE_BACKER) + ->setTransactionType(FundInitiativeBackerTransaction::TRANSACTIONTYPE) ->setMetadataValue( FundInitiativeTransaction::PROPERTY_AMOUNT, $backer->getAmountAsCurrency()->serializeForStorage()) @@ -134,7 +134,7 @@ final class FundBackerProduct extends PhortuneProductImplementation { $xactions = array(); $xactions[] = id(new FundInitiativeTransaction()) - ->setTransactionType(FundInitiativeTransaction::TYPE_REFUND) + ->setTransactionType(FundInitiativeRefundTransaction::TRANSACTIONTYPE) ->setMetadataValue( FundInitiativeTransaction::PROPERTY_AMOUNT, $amount->serializeForStorage()) diff --git a/src/applications/fund/query/FundBackerSearchEngine.php b/src/applications/fund/query/FundBackerSearchEngine.php index c275e58a0c..ba4b3bc39e 100644 --- a/src/applications/fund/query/FundBackerSearchEngine.php +++ b/src/applications/fund/query/FundBackerSearchEngine.php @@ -126,6 +126,7 @@ final class FundBackerSearchEngine } $table = id(new AphrontTableView($rows)) + ->setNoDataString(pht('No backers found.')) ->setHeaders( array( pht('Initiative'), diff --git a/src/applications/fund/query/FundInitiativeQuery.php b/src/applications/fund/query/FundInitiativeQuery.php index 66f87a883c..bbb4ef2746 100644 --- a/src/applications/fund/query/FundInitiativeQuery.php +++ b/src/applications/fund/query/FundInitiativeQuery.php @@ -8,8 +8,6 @@ final class FundInitiativeQuery private $ownerPHIDs; private $statuses; - private $needProjectPHIDs; - public function withIDs(array $ids) { $this->ids = $ids; return $this; @@ -30,87 +28,54 @@ final class FundInitiativeQuery return $this; } - public function needProjectPHIDs($need) { - $this->needProjectPHIDs = $need; - return $this; + public function newResultObject() { + return new FundInitiative(); } protected function loadPage() { - $table = new FundInitiative(); - $conn_r = $table->establishConnection('r'); - - $rows = 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($rows); + return $this->loadStandardPage($this->newResultObject()); } - protected function didFilterPage(array $initiatives) { - - if ($this->needProjectPHIDs) { - $edge_query = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs(mpull($initiatives, 'getPHID')) - ->withEdgeTypes( - array( - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, - )); - $edge_query->execute(); - - foreach ($initiatives as $initiative) { - $phids = $edge_query->getDestinationPHIDs( - array( - $initiative->getPHID(), - )); - $initiative->attachProjectPHIDs($phids); - } - } - - return $initiatives; - } - - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); - - $where[] = $this->buildPagingClause($conn_r); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, - 'id IN (%Ld)', + $conn, + 'i.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, - 'phid IN (%Ls)', + $conn, + 'i.phid IN (%Ls)', $this->phids); } if ($this->ownerPHIDs !== null) { $where[] = qsprintf( - $conn_r, - 'ownerPHID IN (%Ls)', + $conn, + 'i.ownerPHID IN (%Ls)', $this->ownerPHIDs); } if ($this->statuses !== null) { $where[] = qsprintf( - $conn_r, - 'status IN (%Ls)', + $conn, + 'i.status IN (%Ls)', $this->statuses); } - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { return 'PhabricatorFundApplication'; } + protected function getPrimaryTableAlias() { + return 'i'; + } + } diff --git a/src/applications/fund/query/FundInitiativeSearchEngine.php b/src/applications/fund/query/FundInitiativeSearchEngine.php index 3c339c1a64..e075fe5063 100644 --- a/src/applications/fund/query/FundInitiativeSearchEngine.php +++ b/src/applications/fund/query/FundInitiativeSearchEngine.php @@ -11,67 +11,37 @@ final class FundInitiativeSearchEngine return 'PhabricatorFundApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter( - 'ownerPHIDs', - $this->readUsersFromRequest($request, 'owners')); - - $saved->setParameter( - 'statuses', - $this->readListFromRequest($request, 'statuses')); - - return $saved; + public function newQuery() { + return new FundInitiativeQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new FundInitiativeQuery()) - ->needProjectPHIDs(true); + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorUsersSearchField()) + ->setKey('ownerPHIDs') + ->setAliases(array('owner', 'ownerPHID', 'owners')) + ->setLabel(pht('Owners')), + id(new PhabricatorSearchCheckboxesField()) + ->setKey('statuses') + ->setLabel(pht('Statuses')) + ->setOptions(FundInitiative::getStatusNameMap()), + ); + } - $owner_phids = $saved->getParameter('ownerPHIDs'); - if ($owner_phids) { - $query->withOwnerPHIDs($owner_phids); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['ownerPHIDs']) { + $query->withOwnerPHIDs($map['ownerPHIDs']); } - $statuses = $saved->getParameter('statuses'); - if ($statuses) { - $query->withStatuses($statuses); + if ($map['statuses']) { + $query->withStatuses($map['statuses']); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - - $statuses = $saved->getParameter('statuses', array()); - $statuses = array_fuse($statuses); - - $owner_phids = $saved->getParameter('ownerPHIDs', array()); - - $status_map = FundInitiative::getStatusNameMap(); - $status_control = id(new AphrontFormCheckboxControl()) - ->setLabel(pht('Statuses')); - foreach ($status_map as $status => $name) { - $status_control->addCheckbox( - 'statuses[]', - $status, - $name, - isset($statuses[$status])); - } - - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Owners')) - ->setName('owners') - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setValue($owner_phids)) - ->appendChild($status_control); - } - protected function getURI($path) { return '/fund/'.$path; } @@ -112,21 +82,6 @@ final class FundInitiativeSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } - protected function getRequiredHandlePHIDsForResultList( - array $initiatives, - PhabricatorSavedQuery $query) { - - $phids = array(); - foreach ($initiatives as $initiative) { - $phids[] = $initiative->getOwnerPHID(); - foreach ($initiative->getProjectPHIDs() as $project_phid) { - $phids[] = $project_phid; - } - } - - return $phids; - } - protected function renderResultList( array $initiatives, PhabricatorSavedQuery $query, @@ -135,7 +90,30 @@ final class FundInitiativeSearchEngine $viewer = $this->requireViewer(); - $list = id(new PHUIObjectItemListView()); + $load_phids = array(); + foreach ($initiatives as $initiative) { + $load_phids[] = $initiative->getOwnerPHID(); + } + + if ($initiatives) { + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(mpull($initiatives, 'getPHID')) + ->withEdgeTypes( + array( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + )); + + $edge_query->execute(); + + foreach ($edge_query->getDestinationPHIDs() as $phid) { + $load_phids[] = $phid; + } + } + + $handles = $viewer->loadHandles($load_phids); + $handles = iterator_to_array($handles); + + $list = new PHUIObjectItemListView(); foreach ($initiatives as $initiative) { $owner_handle = $handles[$initiative->getOwnerPHID()]; @@ -149,9 +127,12 @@ final class FundInitiativeSearchEngine $item->setDisabled(true); } - $project_handles = array_select_keys( - $handles, - $initiative->getProjectPHIDs()); + $project_phids = $edge_query->getDestinationPHIDs( + array( + $initiative->getPHID(), + )); + + $project_handles = array_select_keys($handles, $project_phids); if ($project_handles) { $item->addAttribute( id(new PHUIHandleTagListView()) @@ -168,9 +149,6 @@ final class FundInitiativeSearchEngine $result->setNoDataString(pht('No initiatives found.')); return $result; - - - return $list; } } diff --git a/src/applications/fund/search/FundInitiativeFerretEngine.php b/src/applications/fund/search/FundInitiativeFerretEngine.php new file mode 100644 index 0000000000..785b40b06f --- /dev/null +++ b/src/applications/fund/search/FundInitiativeFerretEngine.php @@ -0,0 +1,18 @@ +getID(); } + public function getViewURI() { + return '/'.$this->getMonogram(); + } + public function getProjectPHIDs() { return $this->assertAttached($this->projectPHIDs); } @@ -208,4 +213,12 @@ final class FundInitiative extends FundDAO return new FundInitiativeFulltextEngine(); } + +/* -( PhabricatorFerretInterface )----------------------------------------- */ + + + public function newFerretEngine() { + return new FundInitiativeFerretEngine(); + } + } diff --git a/src/applications/fund/storage/FundInitiativeTransaction.php b/src/applications/fund/storage/FundInitiativeTransaction.php index 5c1f8c589d..f6071878c5 100644 --- a/src/applications/fund/storage/FundInitiativeTransaction.php +++ b/src/applications/fund/storage/FundInitiativeTransaction.php @@ -1,15 +1,7 @@ getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_MERCHANT: - if ($old) { - $phids[] = $old; - } - if ($new) { - $phids[] = $new; - } - break; - case self::TYPE_REFUND: - $phids[] = $this->getMetadataValue(self::PROPERTY_BACKER); - break; - } - - return $phids; + public function getBaseTransactionClass() { + return 'FundInitiativeTransactionType'; } - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this initiative.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s renamed this initiative from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - case self::TYPE_RISKS: - return pht( - '%s edited the risks for this initiative.', - $this->renderHandleLink($author_phid)); - case self::TYPE_DESCRIPTION: - return pht( - '%s edited the description of this initiative.', - $this->renderHandleLink($author_phid)); - case self::TYPE_STATUS: - switch ($new) { - case FundInitiative::STATUS_OPEN: - return pht( - '%s reopened this initiative.', - $this->renderHandleLink($author_phid)); - case FundInitiative::STATUS_CLOSED: - return pht( - '%s closed this initiative.', - $this->renderHandleLink($author_phid)); - } - break; - case self::TYPE_BACKER: - $amount = $this->getMetadataValue(self::PROPERTY_AMOUNT); - $amount = PhortuneCurrency::newFromString($amount); - return pht( - '%s backed this initiative with %s.', - $this->renderHandleLink($author_phid), - $amount->formatForDisplay()); - case self::TYPE_REFUND: - $amount = $this->getMetadataValue(self::PROPERTY_AMOUNT); - $amount = PhortuneCurrency::newFromString($amount); - - $backer_phid = $this->getMetadataValue(self::PROPERTY_BACKER); - - return pht( - '%s refunded %s to %s.', - $this->renderHandleLink($author_phid), - $amount->formatForDisplay(), - $this->renderHandleLink($backer_phid)); - case self::TYPE_MERCHANT: - if ($old === null) { - return pht( - '%s set this initiative to pay to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); - } else { - return pht( - '%s changed the merchant receiving funds from this '. - 'initiative from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } else { - return pht( - '%s renamed %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_STATUS: - switch ($new) { - case FundInitiative::STATUS_OPEN: - return pht( - '%s reopened %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case FundInitiative::STATUS_CLOSED: - return pht( - '%s closed %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_BACKER: - $amount = $this->getMetadataValue(self::PROPERTY_AMOUNT); - $amount = PhortuneCurrency::newFromString($amount); - return pht( - '%s backed %s with %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $amount->formatForDisplay()); - case self::TYPE_REFUND: - $amount = $this->getMetadataValue(self::PROPERTY_AMOUNT); - $amount = PhortuneCurrency::newFromString($amount); - - $backer_phid = $this->getMetadataValue(self::PROPERTY_BACKER); - - return pht( - '%s refunded %s to %s for %s.', - $this->renderHandleLink($author_phid), - $amount->formatForDisplay(), - $this->renderHandleLink($backer_phid), - $this->renderHandleLink($object_phid)); - } - - return parent::getTitleForFeed(); + protected function shouldPublishFeedStory( + PhabricatorLiskDAO $object, + array $xactions) { + return true; } public function getMailTags() { $tags = parent::getMailTags(); switch ($this->getTransactionType()) { - case self::TYPE_STATUS: + case FundInitiativeStatusTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_STATUS; break; - case self::TYPE_BACKER: - case self::TYPE_REFUND: + case FundInitiativeBackerTransaction::TRANSACTIONTYPE: + case FundInitiativeRefundTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_BACKER; break; default: @@ -219,31 +51,4 @@ final class FundInitiativeTransaction return $tags; } - - public function shouldHide() { - $old = $this->getOldValue(); - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - case self::TYPE_RISKS: - return ($old === null); - } - return parent::shouldHide(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - case self::TYPE_RISKS: - return ($this->getOldValue() !== null); - } - - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); - } } diff --git a/src/applications/fund/xaction/FundBackerRefundTransaction.php b/src/applications/fund/xaction/FundBackerRefundTransaction.php new file mode 100644 index 0000000000..0aad952482 --- /dev/null +++ b/src/applications/fund/xaction/FundBackerRefundTransaction.php @@ -0,0 +1,13 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + +} diff --git a/src/applications/fund/xaction/FundBackerTransactionType.php b/src/applications/fund/xaction/FundBackerTransactionType.php new file mode 100644 index 0000000000..86ca6703d7 --- /dev/null +++ b/src/applications/fund/xaction/FundBackerTransactionType.php @@ -0,0 +1,4 @@ +getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); + $total = $object->getTotalAsCurrency()->add($amount); + $object->setTotalAsCurrency($total); + } + + public function applyExternalEffects($object, $value) { + $backer = id(new FundBackerQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($value)) + ->executeOne(); + if (!$backer) { + throw new Exception(pht('Unable to load %s!', 'FundBacker')); + } + + $subx = array(); + $subx[] = id(new FundBackerTransaction()) + ->setTransactionType(FundBackerStatusTransaction::TRANSACTIONTYPE) + ->setNewValue(FundBacker::STATUS_PURCHASED); + + $content_source = $this->getEditor()->getContentSource(); + + $editor = id(new FundBackerEditor()) + ->setActor($this->getActor()) + ->setContentSource($content_source) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($backer, $subx); + } + + public function getTitle() { + $amount = $this->getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); + return pht( + '%s backed this initiative with %s.', + $this->renderAuthor(), + $amount->formatForDisplay()); + } + + public function getTitleForFeed() { + $amount = $this->getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); + return pht( + '%s backed %s with %s.', + $this->renderAuthor(), + $this->renderObject(), + $amount->formatForDisplay()); + } + + public function getIcon() { + return 'fa-heart'; + } + + public function getColor() { + return 'red'; + } + + +} diff --git a/src/applications/fund/xaction/FundInitiativeDescriptionTransaction.php b/src/applications/fund/xaction/FundInitiativeDescriptionTransaction.php new file mode 100644 index 0000000000..f32b8499cf --- /dev/null +++ b/src/applications/fund/xaction/FundInitiativeDescriptionTransaction.php @@ -0,0 +1,75 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function shouldHide() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + if (!strlen($old) && !strlen($new)) { + return true; + } + return false; + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s set the initiative description.', + $this->renderAuthor()); + } else { + return pht( + '%s updated the initiative description.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + return pht( + '%s updated the initiative description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO INITIATIVE DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + +} diff --git a/src/applications/fund/xaction/FundInitiativeMerchantTransaction.php b/src/applications/fund/xaction/FundInitiativeMerchantTransaction.php new file mode 100644 index 0000000000..82bd6b5bf4 --- /dev/null +++ b/src/applications/fund/xaction/FundInitiativeMerchantTransaction.php @@ -0,0 +1,93 @@ +getMerchantPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setMerchantPHID($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + $new_merchant = $this->renderHandleList(array($new)); + + $old = $this->getOldValue(); + $old_merchant = $this->renderHandleList(array($old)); + + if ($old) { + return pht( + '%s changed the merchant receiving funds from this '. + 'initiative from %s to %s.', + $this->renderAuthor(), + $old_merchant, + $new_merchant); + } else { + return pht( + '%s set the merchant receiving funds from this '. + 'initiative to %s.', + $this->renderAuthor(), + $new_merchant); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + $new_merchant = $this->renderHandleList(array($new)); + + $old = $this->getOldValue(); + $old_merchant = $this->renderHandleList(array($old)); + + return pht( + '%s changed the merchant receiving funds from %s '. + 'from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $old_merchant, + $new_merchant); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getMerchantPHID(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Initiatives must have a payable merchant.')); + } + + foreach ($xactions as $xaction) { + $merchant_phid = $xaction->getNewValue(); + + // Make sure the actor has permission to edit the merchant they're + // selecting. You aren't allowed to send payments to an account you + // do not control. + $merchants = id(new PhortuneMerchantQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($merchant_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + if (!$merchants) { + $errors[] = $this->newInvalidError( + pht('You must specify a merchant account you control as the '. + 'recipient of funds from this initiative.')); + } + } + + return $errors; + } + + public function getIcon() { + return 'fa-bank'; + } + + +} diff --git a/src/applications/fund/xaction/FundInitiativeNameTransaction.php b/src/applications/fund/xaction/FundInitiativeNameTransaction.php new file mode 100644 index 0000000000..4a47c20a17 --- /dev/null +++ b/src/applications/fund/xaction/FundInitiativeNameTransaction.php @@ -0,0 +1,71 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s created this initiative.', + $this->renderAuthor()); + } else { + return pht( + '%s renamed this initiative from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s created initiative %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Initiatives must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The name can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + + +} diff --git a/src/applications/fund/xaction/FundInitiativeRefundTransaction.php b/src/applications/fund/xaction/FundInitiativeRefundTransaction.php new file mode 100644 index 0000000000..0741e29d10 --- /dev/null +++ b/src/applications/fund/xaction/FundInitiativeRefundTransaction.php @@ -0,0 +1,77 @@ +getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); + $total = $object->getTotalAsCurrency()->subtract($amount); + $object->setTotalAsCurrency($total); + } + + public function applyExternalEffects($object, $value) { + $backer = id(new FundBackerQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($value)) + ->executeOne(); + if (!$backer) { + throw new Exception(pht('Unable to load %s!', 'FundBacker')); + } + + $subx = array(); + $amount = $this->getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $subx[] = id(new FundBackerTransaction()) + ->setTransactionType(FundBackerStatusTransaction::TRANSACTIONTYPE) + ->setNewValue($amount); + + $content_source = $this->getEditor()->getContentSource(); + + $editor = id(new FundBackerEditor()) + ->setActor($this->getActor()) + ->setContentSource($content_source) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($backer, $subx); + } + + public function getTitle() { + $amount = $this->getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); + $backer_phid = $this->getMetadataValue( + FundInitiativeTransaction::PROPERTY_BACKER); + + return pht( + '%s refunded %s to %s.', + $this->renderAuthor(), + $amount->formatForDisplay(), + $this->renderHandle($backer_phid)); + } + + public function getTitleForFeed() { + $amount = $this->getMetadataValue( + FundInitiativeTransaction::PROPERTY_AMOUNT); + $amount = PhortuneCurrency::newFromString($amount); + $backer_phid = $this->getMetadataValue( + FundInitiativeTransaction::PROPERTY_BACKER); + + return pht( + '%s refunded %s to %s for %s.', + $this->renderAuthor(), + $amount->formatForDisplay(), + $this->renderHandle($backer_phid), + $this->renderObject()); + } + + +} diff --git a/src/applications/fund/xaction/FundInitiativeRisksTransaction.php b/src/applications/fund/xaction/FundInitiativeRisksTransaction.php new file mode 100644 index 0000000000..183f9caa67 --- /dev/null +++ b/src/applications/fund/xaction/FundInitiativeRisksTransaction.php @@ -0,0 +1,80 @@ +getRisks(); + } + + public function applyInternalEffects($object, $value) { + $object->setRisks($value); + } + + public function shouldHide() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + if (!strlen($old) && !strlen($new)) { + return true; + } + return false; + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s set the initiative risks/challenges.', + $this->renderAuthor()); + } else { + return pht( + '%s updated the initiative risks/challenges.', + $this->renderAuthor()); + } + + } + + public function getTitleForFeed() { + return pht( + '%s updated the initiative risks/challenges for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO INITIATIVE RISKS/CHALLENGES'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + public function getIcon() { + return 'fa-ambulance'; + } + + +} diff --git a/src/applications/fund/xaction/FundInitiativeStatusTransaction.php b/src/applications/fund/xaction/FundInitiativeStatusTransaction.php new file mode 100644 index 0000000000..5c0af0e705 --- /dev/null +++ b/src/applications/fund/xaction/FundInitiativeStatusTransaction.php @@ -0,0 +1,51 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + if ($this->getNewValue() == FundInitiative::STATUS_CLOSED) { + return pht( + '%s closed this initiative.', + $this->renderAuthor()); + } else { + return pht( + '%s reopened this initiative.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + if ($this->getNewValue() == FundInitiative::STATUS_CLOSED) { + return pht( + '%s closed %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s reopened %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + if ($this->getNewValue() == FundInitiative::STATUS_CLOSED) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + + +} diff --git a/src/applications/fund/xaction/FundInitiativeTransactionType.php b/src/applications/fund/xaction/FundInitiativeTransactionType.php new file mode 100644 index 0000000000..6bcd17f1c7 --- /dev/null +++ b/src/applications/fund/xaction/FundInitiativeTransactionType.php @@ -0,0 +1,4 @@ +setText(pht('Skip')) ->setTag('a') ->setHref($skip_href) - ->setColor(PHUIButtonView::GREY); - $list_item->setLaunchButton($skip); + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE); + $list_item->setSideColumn($skip); } $list->addItem($list_item); } diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php index 7932451d2e..5b7171a200 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -141,7 +141,7 @@ final class HarbormasterBuildViewController if ($ended) { $when[] = pht( 'Completed at %s', - phabricator_datetime($started, $viewer)); + phabricator_datetime($ended, $viewer)); $duration = ($ended - $started); if ($duration) { diff --git a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php index 28f79ee181..e5a66a3eb8 100644 --- a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php +++ b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php @@ -56,7 +56,7 @@ final class HarbormasterUnitSummaryView extends AphrontView { $tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($tag_color) + ->setColor($tag_color) ->setIcon($tag_icon) ->setName($tag_text); diff --git a/src/applications/herald/controller/HeraldNewController.php b/src/applications/herald/controller/HeraldNewController.php index e9fa1e66bd..fbaf1aeb9e 100644 --- a/src/applications/herald/controller/HeraldNewController.php +++ b/src/applications/herald/controller/HeraldNewController.php @@ -194,8 +194,9 @@ final class HeraldNewController extends HeraldController { ->addCancelButton($cancel_uri, $cancel_text)); $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); $crumbs = $this @@ -203,12 +204,7 @@ final class HeraldNewController extends HeraldController { ->addTextCrumb(pht('Create Rule')) ->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-plus-square'); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter($form_box); return $this->newPage() diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index 74ce553f9c..eaf352a0a7 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -191,7 +191,7 @@ final class HeraldRuleController extends HeraldController { 'a', array( 'href' => '#', - 'class' => 'button green', + 'class' => 'button button-green', 'sigil' => 'create-condition', 'mustcapture' => true, ), @@ -212,7 +212,7 @@ final class HeraldRuleController extends HeraldController { 'a', array( 'href' => '#', - 'class' => 'button green', + 'class' => 'button button-green', 'sigil' => 'create-action', 'mustcapture' => true, ), @@ -238,9 +238,9 @@ final class HeraldRuleController extends HeraldController { ? pht('Edit Herald Rule: %s', $rule->getName()) : pht('Create Herald Rule: %s', idx($content_type_map, $content_type)); - $icon = $rule->getID() ? 'fa-pencil' : 'fa-plus-square'; - $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setFormErrors($errors) ->setForm($form); @@ -249,12 +249,7 @@ final class HeraldRuleController extends HeraldController { ->addTextCrumb($title) ->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-plus-square'); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter($form_box); return $this->newPage() diff --git a/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php b/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php index dc638a018a..5988d39f46 100644 --- a/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php +++ b/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php @@ -67,7 +67,7 @@ final class PhabricatorHomeProfileMenuEngine $items[] = $this->newItem() ->setBuiltinKey(PhabricatorHomeConstants::ITEM_APPS_LABEL) ->setMenuItemKey(PhabricatorLabelProfileMenuItem::MENUITEMKEY) - ->setMenuItemProperties(array('name' => pht('Applications'))); + ->setMenuItemProperties(array('name' => pht('Favorites'))); foreach ($applications as $application) { if (!$application->isPinnedByDefault($viewer)) { diff --git a/src/applications/legalpad/application/PhabricatorLegalpadApplication.php b/src/applications/legalpad/application/PhabricatorLegalpadApplication.php index 709fd7cc07..a3eaff5792 100644 --- a/src/applications/legalpad/application/PhabricatorLegalpadApplication.php +++ b/src/applications/legalpad/application/PhabricatorLegalpadApplication.php @@ -53,10 +53,10 @@ final class PhabricatorLegalpadApplication extends PhabricatorApplication { '/L(?P\d+)' => 'LegalpadDocumentSignController', '/legalpad/' => array( '' => 'LegalpadDocumentListController', - '(?:query/(?P[^/]+)/)?' => 'LegalpadDocumentListController', - 'create/' => 'LegalpadDocumentEditController', - 'edit/(?P\d+)/' => 'LegalpadDocumentEditController', - 'comment/(?P\d+)/' => 'LegalpadDocumentCommentController', + '(?:query/(?P[^/]+)/)?' + => 'LegalpadDocumentListController', + $this->getEditRoutePattern('edit/') + => 'LegalpadDocumentEditController', 'view/(?P\d+)/' => 'LegalpadDocumentManageController', 'done/' => 'LegalpadDocumentDoneController', 'verify/(?P[^/]+)/' diff --git a/src/applications/legalpad/controller/LegalpadController.php b/src/applications/legalpad/controller/LegalpadController.php index fbfb7038f4..006f1329d5 100644 --- a/src/applications/legalpad/controller/LegalpadController.php +++ b/src/applications/legalpad/controller/LegalpadController.php @@ -9,7 +9,7 @@ abstract class LegalpadController extends PhabricatorController { $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); if ($for_app) { - $nav->addFilter('create/', pht('Create Document')); + $nav->addFilter('edit/', pht('Create Document')); } id(new LegalpadDocumentSearchEngine()) diff --git a/src/applications/legalpad/controller/LegalpadDocumentCommentController.php b/src/applications/legalpad/controller/LegalpadDocumentCommentController.php deleted file mode 100644 index 530696437b..0000000000 --- a/src/applications/legalpad/controller/LegalpadDocumentCommentController.php +++ /dev/null @@ -1,72 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - if (!$request->isFormPost()) { - return new Aphront400Response(); - } - - $document = id(new LegalpadDocumentQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needDocumentBodies(true) - ->executeOne(); - - if (!$document) { - return new Aphront404Response(); - } - - $is_preview = $request->isPreviewRequest(); - - $draft = PhabricatorDraft::buildFromRequest($request); - - $document_uri = $this->getApplicationURI('view/'.$document->getID()); - - $comment = $request->getStr('comment'); - - $xactions = array(); - - if (strlen($comment)) { - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) - ->attachComment( - id(new LegalpadTransactionComment()) - ->setDocumentID($document->getID()) - ->setLineNumber(0) - ->setLineLength(0) - ->setContent($comment)); - } - - $editor = id(new LegalpadDocumentEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect($request->isContinueRequest()) - ->setIsPreview($is_preview); - - try { - $xactions = $editor->applyTransactions($document, $xactions); - } catch (PhabricatorApplicationTransactionNoEffectException $ex) { - return id(new PhabricatorApplicationTransactionNoEffectResponse()) - ->setCancelURI($document_uri) - ->setException($ex); - } - - if ($draft) { - $draft->replaceOrDelete(); - } - - if ($request->isAjax() && $is_preview) { - return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($viewer) - ->setTransactions($xactions) - ->setIsPreview($is_preview); - } else { - return id(new AphrontRedirectResponse())->setURI($document_uri); - } - } - -} diff --git a/src/applications/legalpad/controller/LegalpadDocumentEditController.php b/src/applications/legalpad/controller/LegalpadDocumentEditController.php index 8f84f60339..be46a55dab 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentEditController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentEditController.php @@ -3,270 +3,9 @@ final class LegalpadDocumentEditController extends LegalpadController { public function handleRequest(AphrontRequest $request) { - $viewer = $request->getViewer(); - $id = $request->getURIData('id'); - - if (!$id) { - $is_create = true; - - $this->requireApplicationCapability( - LegalpadCreateDocumentsCapability::CAPABILITY); - - $document = LegalpadDocument::initializeNewDocument($viewer); - $body = id(new LegalpadDocumentBody()) - ->setCreatorPHID($viewer->getPHID()); - $document->attachDocumentBody($body); - $document->setDocumentBodyPHID(PhabricatorPHIDConstants::PHID_VOID); - } else { - $is_create = false; - - $document = id(new LegalpadDocumentQuery()) - ->setViewer($viewer) - ->needDocumentBodies(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($id)) - ->executeOne(); - if (!$document) { - return new Aphront404Response(); - } - } - - $e_title = true; - $e_text = true; - - $title = $document->getDocumentBody()->getTitle(); - $text = $document->getDocumentBody()->getText(); - $v_signature_type = $document->getSignatureType(); - $v_preamble = $document->getPreamble(); - $v_require_signature = $document->getRequireSignature(); - - $errors = array(); - $can_view = null; - $can_edit = null; - if ($request->isFormPost()) { - - $xactions = array(); - - $title = $request->getStr('title'); - if (!strlen($title)) { - $e_title = pht('Required'); - $errors[] = pht('The document title may not be blank.'); - } else { - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(LegalpadTransaction::TYPE_TITLE) - ->setNewValue($title); - } - - $text = $request->getStr('text'); - if (!strlen($text)) { - $e_text = pht('Required'); - $errors[] = pht('The document may not be blank.'); - } else { - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(LegalpadTransaction::TYPE_TEXT) - ->setNewValue($text); - } - - $can_view = $request->getStr('can_view'); - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($can_view); - $can_edit = $request->getStr('can_edit'); - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) - ->setNewValue($can_edit); - - if ($is_create) { - $v_signature_type = $request->getStr('signatureType'); - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(LegalpadTransaction::TYPE_SIGNATURE_TYPE) - ->setNewValue($v_signature_type); - } - - $v_preamble = $request->getStr('preamble'); - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(LegalpadTransaction::TYPE_PREAMBLE) - ->setNewValue($v_preamble); - - $v_require_signature = $request->getBool('requireSignature', 0); - if ($v_require_signature) { - if (!$viewer->getIsAdmin()) { - $errors[] = pht('Only admins may require signature.'); - } - $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; - if ($v_signature_type != $individual) { - $errors[] = pht( - 'Only documents with signature type "individual" may require '. - 'signing to use Phabricator.'); - } - } - if ($viewer->getIsAdmin()) { - $xactions[] = id(new LegalpadTransaction()) - ->setTransactionType(LegalpadTransaction::TYPE_REQUIRE_SIGNATURE) - ->setNewValue($v_require_signature); - } - - if (!$errors) { - $editor = id(new LegalpadDocumentEditor()) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->setActor($viewer); - - $xactions = $editor->applyTransactions($document, $xactions); - - return id(new AphrontRedirectResponse()) - ->setURI($this->getApplicationURI('view/'.$document->getID())); - } - } - - if ($errors) { - // set these to what was specified in the form on post - $document->setViewPolicy($can_view); - $document->setEditPolicy($can_edit); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setID('document-title') - ->setLabel(pht('Title')) - ->setError($e_title) - ->setValue($title) - ->setName('title')); - - if ($is_create) { - $form->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Who Should Sign?')) - ->setName(pht('signatureType')) - ->setValue($v_signature_type) - ->setOptions(LegalpadDocument::getSignatureTypeMap())); - $show_require = true; - $caption = pht('Applies only to documents individuals sign.'); - } else { - $form->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Who Should Sign?')) - ->setValue($document->getSignatureTypeName())); - $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; - $show_require = $document->getSignatureType() == $individual; - $caption = null; - } - - if ($show_require) { - $form - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->setDisabled(!$viewer->getIsAdmin()) - ->setLabel(pht('Require Signature')) - ->addCheckbox( - 'requireSignature', - 'requireSignature', - pht('Should signing this document be required to use Phabricator?'), - $v_require_signature) - ->setCaption($caption)); - } - - $form - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setID('preamble') - ->setLabel(pht('Preamble')) - ->setValue($v_preamble) - ->setName('preamble') - ->setCaption( - pht('Optional help text for users signing this document.'))) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setID('document-text') - ->setLabel(pht('Document Body')) - ->setError($e_text) - ->setValue($text) - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) - ->setName('text')); - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($document) - ->execute(); - - $form - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicyObject($document) - ->setPolicies($policies) - ->setName('can_view')) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicyObject($document) - ->setPolicies($policies) - ->setName('can_edit')); - - $crumbs = $this->buildApplicationCrumbs(); - $submit = new AphrontFormSubmitControl(); - if ($is_create) { - $submit->setValue(pht('Create Document')); - $submit->addCancelButton($this->getApplicationURI()); - $title = pht('Create Document'); - $short = pht('Create'); - $header_icon = 'fa-plus-square'; - } else { - $submit->setValue(pht('Save Document')); - $submit->addCancelButton( - $this->getApplicationURI('view/'.$document->getID())); - $title = pht('Edit Document: %s', $document->getTitle()); - $short = pht('Edit'); - $header_icon = 'fa-pencil'; - - $crumbs->addTextCrumb( - $document->getMonogram(), - $this->getApplicationURI('view/'.$document->getID())); - } - - $form->appendChild($submit); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Document')) - ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $crumbs->addTextCrumb($short); - $crumbs->setBorder(true); - - $preview = id(new PHUIRemarkupPreviewPanel()) - ->setHeader($document->getTitle()) - ->setPreviewURI($this->getApplicationURI('document/preview/')) - ->setControlID('document-text') - ->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - $preview, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - + return id(new LegalpadDocumentEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/legalpad/controller/LegalpadDocumentListController.php b/src/applications/legalpad/controller/LegalpadDocumentListController.php index cbd92f87a9..852f71970d 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentListController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentListController.php @@ -26,7 +26,7 @@ final class LegalpadDocumentListController extends LegalpadController { $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Document')) - ->setHref($this->getApplicationURI('create/')) + ->setHref($this->getApplicationURI('edit/')) ->setIcon('fa-plus-square') ->setDisabled(!$can_create) ->setWorkflow(!$can_create)); diff --git a/src/applications/legalpad/controller/LegalpadDocumentManageController.php b/src/applications/legalpad/controller/LegalpadDocumentManageController.php index 6d9360a731..b39bc89270 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentManageController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentManageController.php @@ -37,6 +37,7 @@ final class LegalpadDocumentManageController extends LegalpadController { $document, new LegalpadTransactionQuery(), $engine); + $timeline->setQuoteRef($document->getMonogram()); $title = $document_body->getTitle(); @@ -50,9 +51,7 @@ final class LegalpadDocumentManageController extends LegalpadController { $properties = $this->buildPropertyView($document, $engine); $document_view = $this->buildDocumentView($document, $engine); - $comment_form_id = celerity_generate_unique_node_id(); - - $add_comment = $this->buildAddCommentView($document, $comment_form_id); + $comment_form = $this->buildCommentView($document, $timeline); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( @@ -69,7 +68,7 @@ final class LegalpadDocumentManageController extends LegalpadController { $properties, $document_view, $timeline, - $add_comment, + $comment_form, )); return $this->newPage() @@ -181,31 +180,14 @@ final class LegalpadDocumentManageController extends LegalpadController { ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); } - private function buildAddCommentView( - LegalpadDocument $document, - $comment_form_id) { + private function buildCommentView(LegalpadDocument $document, $timeline) { $viewer = $this->getViewer(); + $box = id(new LegalpadDocumentEditEngine()) + ->setViewer($viewer) + ->buildEditEngineCommentView($document) + ->setTransactionTimeline($timeline); - $draft = PhabricatorDraft::newFromUserAndKey($viewer, $document->getPHID()); - - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - - $title = $is_serious - ? pht('Add Comment') - : pht('Debate Legislation'); - - $form = id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setObjectPHID($document->getPHID()) - ->setFormID($comment_form_id) - ->setHeaderText($title) - ->setDraft($draft) - ->setSubmitButtonName(pht('Add Comment')) - ->setAction($this->getApplicationURI('/comment/'.$document->getID().'/')) - ->setRequestURI($this->getRequest()->getRequestURI()); - - return $form; - + return $box; } } diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditEngine.php b/src/applications/legalpad/editor/LegalpadDocumentEditEngine.php new file mode 100644 index 0000000000..814647b82a --- /dev/null +++ b/src/applications/legalpad/editor/LegalpadDocumentEditEngine.php @@ -0,0 +1,169 @@ +getViewer(); + + $document = LegalpadDocument::initializeNewDocument($viewer); + $body = id(new LegalpadDocumentBody()) + ->setCreatorPHID($viewer->getPHID()); + $document->attachDocumentBody($body); + $document->setDocumentBodyPHID(PhabricatorPHIDConstants::PHID_VOID); + + return $document; + } + + protected function newObjectQuery() { + return id(new LegalpadDocumentQuery()) + ->needDocumentBodies(true); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Document'); + } + + protected function getObjectEditTitleText($object) { + $body = $object->getDocumentBody(); + $title = $body->getTitle(); + return pht('Edit Document: %s', $title); + } + + protected function getObjectEditShortText($object) { + $body = $object->getDocumentBody(); + return $body->getTitle(); + } + + protected function getObjectCreateShortText() { + return pht('Create Document'); + } + + protected function getObjectName() { + return pht('Document'); + } + + protected function getObjectCreateCancelURI($object) { + return $this->getApplication()->getApplicationURI('/'); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('edit/'); + } + + protected function getObjectViewURI($object) { + $id = $object->getID(); + return $this->getApplication()->getApplicationURI('view/'.$id.'/'); + } + + + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + LegalpadCreateDocumentsCapability::CAPABILITY); + } + + protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + + $body = $object->getDocumentBody(); + $document_body = $body->getText(); + + $is_create = $this->getIsCreate(); + $is_admin = $viewer->getIsAdmin(); + + $fields = array(); + $fields[] = + id(new PhabricatorTextEditField()) + ->setKey('title') + ->setLabel(pht('Title')) + ->setDescription(pht('Document Title.')) + ->setConduitTypeDescription(pht('New document title.')) + ->setValue($object->getTitle()) + ->setIsRequired(true) + ->setTransactionType( + LegalpadDocumentTitleTransaction::TRANSACTIONTYPE); + + if ($is_create) { + $fields[] = + id(new PhabricatorSelectEditField()) + ->setKey('signatureType') + ->setLabel(pht('Who Should Sign?')) + ->setDescription(pht('Type of signature required')) + ->setConduitTypeDescription(pht('New document signature type.')) + ->setValue($object->getSignatureType()) + ->setOptions(LegalpadDocument::getSignatureTypeMap()) + ->setTransactionType( + LegalpadDocumentSignatureTypeTransaction::TRANSACTIONTYPE); + $show_require = true; + } else { + $fields[] = id(new PhabricatorStaticEditField()) + ->setLabel(pht('Who Should Sign?')) + ->setValue($object->getSignatureTypeName()); + $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; + $show_require = $object->getSignatureType() == $individual; + } + + if ($show_require && $is_admin) { + $fields[] = + id(new PhabricatorBoolEditField()) + ->setKey('requireSignature') + ->setOptions( + pht('No Signature Required'), + pht('Signature Required to use Phabricator')) + ->setAsCheckbox(true) + ->setTransactionType( + LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Marks this document as required signing.')) + ->setConduitDescription( + pht('Marks this document as required signing.')) + ->setValue($object->getRequireSignature()); + } + + $fields[] = + id(new PhabricatorRemarkupEditField()) + ->setKey('preamble') + ->setLabel(pht('Preamble')) + ->setDescription(pht('The preamble of the document.')) + ->setConduitTypeDescription(pht('New document preamble.')) + ->setValue($object->getPreamble()) + ->setTransactionType( + LegalpadDocumentPreambleTransaction::TRANSACTIONTYPE); + + $fields[] = + id(new PhabricatorRemarkupEditField()) + ->setKey('text') + ->setLabel(pht('Document Body')) + ->setDescription(pht('The body of text of the document.')) + ->setConduitTypeDescription(pht('New document body.')) + ->setValue($document_body) + ->setIsRequired(true) + ->setTransactionType( + LegalpadDocumentTextTransaction::TRANSACTIONTYPE); + + return $fields; + + } + +} diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditor.php b/src/applications/legalpad/editor/LegalpadDocumentEditor.php index 5e319b5905..35f2487a81 100644 --- a/src/applications/legalpad/editor/LegalpadDocumentEditor.php +++ b/src/applications/legalpad/editor/LegalpadDocumentEditor.php @@ -3,8 +3,6 @@ final class LegalpadDocumentEditor extends PhabricatorApplicationTransactionEditor { - private $isContribution = false; - public function getEditorApplicationClass() { return 'PhabricatorLegalpadApplication'; } @@ -13,15 +11,6 @@ final class LegalpadDocumentEditor return pht('Legalpad Documents'); } - private function setIsContribution($is_contribution) { - $this->isContribution = $is_contribution; - return $this; - } - - private function isContribution() { - return $this->isContribution; - } - public function getTransactionTypes() { $types = parent::getTransactionTypes(); @@ -29,111 +18,50 @@ final class LegalpadDocumentEditor $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = LegalpadTransaction::TYPE_TITLE; - $types[] = LegalpadTransaction::TYPE_TEXT; - $types[] = LegalpadTransaction::TYPE_SIGNATURE_TYPE; - $types[] = LegalpadTransaction::TYPE_PREAMBLE; - $types[] = LegalpadTransaction::TYPE_REQUIRE_SIGNATURE; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case LegalpadTransaction::TYPE_TITLE: - return $object->getDocumentBody()->getTitle(); - case LegalpadTransaction::TYPE_TEXT: - return $object->getDocumentBody()->getText(); - case LegalpadTransaction::TYPE_SIGNATURE_TYPE: - return $object->getSignatureType(); - case LegalpadTransaction::TYPE_PREAMBLE: - return $object->getPreamble(); - case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: - return (bool)$object->getRequireSignature(); - } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this document.', $author); } - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case LegalpadTransaction::TYPE_TITLE: - case LegalpadTransaction::TYPE_TEXT: - case LegalpadTransaction::TYPE_SIGNATURE_TYPE: - case LegalpadTransaction::TYPE_PREAMBLE: - return $xaction->getNewValue(); - case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: - return (bool)$xaction->getNewValue(); - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case LegalpadTransaction::TYPE_TITLE: - $object->setTitle($xaction->getNewValue()); - $body = $object->getDocumentBody(); - $body->setTitle($xaction->getNewValue()); - $this->setIsContribution(true); - break; - case LegalpadTransaction::TYPE_TEXT: - $body = $object->getDocumentBody(); - $body->setText($xaction->getNewValue()); - $this->setIsContribution(true); - break; - case LegalpadTransaction::TYPE_SIGNATURE_TYPE: - $object->setSignatureType($xaction->getNewValue()); - break; - case LegalpadTransaction::TYPE_PREAMBLE: - $object->setPreamble($xaction->getNewValue()); - break; - case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: - $object->setRequireSignature((int)$xaction->getNewValue()); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: - if ($xaction->getNewValue()) { - $session = new PhabricatorAuthSession(); - queryfx( - $session->establishConnection('w'), - 'UPDATE %T SET signedLegalpadDocuments = 0', - $session->getTableName()); - } - break; - } - return; + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); } protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { - if ($this->isContribution()) { + $is_contribution = false; + + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case LegalpadDocumentTitleTransaction::TRANSACTIONTYPE: + case LegalpadDocumentTextTransaction::TRANSACTIONTYPE: + $is_contribution = true; + break; + } + } + + if ($is_contribution) { + $text = $object->getDocumentBody()->getText(); + $title = $object->getDocumentBody()->getTitle(); $object->setVersions($object->getVersions() + 1); - $body = $object->getDocumentBody(); + + $body = new LegalpadDocumentBody(); + $body->setCreatorPHID($this->getActingAsPHID()); + $body->setText($text); + $body->setTitle($title); $body->setVersion($object->getVersions()); $body->setDocumentPHID($object->getPHID()); $body->save(); $object->setDocumentBodyPHID($body->getPHID()); - $actor = $this->getActor(); $type = PhabricatorContributedToObjectEdgeType::EDGECONST; id(new PhabricatorEdgeEditor()) - ->addEdge($actor->getPHID(), $type, $object->getPHID()) + ->addEdge($this->getActingAsPHID(), $type, $object->getPHID()) ->save(); $type = PhabricatorObjectHasContributorEdgeType::EDGECONST; @@ -149,23 +77,38 @@ final class LegalpadDocumentEditor return $xactions; } - protected function mergeTransactions( - PhabricatorApplicationTransaction $u, - PhabricatorApplicationTransaction $v) { + protected function validateAllTransactions(PhabricatorLiskDAO $object, + array $xactions) { + $errors = array(); - $type = $u->getTransactionType(); - switch ($type) { - case LegalpadTransaction::TYPE_TITLE: - case LegalpadTransaction::TYPE_TEXT: - case LegalpadTransaction::TYPE_SIGNATURE_TYPE: - case LegalpadTransaction::TYPE_PREAMBLE: - case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: - return $v; + $is_required = (bool)$object->getRequireSignature(); + $document_type = $object->getSignatureType(); + $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; + + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE: + $is_required = (bool)$xaction->getNewValue(); + break; + case LegalpadDocumentSignatureTypeTransaction::TRANSACTIONTYPE: + $document_type = $xaction->getNewValue(); + break; + } } - return parent::mergeTransactions($u, $v); + if ($is_required && ($document_type != $individual)) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE, + pht('Invalid'), + pht('Only documents with signature type "individual" may '. + 'require signing to use Phabricator.'), + null); + } + + return $errors; } + /* -( Sending Mail )------------------------------------------------------- */ protected function shouldSendMail( @@ -201,10 +144,10 @@ final class LegalpadDocumentEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case LegalpadTransaction::TYPE_TEXT: - case LegalpadTransaction::TYPE_TITLE: - case LegalpadTransaction::TYPE_PREAMBLE: - case LegalpadTransaction::TYPE_REQUIRE_SIGNATURE: + case LegalpadDocumentTextTransaction::TRANSACTIONTYPE: + case LegalpadDocumentTitleTransaction::TRANSACTIONTYPE: + case LegalpadDocumentPreambleTransaction::TRANSACTIONTYPE: + case LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE: return true; } diff --git a/src/applications/legalpad/storage/LegalpadDocument.php b/src/applications/legalpad/storage/LegalpadDocument.php index fcecac991f..55d1ef9fa9 100644 --- a/src/applications/legalpad/storage/LegalpadDocument.php +++ b/src/applications/legalpad/storage/LegalpadDocument.php @@ -120,6 +120,10 @@ final class LegalpadDocument extends LegalpadDAO return 'L'.$this->getID(); } + public function getViewURI() { + return '/'.$this->getMonogram(); + } + public function getUserSignature($phid) { return $this->assertAttachedKey($this->userSignatures, $phid); } diff --git a/src/applications/legalpad/storage/LegalpadTransaction.php b/src/applications/legalpad/storage/LegalpadTransaction.php index f85c569279..c43c86c5fc 100644 --- a/src/applications/legalpad/storage/LegalpadTransaction.php +++ b/src/applications/legalpad/storage/LegalpadTransaction.php @@ -1,12 +1,6 @@ getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - case self::TYPE_TEXT: - return ($old === null); - case self::TYPE_SIGNATURE_TYPE: - return true; - } - - return parent::shouldHide(); - } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_TITLE: - return pht( - '%s renamed this document from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - case self::TYPE_TEXT: - return pht( - "%s updated the document's text.", - $this->renderHandleLink($author_phid)); - case self::TYPE_PREAMBLE: - return pht( - '%s updated the preamble.', - $this->renderHandleLink($author_phid)); - case self::TYPE_REQUIRE_SIGNATURE: - if ($new) { - $text = pht( - '%s set the document to require signatures.', - $this->renderHandleLink($author_phid)); - } else { - $text = pht( - '%s set the document to not require signatures.', - $this->renderHandleLink($author_phid)); - } - return $text; - } - - return parent::getTitle(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - case self::TYPE_TEXT: - case self::TYPE_PREAMBLE: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); + public function getBaseTransactionClass() { + return 'LegalpadDocumentTransactionType'; } } diff --git a/src/applications/legalpad/xaction/LegalpadDocumentPreambleTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentPreambleTransaction.php new file mode 100644 index 0000000000..a2b5e1f5cd --- /dev/null +++ b/src/applications/legalpad/xaction/LegalpadDocumentPreambleTransaction.php @@ -0,0 +1,57 @@ +getPreamble(); + } + + public function applyInternalEffects($object, $value) { + $object->setPreamble($value); + } + + public function getTitle() { + return pht( + '%s updated the document preamble.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the document preamble for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO DOCUMENT PREAMBLE'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + +} diff --git a/src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php new file mode 100644 index 0000000000..3819f38a70 --- /dev/null +++ b/src/applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php @@ -0,0 +1,72 @@ +getRequireSignature(); + } + + public function applyInternalEffects($object, $value) { + $object->setRequireSignature((int)$value); + } + + public function applyExternalEffects($object, $value) { + if ($value) { + $session = new PhabricatorAuthSession(); + queryfx( + $session->establishConnection('w'), + 'UPDATE %T SET signedLegalpadDocuments = 0', + $session->getTableName()); + } + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s set the document to require signatures.', + $this->renderAuthor()); + } else { + return pht( + '%s set the document to not require signatures.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s set the document %s to require signatures.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s set the document %s to not require signatures.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $is_admin = $this->getActor()->getIsAdmin(); + + if (!$is_admin) { + $errors[] = $this->newInvalidError( + pht('Only admins may require signature.')); + } + + return $errors; + } + + public function getIcon() { + return 'fa-pencil-square'; + } + +} diff --git a/src/applications/legalpad/xaction/LegalpadDocumentSignatureTypeTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentSignatureTypeTransaction.php new file mode 100644 index 0000000000..0fe42f0a6f --- /dev/null +++ b/src/applications/legalpad/xaction/LegalpadDocumentSignatureTypeTransaction.php @@ -0,0 +1,29 @@ +getSignatureType(); + } + + public function applyInternalEffects($object, $value) { + $object->setSignatureType($value); + } + + public function getTitle() { + return pht( + '%s updated the document signature type.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the document signature type for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + +} diff --git a/src/applications/legalpad/xaction/LegalpadDocumentTextTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentTextTransaction.php new file mode 100644 index 0000000000..7c28002da2 --- /dev/null +++ b/src/applications/legalpad/xaction/LegalpadDocumentTextTransaction.php @@ -0,0 +1,68 @@ +getDocumentBody(); + return $body->getText(); + } + + public function applyInternalEffects($object, $value) { + $body = $object->getDocumentBody(); + $body->setText($value); + $object->attachDocumentBody($body); + } + + public function getTitle() { + $old = $this->getOldValue(); + + if (!strlen($old)) { + return pht( + '%s set the document text.', + $this->renderAuthor()); + } else { + return pht( + '%s updated the document text.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + return pht( + '%s updated the document text for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO DOCUMENT TEXT'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + +} diff --git a/src/applications/legalpad/xaction/LegalpadDocumentTitleTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentTitleTransaction.php new file mode 100644 index 0000000000..725f4f4d4c --- /dev/null +++ b/src/applications/legalpad/xaction/LegalpadDocumentTitleTransaction.php @@ -0,0 +1,75 @@ +getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setTitle($value); + $body = $object->getDocumentBody(); + $body->setTitle($value); + $object->attachDocumentBody($body); + } + + public function getTitle() { + $old = $this->getOldValue(); + + if (!strlen($old)) { + return pht( + '%s created this document.', + $this->renderAuthor()); + } else { + return pht( + '%s renamed this document from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + + if (!strlen($old)) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getTitle(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Documents must have a title.')); + } + + $max_length = $object->getColumnMaximumByteLength('title'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The title can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/legalpad/xaction/LegalpadDocumentTransactionType.php b/src/applications/legalpad/xaction/LegalpadDocumentTransactionType.php new file mode 100644 index 0000000000..58763914ee --- /dev/null +++ b/src/applications/legalpad/xaction/LegalpadDocumentTransactionType.php @@ -0,0 +1,4 @@ +[^/]+)/)?' => 'PhabricatorMacroListController', 'create/' => 'PhabricatorMacroEditController', 'view/(?P[1-9]\d*)/' => 'PhabricatorMacroViewController', - 'comment/(?P[1-9]\d*)/' => 'PhabricatorMacroCommentController', - 'edit/(?P[1-9]\d*)/' => 'PhabricatorMacroEditController', + $this->getEditRoutePattern('edit/') + => 'PhabricatorMacroEditController', 'audio/(?P[1-9]\d*)/' => 'PhabricatorMacroAudioController', 'disable/(?P[1-9]\d*)/' => 'PhabricatorMacroDisableController', 'meme/' => 'PhabricatorMacroMemeController', diff --git a/src/applications/macro/conduit/MacroEditConduitAPIMethod.php b/src/applications/macro/conduit/MacroEditConduitAPIMethod.php new file mode 100644 index 0000000000..de5fd38655 --- /dev/null +++ b/src/applications/macro/conduit/MacroEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +getBool('behaviorForm')) { $xactions[] = id(new PhabricatorMacroTransaction()) ->setTransactionType( - PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR) + PhabricatorMacroAudioBehaviorTransaction::TRANSACTIONTYPE) ->setNewValue($request->getStr('audioBehavior')); } else { $file = null; @@ -50,15 +50,20 @@ final class PhabricatorMacroAudioController extends PhabricatorMacroController { if ($file) { if (!$file->isAudio()) { - $errors[] = pht('You must upload audio.'); + $errors[] = pht( + 'The file you uploaded is invalid: it is not recognizable as '. + 'a valid audio file.'); $e_file = pht('Invalid'); } else { $xactions[] = id(new PhabricatorMacroTransaction()) - ->setTransactionType(PhabricatorMacroTransaction::TYPE_AUDIO) + ->setTransactionType( + PhabricatorMacroAudioTransaction::TRANSACTIONTYPE) ->setNewValue($file->getPHID()); } } else { - $errors[] = pht('You must upload an audio file.'); + $errors[] = pht( + 'To change the audio for a macro, you must upload an audio '. + 'file.'); $e_file = pht('Required'); } } diff --git a/src/applications/macro/controller/PhabricatorMacroCommentController.php b/src/applications/macro/controller/PhabricatorMacroCommentController.php deleted file mode 100644 index f038140be1..0000000000 --- a/src/applications/macro/controller/PhabricatorMacroCommentController.php +++ /dev/null @@ -1,63 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - if (!$request->isFormPost()) { - return new Aphront400Response(); - } - - $macro = id(new PhabricatorMacroQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$macro) { - return new Aphront404Response(); - } - - $is_preview = $request->isPreviewRequest(); - $draft = PhabricatorDraft::buildFromRequest($request); - - $view_uri = $this->getApplicationURI('/view/'.$macro->getID().'/'); - - $xactions = array(); - $xactions[] = id(new PhabricatorMacroTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) - ->attachComment( - id(new PhabricatorMacroTransactionComment()) - ->setContent($request->getStr('comment'))); - - $editor = id(new PhabricatorMacroEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect($request->isContinueRequest()) - ->setContentSourceFromRequest($request) - ->setIsPreview($is_preview); - - try { - $xactions = $editor->applyTransactions($macro, $xactions); - } catch (PhabricatorApplicationTransactionNoEffectException $ex) { - return id(new PhabricatorApplicationTransactionNoEffectResponse()) - ->setCancelURI($view_uri) - ->setException($ex); - } - - if ($draft) { - $draft->replaceOrDelete(); - } - - if ($request->isAjax() && $is_preview) { - return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($viewer) - ->setTransactions($xactions) - ->setIsPreview($is_preview); - } else { - return id(new AphrontRedirectResponse()) - ->setURI($view_uri); - } - } - -} diff --git a/src/applications/macro/controller/PhabricatorMacroDisableController.php b/src/applications/macro/controller/PhabricatorMacroDisableController.php index a9868647f0..6c74e44442 100644 --- a/src/applications/macro/controller/PhabricatorMacroDisableController.php +++ b/src/applications/macro/controller/PhabricatorMacroDisableController.php @@ -22,7 +22,8 @@ final class PhabricatorMacroDisableController if ($request->isDialogFormPost() || $macro->getIsDisabled()) { $xaction = id(new PhabricatorMacroTransaction()) - ->setTransactionType(PhabricatorMacroTransaction::TYPE_DISABLED) + ->setTransactionType( + PhabricatorMacroDisabledTransaction::TRANSACTIONTYPE) ->setNewValue($macro->getIsDisabled() ? 0 : 1); $editor = id(new PhabricatorMacroEditor()) diff --git a/src/applications/macro/controller/PhabricatorMacroEditController.php b/src/applications/macro/controller/PhabricatorMacroEditController.php index 110296c172..d4b7d5aeec 100644 --- a/src/applications/macro/controller/PhabricatorMacroEditController.php +++ b/src/applications/macro/controller/PhabricatorMacroEditController.php @@ -1,302 +1,10 @@ getViewer(); - $id = $request->getURIData('id'); - - $this->requireApplicationCapability( - PhabricatorMacroManageCapability::CAPABILITY); - - if ($id) { - $macro = id(new PhabricatorMacroQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needFiles(true) - ->executeOne(); - if (!$macro) { - return new Aphront404Response(); - } - } else { - $macro = new PhabricatorFileImageMacro(); - $macro->setAuthorPHID($viewer->getPHID()); - } - - $errors = array(); - $e_name = true; - $e_file = null; - $file = null; - - if ($request->isFormPost()) { - $original = clone $macro; - - $new_name = null; - if ($request->getBool('name_form') || !$macro->getID()) { - $new_name = $request->getStr('name'); - - $macro->setName($new_name); - - if (!strlen($macro->getName())) { - $errors[] = pht('Macro name is required.'); - $e_name = pht('Required'); - } else if (!preg_match('/^[a-z0-9:_-]{3,}\z/', $macro->getName())) { - $errors[] = pht( - 'Macro must be at least three characters long and contain only '. - 'lowercase letters, digits, hyphens, colons and underscores.'); - $e_name = pht('Invalid'); - } else { - $e_name = null; - } - } - - $uri = $request->getStr('url'); - - $engine = new PhabricatorDestructionEngine(); - - $file = null; - if ($request->getFileExists('file')) { - $file = PhabricatorFile::newFromPHPUpload( - $_FILES['file'], - array( - 'name' => $request->getStr('name'), - 'authorPHID' => $viewer->getPHID(), - 'isExplicitUpload' => true, - 'canCDN' => true, - )); - } else if ($uri) { - try { - // Rate limit outbound fetches to make this mechanism less useful for - // scanning networks and ports. - PhabricatorSystemActionEngine::willTakeAction( - array($viewer->getPHID()), - new PhabricatorFilesOutboundRequestAction(), - 1); - - $file = PhabricatorFile::newFromFileDownload( - $uri, - array( - 'name' => $request->getStr('name'), - 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, - 'isExplicitUpload' => true, - 'canCDN' => true, - )); - - if (!$file->isViewableInBrowser()) { - $mime_type = $file->getMimeType(); - $engine->destroyObject($file); - $file = null; - throw new Exception( - pht( - 'The URI "%s" does not correspond to a valid image file, got '. - 'a file with MIME type "%s". You must specify the URI of a '. - 'valid image file.', - $uri, - $mime_type)); - } else { - $file - ->setAuthorPHID($viewer->getPHID()) - ->save(); - } - } catch (HTTPFutureHTTPResponseStatus $status) { - $errors[] = pht( - 'The URI "%s" could not be loaded, got %s error.', - $uri, - $status->getStatusCode()); - } catch (Exception $ex) { - $errors[] = $ex->getMessage(); - } - } else if ($request->getStr('phid')) { - $file = id(new PhabricatorFileQuery()) - ->setViewer($viewer) - ->withPHIDs(array($request->getStr('phid'))) - ->executeOne(); - } - - if ($file) { - if (!$file->isViewableInBrowser()) { - $errors[] = pht('You must upload an image.'); - $e_file = pht('Invalid'); - } else { - $macro->setFilePHID($file->getPHID()); - $macro->attachFile($file); - $e_file = null; - } - } - - if (!$macro->getID() && !$file) { - $errors[] = pht('You must upload an image to create a macro.'); - $e_file = pht('Required'); - } - - if (!$errors) { - try { - $xactions = array(); - - if ($new_name !== null) { - $xactions[] = id(new PhabricatorMacroTransaction()) - ->setTransactionType(PhabricatorMacroTransaction::TYPE_NAME) - ->setNewValue($new_name); - } - - if ($file) { - $xactions[] = id(new PhabricatorMacroTransaction()) - ->setTransactionType(PhabricatorMacroTransaction::TYPE_FILE) - ->setNewValue($file->getPHID()); - } - - $editor = id(new PhabricatorMacroEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect(true) - ->setContentSourceFromRequest($request); - - $xactions = $editor->applyTransactions($original, $xactions); - - $view_uri = $this->getApplicationURI('/view/'.$original->getID().'/'); - return id(new AphrontRedirectResponse())->setURI($view_uri); - } catch (AphrontDuplicateKeyQueryException $ex) { - throw $ex; - $errors[] = pht('Macro name is not unique!'); - $e_name = pht('Duplicate'); - } - } - } - - $current_file = null; - if ($macro->getFilePHID()) { - $current_file = $macro->getFile(); - } - - $form = new AphrontFormView(); - $form->addHiddenInput('name_form', 1); - $form->setUser($request->getUser()); - - $form - ->setEncType('multipart/form-data') - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setValue($macro->getName()) - ->setCaption( - pht('This word or phrase will be replaced with the image.')) - ->setError($e_name)); - - if (!$macro->getID()) { - if ($current_file) { - $current_file_view = id(new PhabricatorFileLinkView()) - ->setViewer($viewer) - ->setFilePHID($current_file->getPHID()) - ->setFileName($current_file->getName()) - ->setFileViewable(true) - ->setFileViewURI($current_file->getBestURI()) - ->render(); - $form->addHiddenInput('phid', $current_file->getPHID()); - $form->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Selected File')) - ->setValue($current_file_view)); - - $other_label = pht('Change File'); - } else { - $other_label = pht('File'); - } - - $form->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('URL')) - ->setName('url') - ->setValue($request->getStr('url')) - ->setError($request->getFileExists('file') ? false : $e_file)); - - $form->appendChild( - id(new AphrontFormFileControl()) - ->setLabel($other_label) - ->setName('file') - ->setError($request->getStr('url') ? false : $e_file)); - } - - - $view_uri = $this->getApplicationURI('/view/'.$macro->getID().'/'); - - if ($macro->getID()) { - $cancel_uri = $view_uri; - } else { - $cancel_uri = $this->getApplicationURI(); - } - - $form - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save Image Macro')) - ->addCancelButton($cancel_uri)); - - $crumbs = $this->buildApplicationCrumbs(); - - if ($macro->getID()) { - $title = pht('Edit Macro: %s', $macro->getName()); - $crumb = pht('Edit Macro'); - $header_icon = 'fa-pencil'; - - $crumbs->addTextCrumb(pht('Macro: %s', $macro->getName()), $view_uri); - } else { - $title = pht('Create Image Macro'); - $crumb = pht('Create Macro'); - $header_icon = 'fa-plus-square'; - } - - $crumbs->addTextCrumb($crumb, $request->getRequestURI()); - $crumbs->setBorder(true); - - $upload = null; - if ($macro->getID()) { - $upload_form = id(new AphrontFormView()) - ->setEncType('multipart/form-data') - ->setUser($request->getUser()); - - $upload_form->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('URL')) - ->setName('url') - ->setValue($request->getStr('url'))); - - $upload_form - ->appendChild( - id(new AphrontFormFileControl()) - ->setLabel(pht('File')) - ->setName('file')) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Upload File'))); - - $upload = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Upload New File')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($upload_form); - } - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Macro')) - ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $form_box, - $upload, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - + return id(new PhabricatorMacroEditEngine()) + ->setController($this) + ->buildResponse(); } - } diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php index 386029c7ce..1bcf34240a 100644 --- a/src/applications/macro/controller/PhabricatorMacroViewController.php +++ b/src/applications/macro/controller/PhabricatorMacroViewController.php @@ -36,6 +36,8 @@ final class PhabricatorMacroViewController $macro, new PhabricatorMacroTransactionQuery()); + $comment_form = $this->buildCommentForm($macro, $timeline); + $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($macro) @@ -45,32 +47,16 @@ final class PhabricatorMacroViewController if (!$macro->getIsDisabled()) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } else { - $header->setStatus('fa-ban', 'red', pht('Archived')); + $header->setStatus('fa-ban', 'indigo', pht('Archived')); } - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - - $comment_header = $is_serious - ? pht('Add Comment') - : pht('Grovel in Awe'); - - $draft = PhabricatorDraft::newFromUserAndKey($viewer, $macro->getPHID()); - - $add_comment_form = id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setObjectPHID($macro->getPHID()) - ->setDraft($draft) - ->setHeaderText($comment_header) - ->setAction($this->getApplicationURI('/comment/'.$macro->getID().'/')) - ->setSubmitButtonName(pht('Add Comment')); - $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) ->setCurtain($curtain) ->setMainColumn(array( $timeline, - $add_comment_form, + $comment_form, )) ->addPropertySection(pht('Macro'), $file) ->addPropertySection(pht('Details'), $details); @@ -82,6 +68,16 @@ final class PhabricatorMacroViewController ->appendChild($view); } + private function buildCommentForm( + PhabricatorFileImageMacro $macro, $timeline) { + $viewer = $this->getViewer(); + + return id(new PhabricatorMacroEditEngine()) + ->setViewer($viewer) + ->buildEditEngineCommentView($macro) + ->setTransactionTimeline($timeline); + } + private function buildCurtain( PhabricatorFileImageMacro $macro) { $can_manage = $this->hasApplicationCapability( diff --git a/src/applications/macro/editor/PhabricatorMacroEditEngine.php b/src/applications/macro/editor/PhabricatorMacroEditEngine.php new file mode 100644 index 0000000000..ff348c3163 --- /dev/null +++ b/src/applications/macro/editor/PhabricatorMacroEditEngine.php @@ -0,0 +1,107 @@ +getViewer(); + return PhabricatorFileImageMacro::initializeNewFileImageMacro($viewer); + } + + protected function newObjectQuery() { + return new PhabricatorMacroQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Macro'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Macro %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return $object->getName(); + } + + protected function getObjectCreateShortText() { + return pht('Create Macro'); + } + + protected function getObjectName() { + return pht('Macro'); + } + + protected function getObjectViewURI($object) { + return $object->getViewURI(); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('edit/'); + } + + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + PhabricatorMacroManageCapability::CAPABILITY); + } + + protected function willConfigureFields($object, array $fields) { + if ($this->getIsCreate()) { + $subscribers_field = idx($fields, + PhabricatorSubscriptionsEditEngineExtension::FIELDKEY); + if ($subscribers_field) { + // By default, hide the subscribers field when creating a macro + // because it makes the workflow SO HARD and wastes SO MUCH TIME. + $subscribers_field->setIsHidden(true); + } + } + return $fields; + } + + protected function buildCustomEditFields($object) { + + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Macro name.')) + ->setConduitDescription(pht('Name of the macro.')) + ->setConduitTypeDescription(pht('New macro name.')) + ->setTransactionType(PhabricatorMacroNameTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getName()), + id(new PhabricatorFileEditField()) + ->setKey('filePHID') + ->setLabel(pht('Image File')) + ->setDescription(pht('Image file to import.')) + ->setTransactionType(PhabricatorMacroFileTransaction::TRANSACTIONTYPE) + ->setConduitDescription(pht('File PHID to import.')) + ->setConduitTypeDescription(pht('File PHID.')) + ->setValue($object->getFilePHID()), + ); + + } + +} diff --git a/src/applications/macro/editor/PhabricatorMacroEditor.php b/src/applications/macro/editor/PhabricatorMacroEditor.php index 1bb139a4de..5d28b78f5f 100644 --- a/src/applications/macro/editor/PhabricatorMacroEditor.php +++ b/src/applications/macro/editor/PhabricatorMacroEditor.php @@ -11,140 +11,17 @@ final class PhabricatorMacroEditor return pht('Macros'); } - public function getTransactionTypes() { - $types = parent::getTransactionTypes(); - - $types[] = PhabricatorTransactions::TYPE_COMMENT; - $types[] = PhabricatorMacroTransaction::TYPE_NAME; - $types[] = PhabricatorMacroTransaction::TYPE_DISABLED; - $types[] = PhabricatorMacroTransaction::TYPE_FILE; - $types[] = PhabricatorMacroTransaction::TYPE_AUDIO; - $types[] = PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR; - - return $types; + public function getCreateObjectTitle($author, $object) { + return pht('%s created this macro.', $author); } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransaction::TYPE_NAME: - return $object->getName(); - case PhabricatorMacroTransaction::TYPE_DISABLED: - return $object->getIsDisabled(); - case PhabricatorMacroTransaction::TYPE_FILE: - return $object->getFilePHID(); - case PhabricatorMacroTransaction::TYPE_AUDIO: - return $object->getAudioPHID(); - case PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR: - return $object->getAudioBehavior(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransaction::TYPE_NAME: - case PhabricatorMacroTransaction::TYPE_DISABLED: - case PhabricatorMacroTransaction::TYPE_FILE: - case PhabricatorMacroTransaction::TYPE_AUDIO: - case PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR: - return $xaction->getNewValue(); - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - break; - case PhabricatorMacroTransaction::TYPE_DISABLED: - $object->setIsDisabled($xaction->getNewValue()); - break; - case PhabricatorMacroTransaction::TYPE_FILE: - $object->setFilePHID($xaction->getNewValue()); - break; - case PhabricatorMacroTransaction::TYPE_AUDIO: - $object->setAudioPHID($xaction->getNewValue()); - break; - case PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR: - $object->setAudioBehavior($xaction->getNewValue()); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransaction::TYPE_FILE: - case PhabricatorMacroTransaction::TYPE_AUDIO: - // When changing a macro's image or audio, attach the underlying files - // to the macro (and detach the old files). - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - $all = array(); - if ($old) { - $all[] = $old; - } - if ($new) { - $all[] = $new; - } - - $files = id(new PhabricatorFileQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs($all) - ->execute(); - $files = mpull($files, null, 'getPHID'); - - $old_file = idx($files, $old); - if ($old_file) { - $old_file->detachFromObject($object->getPHID()); - } - - $new_file = idx($files, $new); - if ($new_file) { - $new_file->attachToObject($object->getPHID()); - } - break; - } - } - - protected function mergeTransactions( - PhabricatorApplicationTransaction $u, - PhabricatorApplicationTransaction $v) { - - $type = $u->getTransactionType(); - switch ($type) { - case PhabricatorMacroTransaction::TYPE_NAME: - case PhabricatorMacroTransaction::TYPE_DISABLED: - case PhabricatorMacroTransaction::TYPE_FILE: - case PhabricatorMacroTransaction::TYPE_AUDIO: - case PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR: - return $v; - } - - return parent::mergeTransactions($u, $v); + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); } protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { - foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorMacroTransaction::TYPE_NAME; - return ($xaction->getOldValue() !== null); - default: - break; - } - } return true; } diff --git a/src/applications/macro/storage/PhabricatorFileImageMacro.php b/src/applications/macro/storage/PhabricatorFileImageMacro.php index bc23e639d7..0cd6726f5f 100644 --- a/src/applications/macro/storage/PhabricatorFileImageMacro.php +++ b/src/applications/macro/storage/PhabricatorFileImageMacro.php @@ -41,6 +41,12 @@ final class PhabricatorFileImageMacro extends PhabricatorFileDAO return $this->assertAttached($this->audio); } + public static function initializeNewFileImageMacro(PhabricatorUser $actor) { + $macro = id(new self()) + ->setAuthorPHID($actor->getPHID()); + return $macro; + } + protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, @@ -80,6 +86,10 @@ final class PhabricatorFileImageMacro extends PhabricatorFileDAO return parent::save(); } + public function getViewURI() { + return '/macro/view/'.$this->getID().'/'; + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -128,11 +138,19 @@ final class PhabricatorFileImageMacro extends PhabricatorFileDAO public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { - return PhabricatorPolicies::getMostOpenPolicy(); + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return PhabricatorPolicies::getMostOpenPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + $app = PhabricatorApplication::getByClass( + 'PhabricatorMacroApplication'); + return $app->getPolicy(PhabricatorMacroManageCapability::CAPABILITY); + } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { diff --git a/src/applications/macro/storage/PhabricatorMacroTransaction.php b/src/applications/macro/storage/PhabricatorMacroTransaction.php index 46eb932476..3a15b4c15d 100644 --- a/src/applications/macro/storage/PhabricatorMacroTransaction.php +++ b/src/applications/macro/storage/PhabricatorMacroTransaction.php @@ -1,14 +1,7 @@ getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_FILE: - case self::TYPE_AUDIO: - if ($old !== null) { - $phids[] = $old; - } - $phids[] = $new; - break; - } - - return $phids; - } - - public function shouldHide() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - return ($old === null); - } - - return parent::shouldHide(); - } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - return pht( - '%s renamed this macro from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - break; - case self::TYPE_DISABLED: - if ($new) { - return pht( - '%s disabled this macro.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s restored this macro.', - $this->renderHandleLink($author_phid)); - } - break; - - case self::TYPE_AUDIO: - if (!$old) { - return pht( - '%s attached audio: %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); - } else { - return pht( - '%s changed the audio for this macro from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - - case self::TYPE_AUDIO_BEHAVIOR: - switch ($new) { - case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE: - return pht( - '%s set the audio to play once.', - $this->renderHandleLink($author_phid)); - case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP: - return pht( - '%s set the audio to loop.', - $this->renderHandleLink($author_phid)); - default: - return pht( - '%s disabled the audio for this macro.', - $this->renderHandleLink($author_phid)); - } - - case self::TYPE_FILE: - if ($old === null) { - return pht( - '%s created this macro.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s changed the image for this macro from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - break; - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - return pht( - '%s renamed %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); - case self::TYPE_DISABLED: - if ($new) { - return pht( - '%s disabled %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s restored %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - case self::TYPE_FILE: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s updated the image for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - - case self::TYPE_AUDIO: - if (!$old) { - return pht( - '%s attached audio to %s: %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($new)); - } else { - return pht( - '%s changed the audio for %s from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - - case self::TYPE_AUDIO_BEHAVIOR: - switch ($new) { - case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE: - return pht( - '%s set the audio for %s to play once.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP: - return pht( - '%s set the audio for %s to loop.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - default: - return pht( - '%s disabled the audio for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - - } - - return parent::getTitleForFeed(); - } - - public function getActionName() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht('Created'); - } else { - return pht('Renamed'); - } - case self::TYPE_DISABLED: - if ($new) { - return pht('Disabled'); - } else { - return pht('Restored'); - } - case self::TYPE_FILE: - if ($old === null) { - return pht('Created'); - } else { - return pht('Edited Image'); - } - - case self::TYPE_AUDIO: - return pht('Audio'); - - case self::TYPE_AUDIO_BEHAVIOR: - return pht('Audio Behavior'); - - } - - return parent::getActionName(); - } - - public function getActionStrength() { - switch ($this->getTransactionType()) { - case self::TYPE_DISABLED: - return 2.0; - case self::TYPE_FILE: - return 1.5; - } - return parent::getActionStrength(); - } - - public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - return 'fa-pencil'; - case self::TYPE_FILE: - if ($old === null) { - return 'fa-plus'; - } else { - return 'fa-pencil'; - } - case self::TYPE_DISABLED: - if ($new) { - return 'fa-times'; - } else { - return 'fa-undo'; - } - case self::TYPE_AUDIO: - return 'fa-headphones'; - } - - return parent::getIcon(); - } - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - return PhabricatorTransactions::COLOR_BLUE; - case self::TYPE_FILE: - if ($old === null) { - return PhabricatorTransactions::COLOR_GREEN; - } else { - return PhabricatorTransactions::COLOR_BLUE; - } - case self::TYPE_DISABLED: - if ($new) { - return PhabricatorTransactions::COLOR_RED; - } else { - return PhabricatorTransactions::COLOR_SKY; - } - } - - return parent::getColor(); + public function getBaseTransactionClass() { + return 'PhabricatorMacroTransactionType'; } diff --git a/src/applications/macro/xaction/PhabricatorMacroAudioBehaviorTransaction.php b/src/applications/macro/xaction/PhabricatorMacroAudioBehaviorTransaction.php new file mode 100644 index 0000000000..e27e9081ed --- /dev/null +++ b/src/applications/macro/xaction/PhabricatorMacroAudioBehaviorTransaction.php @@ -0,0 +1,69 @@ +getAudioBehavior(); + } + + public function applyInternalEffects($object, $value) { + $object->setAudioBehavior($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + $old = $this->getOldValue(); + switch ($new) { + case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE: + return pht( + '%s set the audio to play once.', + $this->renderAuthor()); + case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP: + return pht( + '%s set the audio to loop.', + $this->renderAuthor()); + default: + return pht( + '%s disabled the audio for this macro.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + $old = $this->getOldValue(); + switch ($new) { + case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE: + return pht( + '%s set the audio for %s to play once.', + $this->renderAuthor(), + $this->renderObject()); + case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP: + return pht( + '%s set the audio for %s to loop.', + $this->renderAuthor(), + $this->renderObject()); + default: + return pht( + '%s disabled the audio for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + switch ($new) { + case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE: + return 'fa-play-circle'; + case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP: + return 'fa-repeat'; + default: + return 'fa-pause-circle'; + } + } + +} diff --git a/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php b/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php new file mode 100644 index 0000000000..26dc64c1f3 --- /dev/null +++ b/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php @@ -0,0 +1,84 @@ +getAudioPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setAudioPHID($value); + } + + public function applyExternalEffects($object, $value) { + $old = $this->generateOldValue($object); + $new = $value; + $all = array(); + if ($old) { + $all[] = $old; + } + if ($new) { + $all[] = $new; + } + + $files = id(new PhabricatorFileQuery()) + ->setViewer($this->getActor()) + ->withPHIDs($all) + ->execute(); + $files = mpull($files, null, 'getPHID'); + + $old_file = idx($files, $old); + if ($old_file) { + $old_file->detachFromObject($object->getPHID()); + } + + $new_file = idx($files, $new); + if ($new_file) { + $new_file->attachToObject($object->getPHID()); + } + } + + public function getTitle() { + $new = $this->getNewValue(); + $old = $this->getOldValue(); + if (!$old) { + return pht( + '%s attached audio: %s.', + $this->renderAuthor(), + $this->renderHandle($new)); + } else { + return pht( + '%s changed the audio for this macro from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($old), + $this->renderHandle($new)); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + $old = $this->getOldValue(); + if (!$old) { + return pht( + '%s attached audio to %s: %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($new)); + } else { + return pht( + '%s changed the audio for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($old), + $this->renderHandle($new)); + } + } + + public function getIcon() { + return 'fa-music'; + } + +} diff --git a/src/applications/macro/xaction/PhabricatorMacroDisabledTransaction.php b/src/applications/macro/xaction/PhabricatorMacroDisabledTransaction.php new file mode 100644 index 0000000000..334dc9ef6f --- /dev/null +++ b/src/applications/macro/xaction/PhabricatorMacroDisabledTransaction.php @@ -0,0 +1,50 @@ +getIsDisabled(); + } + + public function applyInternalEffects($object, $value) { + $object->setIsDisabled($value); + } + + public function getTitle() { + if ($this->getNewValue()) { + return pht( + '%s disabled this macro.', + $this->renderAuthor()); + } else { + return pht( + '%s restored this macro.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + if ($this->getNewValue()) { + return pht( + '%s disabled %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s restored %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + if ($this->getNewValue()) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + +} diff --git a/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php b/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php new file mode 100644 index 0000000000..fb0c56f1c1 --- /dev/null +++ b/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php @@ -0,0 +1,104 @@ +getFilePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setFilePHID($value); + } + + public function applyExternalEffects($object, $value) { + $old = $this->generateOldValue($object); + $new = $value; + $all = array(); + if ($old) { + $all[] = $old; + } + if ($new) { + $all[] = $new; + } + + $files = id(new PhabricatorFileQuery()) + ->setViewer($this->getActor()) + ->withPHIDs($all) + ->execute(); + $files = mpull($files, null, 'getPHID'); + + $old_file = idx($files, $old); + if ($old_file) { + $old_file->detachFromObject($object->getPHID()); + } + + $new_file = idx($files, $new); + if ($new_file) { + $new_file->attachToObject($object->getPHID()); + } + } + + public function getTitle() { + return pht( + '%s changed the image for this macro.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s changed the image for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + $viewer = $this->getActor(); + + $old_phid = $object->getFilePHID(); + + foreach ($xactions as $xaction) { + $file_phid = $xaction->getNewValue(); + + if (!$old_phid) { + if ($this->isEmptyTextTransaction($file_phid, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Image macros must have a file.')); + return $errors; + } + } + + // Only validate if file was uploaded + if ($file_phid) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + + if (!$file) { + $errors[] = $this->newInvalidError( + pht('"%s" is not a valid file PHID.', + $file_phid)); + } else { + if (!$file->isViewableImage()) { + $mime_type = $file->getMimeType(); + $errors[] = $this->newInvalidError( + pht('File mime type of "%s" is not a valid viewable image.', + $mime_type)); + } + } + } + + } + + return $errors; + } + + public function getIcon() { + return 'fa-file-image-o'; + } + +} diff --git a/src/applications/macro/xaction/PhabricatorMacroNameTransaction.php b/src/applications/macro/xaction/PhabricatorMacroNameTransaction.php new file mode 100644 index 0000000000..68710605aa --- /dev/null +++ b/src/applications/macro/xaction/PhabricatorMacroNameTransaction.php @@ -0,0 +1,81 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this macro from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + $viewer = $this->getActor(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Macros must have a name.')); + return $errors; + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $old_value = $this->generateOldValue($object); + $new_value = $xaction->getNewValue(); + + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The name can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + + if (!preg_match('/^[a-z0-9:_-]{3,}\z/', $new_value)) { + $errors[] = $this->newInvalidError( + pht('Macro name "%s" be at least three characters long and contain '. + 'only lowercase letters, digits, hyphens, colons and '. + 'underscores.', + $new_value)); + } + + // Check name is unique when updating / creating + if ($old_value != $new_value) { + $macro = id(new PhabricatorMacroQuery()) + ->setViewer($viewer) + ->withNames(array($new_value)) + ->executeOne(); + + if ($macro) { + $errors[] = $this->newInvalidError( + pht('Macro "%s" already exists.', $new_value)); + } + } + + } + + return $errors; + } + +} diff --git a/src/applications/macro/xaction/PhabricatorMacroTransactionType.php b/src/applications/macro/xaction/PhabricatorMacroTransactionType.php new file mode 100644 index 0000000000..6a8e98c05b --- /dev/null +++ b/src/applications/macro/xaction/PhabricatorMacroTransactionType.php @@ -0,0 +1,4 @@ +moveTask($viewer, $t[9], $t[1], true); + $tasks = $this->loadTasks($viewer, $auto_base); + $this->assertEqual( + array(8, 7, 6, 5, 4, 3, 2, 1, 9), + array_keys($tasks)); + + // Move task 3 to the beginning. + $this->moveTask($viewer, $t[3], $t[8], false); + $tasks = $this->loadTasks($viewer, $auto_base); + $this->assertEqual( + array(3, 8, 7, 6, 5, 4, 2, 1, 9), + array_keys($tasks)); + + // Move task 3 to the end. + $this->moveTask($viewer, $t[3], $t[9], true); + $tasks = $this->loadTasks($viewer, $auto_base); + $this->assertEqual( + array(8, 7, 6, 5, 4, 2, 1, 9, 3), + array_keys($tasks)); + + // Move task 5 to before task 4 (this is its current position). + $this->moveTask($viewer, $t[5], $t[4], false); + $tasks = $this->loadTasks($viewer, $auto_base); + $this->assertEqual( + array(8, 7, 6, 5, 4, 2, 1, 9, 3), + array_keys($tasks)); } private function newTask(PhabricatorUser $viewer, $title) { @@ -133,7 +161,7 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); @@ -166,14 +194,17 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase { $dst, $is_after); + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head($keyword_map[$pri]); + $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) - ->setNewValue($pri); + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) + ->setNewValue($keyword); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY) + ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) ->setNewValue($sub); return $this->applyTaskTransactions($viewer, $src, $xactions); @@ -189,14 +220,17 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase { $target_priority, $is_end); + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head($keyword_map[$pri]); + $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) - ->setNewValue($pri); + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) + ->setNewValue($keyword); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY) + ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) ->setNewValue($sub); return $this->applyTaskTransactions($viewer, $src, $xactions); diff --git a/src/applications/maniphest/application/PhabricatorManiphestApplication.php b/src/applications/maniphest/application/PhabricatorManiphestApplication.php index 022d81a33d..692f45fdbc 100644 --- a/src/applications/maniphest/application/PhabricatorManiphestApplication.php +++ b/src/applications/maniphest/application/PhabricatorManiphestApplication.php @@ -6,6 +6,10 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication { return pht('Maniphest'); } + public function getMenuName() { + return pht('Tasks'); + } + public function getShortDescription() { return pht('Tasks and Bugs'); } diff --git a/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php b/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php index f4ca24863b..1b083d88f9 100644 --- a/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php +++ b/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php @@ -76,9 +76,9 @@ final class ManiphestTaskEditBulkJobType $value_map = array(); $type_map = array( 'add_comment' => PhabricatorTransactions::TYPE_COMMENT, - 'assign' => ManiphestTransaction::TYPE_OWNER, - 'status' => ManiphestTransaction::TYPE_STATUS, - 'priority' => ManiphestTransaction::TYPE_PRIORITY, + 'assign' => ManiphestTaskOwnerTransaction::TRANSACTIONTYPE, + 'status' => ManiphestTaskStatusTransaction::TRANSACTIONTYPE, + 'priority' => ManiphestTaskPriorityTransaction::TRANSACTIONTYPE, 'add_project' => PhabricatorTransactions::TYPE_EDGE, 'remove_project' => PhabricatorTransactions::TYPE_EDGE, 'add_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS, @@ -114,13 +114,13 @@ final class ManiphestTaskEditBulkJobType case PhabricatorTransactions::TYPE_COMMENT: $current = null; break; - case ManiphestTransaction::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: $current = $task->getOwnerPHID(); break; - case ManiphestTransaction::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $current = $task->getStatus(); break; - case ManiphestTransaction::TYPE_PRIORITY: + case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE: $current = $task->getPriority(); break; case PhabricatorTransactions::TYPE_EDGE: @@ -153,7 +153,7 @@ final class ManiphestTaskEditBulkJobType } $value = head($value); break; - case ManiphestTransaction::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: if (empty($value)) { continue 2; } @@ -285,6 +285,11 @@ final class ManiphestTaskEditBulkJobType '=' => array_fuse($value), )); break; + case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE: + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head(idx($keyword_map, $value)); + $xaction->setNewValue($keyword); + break; default: $xaction->setNewValue($value); break; diff --git a/src/applications/maniphest/command/ManiphestAssignEmailCommand.php b/src/applications/maniphest/command/ManiphestAssignEmailCommand.php index 98e8a913a8..de014b6c66 100644 --- a/src/applications/maniphest/command/ManiphestAssignEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestAssignEmailCommand.php @@ -53,7 +53,7 @@ final class ManiphestAssignEmailCommand } $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setNewValue($assign_phid); return $xactions; diff --git a/src/applications/maniphest/command/ManiphestClaimEmailCommand.php b/src/applications/maniphest/command/ManiphestClaimEmailCommand.php index 4a6a348dbb..babc853b67 100644 --- a/src/applications/maniphest/command/ManiphestClaimEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestClaimEmailCommand.php @@ -23,7 +23,7 @@ final class ManiphestClaimEmailCommand $xactions = array(); $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setNewValue($viewer->getPHID()); return $xactions; diff --git a/src/applications/maniphest/command/ManiphestCloseEmailCommand.php b/src/applications/maniphest/command/ManiphestCloseEmailCommand.php index 8104fd8b8d..eab01ad615 100644 --- a/src/applications/maniphest/command/ManiphestCloseEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestCloseEmailCommand.php @@ -24,7 +24,7 @@ final class ManiphestCloseEmailCommand $xactions = array(); $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setNewValue(ManiphestTaskStatus::getDefaultClosedStatus()); return $xactions; diff --git a/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php b/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php index f774578240..ef966a50da 100644 --- a/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php @@ -49,18 +49,8 @@ final class ManiphestPriorityEmailCommand array $argv) { $xactions = array(); - $target = phutil_utf8_strtolower(head($argv)); - $priority = null; - - $keywords = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); - foreach ($keywords as $key => $words) { - foreach ($words as $word) { - if ($word == $target) { - $priority = $key; - break; - } - } - } + $keyword = phutil_utf8_strtolower(head($argv)); + $priority = ManiphestTaskPriority::getTaskPriorityFromKeyword($keyword); if ($priority === null) { return array(); @@ -71,8 +61,8 @@ final class ManiphestPriorityEmailCommand } $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) - ->setNewValue($priority); + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) + ->setNewValue($keyword); return $xactions; } diff --git a/src/applications/maniphest/command/ManiphestStatusEmailCommand.php b/src/applications/maniphest/command/ManiphestStatusEmailCommand.php index dace0cb255..0387cae34e 100644 --- a/src/applications/maniphest/command/ManiphestStatusEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestStatusEmailCommand.php @@ -73,7 +73,7 @@ final class ManiphestStatusEmailCommand } $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setNewValue($status); return $xactions; diff --git a/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php index d2a8de7d81..640e30fee5 100644 --- a/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php +++ b/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php @@ -60,7 +60,7 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { if ($is_new) { $task->setTitle((string)$request->getValue('title')); $task->setDescription((string)$request->getValue('description')); - $changes[ManiphestTransaction::TYPE_STATUS] = + $changes[ManiphestTaskStatusTransaction::TRANSACTIONTYPE] = ManiphestTaskStatus::getDefaultStatus(); $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] = array('+' => array($request->getUser()->getPHID())); @@ -73,12 +73,12 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { $title = $request->getValue('title'); if ($title !== null) { - $changes[ManiphestTransaction::TYPE_TITLE] = $title; + $changes[ManiphestTaskTitleTransaction::TRANSACTIONTYPE] = $title; } $desc = $request->getValue('description'); if ($desc !== null) { - $changes[ManiphestTransaction::TYPE_DESCRIPTION] = $desc; + $changes[ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE] = $desc; } $status = $request->getValue('status'); @@ -88,7 +88,7 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { throw id(new ConduitException('ERR-INVALID-PARAMETER')) ->setErrorDescription(pht('Status set to invalid value.')); } - $changes[ManiphestTransaction::TYPE_STATUS] = $status; + $changes[ManiphestTaskStatusTransaction::TRANSACTIONTYPE] = $status; } } @@ -99,7 +99,9 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { throw id(new ConduitException('ERR-INVALID-PARAMETER')) ->setErrorDescription(pht('Priority set to invalid value.')); } - $changes[ManiphestTransaction::TYPE_PRIORITY] = $priority; + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head(idx($keyword_map, $priority)); + $changes[ManiphestTaskPriorityTransaction::TRANSACTIONTYPE] = $keyword; } $owner_phid = $request->getValue('ownerPHID'); @@ -108,7 +110,7 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { array($owner_phid), PhabricatorPeopleUserPHIDType::TYPECONST, 'ownerPHID'); - $changes[ManiphestTransaction::TYPE_OWNER] = $owner_phid; + $changes[ManiphestTaskOwnerTransaction::TRANSACTIONTYPE] = $owner_phid; } $ccs = $request->getValue('ccPHIDs'); diff --git a/src/applications/maniphest/conduit/ManiphestPrioritySearchConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestPrioritySearchConduitAPIMethod.php new file mode 100644 index 0000000000..eab06fb780 --- /dev/null +++ b/src/applications/maniphest/conduit/ManiphestPrioritySearchConduitAPIMethod.php @@ -0,0 +1,44 @@ +'; + } + + public function getRequiredScope() { + return self::SCOPE_ALWAYS; + } + + protected function execute(ConduitAPIRequest $request) { + $config = ManiphestTaskPriority::getConfig(); + + $results = array(); + foreach ($config as $code => $priority) { + $priority['value'] = $code; + $results[] = $priority; + } + + return array('data' => $results); + } + +} diff --git a/src/applications/maniphest/conduit/ManiphestQueryStatusesConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestQueryStatusesConduitAPIMethod.php index 971be8820b..28afaa4fe1 100644 --- a/src/applications/maniphest/conduit/ManiphestQueryStatusesConduitAPIMethod.php +++ b/src/applications/maniphest/conduit/ManiphestQueryStatusesConduitAPIMethod.php @@ -33,4 +33,14 @@ final class ManiphestQueryStatusesConduitAPIMethod return $results; } + public function getMethodStatus() { + return self::METHOD_STATUS_FROZEN; + } + + public function getMethodStatusDescription() { + return pht( + 'This method is frozen and will eventually be deprecated. New code '. + 'should use "maniphest.status.search" instead.'); + } + } diff --git a/src/applications/maniphest/conduit/ManiphestStatusSearchConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestStatusSearchConduitAPIMethod.php new file mode 100644 index 0000000000..7185cde96f --- /dev/null +++ b/src/applications/maniphest/conduit/ManiphestStatusSearchConduitAPIMethod.php @@ -0,0 +1,52 @@ +'; + } + + public function getRequiredScope() { + return self::SCOPE_ALWAYS; + } + + protected function execute(ConduitAPIRequest $request) { + $config = PhabricatorEnv::getEnvConfig('maniphest.statuses'); + $results = array(); + foreach ($config as $code => $status) { + $stripped_status = array( + 'name' => $status['name'], + 'value' => $code, + 'closed' => !empty($status['closed']), + ); + + if (isset($status['special'])) { + $stripped_status['special'] = $status['special']; + } + + $results[] = $stripped_status; + } + + return array('data' => $results); + } + +} diff --git a/src/applications/maniphest/config/ManiphestPointsConfigOptionType.php b/src/applications/maniphest/config/ManiphestPointsConfigOptionType.php deleted file mode 100644 index f79050b4a1..0000000000 --- a/src/applications/maniphest/config/ManiphestPointsConfigOptionType.php +++ /dev/null @@ -1,10 +0,0 @@ - array( 'name' => pht('Unbreak Now!'), + 'keywords' => array('unbreak'), 'short' => pht('Unbreak!'), 'color' => 'pink', - 'keywords' => array('unbreak'), ), 90 => array( 'name' => pht('Needs Triage'), + 'keywords' => array('triage'), 'short' => pht('Triage'), 'color' => 'violet', - 'keywords' => array('triage'), ), 80 => array( 'name' => pht('High'), + 'keywords' => array('high'), 'short' => pht('High'), 'color' => 'red', - 'keywords' => array('high'), ), 50 => array( 'name' => pht('Normal'), + 'keywords' => array('normal'), 'short' => pht('Normal'), 'color' => 'orange', - 'keywords' => array('normal'), ), 25 => array( 'name' => pht('Low'), + 'keywords' => array('low'), 'short' => pht('Low'), 'color' => 'yellow', - 'keywords' => array('low'), ), 0 => array( 'name' => pht('Wishlist'), + 'keywords' => array('wish', 'wishlist'), 'short' => pht('Wish'), 'color' => 'sky', - 'keywords' => array('wish', 'wishlist'), ), ); - $status_type = 'custom:ManiphestStatusConfigOptionType'; + $status_type = 'maniphest.statuses'; $status_defaults = array( 'open' => array( 'name' => pht('Open'), @@ -265,7 +265,7 @@ EOTEXT ); $fields_json = id(new PhutilJSON())->encodeFormatted($fields_example); - $points_type = 'custom:ManiphestPointsConfigOptionType'; + $points_type = 'maniphest.points'; $points_example_1 = array( 'enabled' => true, @@ -299,7 +299,7 @@ See the example below for a starting point. EOTEXT )); - $subtype_type = 'custom:ManiphestSubtypesConfigOptionsType'; + $subtype_type = 'maniphest.subtypes'; $subtype_default_key = PhabricatorEditEngineSubtype::SUBTYPE_DEFAULT; $subtype_example = array( array( @@ -335,13 +335,46 @@ dictionary with these keys: "task", "feature", or "bug". - `name` //Required string.// Human-readable name for this subtype, like "Task", "Feature Request" or "Bug Report". + - `tag` //Optional string.// Tag text for this subtype. + - `color` //Optional string.// Display color for this subtype. + - `icon` //Optional string.// Icon for the subtype. Each subtype must have a unique key, and you must define a subtype with the key "%s", which is used as a default subtype. + +The tag text (`tag`) is used to set the text shown in the subtype tag on list +views and workboards. If you do not configure it, the default subtype will have +no subtype tag and other subtypes will use their name as tag text. EOTEXT , $subtype_default_key)); + $priorities_description = $this->deformat(pht(<<.// List of unique keywords which identify + this priority, like "high" or "low". Each priority must have at least one + keyword and two priorities may not share the same keyword. + - `short` //Optional string.// Alternate shorter name, used in UIs where + there is less space available. + - `color` //Optional string.// Color for this priority, like "red" or + "blue". + - `disabled` //Optional bool.// Set to true to prevent users from choosing + this priority when creating or editing tasks. Existing tasks will not be + affected, and can be batch edited to a different priority or left to + eventually die out. + +You can choose the default priority for newly created tasks with +"maniphest.default-priority". +EOTEXT + )); + return array( $this->newOption('maniphest.custom-field-definitions', 'wild', array()) @@ -360,30 +393,7 @@ EOTEXT $priority_type, $priority_defaults) ->setSummary(pht('Configure Maniphest priority names.')) - ->setDescription( - pht( - 'Allows you to edit or override the default priorities available '. - 'in Maniphest, like "High", "Normal" and "Low". The configuration '. - 'should contain a map of priority constants to priority '. - 'specifications (see defaults below for examples).'. - "\n\n". - 'The keys you can define for a priority are:'. - "\n\n". - ' - `name` Name of the priority.'."\n". - ' - `short` Alternate shorter name, used in UIs where there is '. - ' not much space available.'."\n". - ' - `color` A color for this priority, like "red" or "blue".'. - ' - `keywords` An optional list of keywords which can '. - ' be used to select this priority when using `!priority` '. - ' commands in email.'."\n". - ' - `disabled` Optional boolean to prevent users from choosing '. - ' this priority when creating or editing tasks. Existing '. - ' tasks will be unaffected, and can be batch edited to a '. - ' different priority or left to eventually die out.'. - "\n\n". - 'You can choose which priority is the default for newly created '. - 'tasks with `%s`.', - 'maniphest.default-priority')), + ->setDescription($priorities_description), $this->newOption('maniphest.statuses', $status_type, $status_defaults) ->setSummary(pht('Configure Maniphest task statuses.')) ->setDescription($status_description) diff --git a/src/applications/maniphest/constants/ManiphestTaskPriority.php b/src/applications/maniphest/constants/ManiphestTaskPriority.php index 16a35a9e83..d559299af9 100644 --- a/src/applications/maniphest/constants/ManiphestTaskPriority.php +++ b/src/applications/maniphest/constants/ManiphestTaskPriority.php @@ -2,6 +2,8 @@ final class ManiphestTaskPriority extends ManiphestConstants { + const UNKNOWN_PRIORITY_KEYWORD = '!!unknown!!'; + /** * Get the priorities and their full descriptions. * @@ -41,6 +43,58 @@ final class ManiphestTaskPriority extends ManiphestConstants { return $map; } + /** + * Get the canonical keyword for a given priority constant. + * + * @return string|null Keyword, or `null` if no keyword is configured. + */ + public static function getKeywordForTaskPriority($priority) { + $map = self::getConfig(); + + $spec = idx($map, $priority); + if (!$spec) { + return null; + } + + $keywords = idx($spec, 'keywords'); + if (!$keywords) { + return null; + } + + return head($keywords); + } + + + /** + * Get a map of supported alternate names for each priority. + * + * Keys are aliases, like "wish" and "wishlist". Values are canonical + * priority keywords, like "wishlist". + * + * @return map Map of aliases to canonical priority keywords. + */ + public static function getTaskPriorityAliasMap() { + $keyword_map = self::getTaskPriorityKeywordsMap(); + + $result = array(); + foreach ($keyword_map as $key => $keywords) { + $target = self::getKeywordForTaskPriority($key); + if ($target === null) { + continue; + } + + // NOTE: Include the raw priority value, like "25", in the list of + // aliases. This supports legacy sources like saved EditEngine forms. + $result[$key] = $target; + + foreach ($keywords as $keyword) { + $result[$keyword] = $target; + } + } + + return $result; + } + /** * Get the priorities and their related short (one-word) descriptions. @@ -105,17 +159,41 @@ final class ManiphestTaskPriority extends ManiphestConstants { return 'fa-arrow-right'; } + public static function getTaskPriorityFromKeyword($keyword) { + $map = self::getTaskPriorityKeywordsMap(); + + foreach ($map as $priority => $keywords) { + if (in_array($keyword, $keywords)) { + return $priority; + } + } + + return null; + } + public static function isDisabledPriority($priority) { $config = idx(self::getConfig(), $priority, array()); return idx($config, 'disabled', false); } - private static function getConfig() { + public static function getConfig() { $config = PhabricatorEnv::getEnvConfig('maniphest.priorities'); krsort($config); return $config; } + private static function isValidPriorityKeyword($keyword) { + if (!strlen($keyword) || strlen($keyword) > 64) { + return false; + } + + // Alphanumeric, but not exclusively numeric + if (!preg_match('/^(?![0-9]*$)[a-zA-Z0-9]+$/', $keyword)) { + return false; + } + return true; + } + public static function validateConfiguration($config) { if (!is_array($config)) { throw new Exception( @@ -125,6 +203,7 @@ final class ManiphestTaskPriority extends ManiphestConstants { $config)); } + $all_keywords = array(); foreach ($config as $key => $value) { if (!ctype_digit((string)$key)) { throw new Exception( @@ -145,11 +224,38 @@ final class ManiphestTaskPriority extends ManiphestConstants { $value, array( 'name' => 'string', + 'keywords' => 'list', 'short' => 'optional string', 'color' => 'optional string', - 'keywords' => 'optional list', 'disabled' => 'optional bool', )); + + $keywords = $value['keywords']; + foreach ($keywords as $keyword) { + if (!self::isValidPriorityKeyword($keyword)) { + throw new Exception( + pht( + 'Key "%s" is not a valid priority keyword. Priority keywords '. + 'must be 1-64 alphanumeric characters and cannot be '. + 'exclusively digits. For example, "%s" or "%s" are '. + 'reasonable choices.', + $keyword, + 'low', + 'critical')); + } + + if (isset($all_keywords[$keyword])) { + throw new Exception( + pht( + 'Two different task priorities ("%s" and "%s") have the same '. + 'keyword ("%s"). Keywords must uniquely identify priorities.', + $value['name'], + $all_keywords[$keyword], + $keyword)); + } + + $all_keywords[$keyword] = $value['name']; + } } } diff --git a/src/applications/maniphest/constants/ManiphestTaskStatus.php b/src/applications/maniphest/constants/ManiphestTaskStatus.php index 5734892f0a..53d2e1afe3 100644 --- a/src/applications/maniphest/constants/ManiphestTaskStatus.php +++ b/src/applications/maniphest/constants/ManiphestTaskStatus.php @@ -97,7 +97,7 @@ final class ManiphestTaskStatus extends ManiphestConstants { ->setName($name) ->setIcon($icon) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($color); + ->setColor($color); return $tag; } @@ -232,16 +232,17 @@ final class ManiphestTaskStatus extends ManiphestConstants { * @task validate */ public static function isValidStatusConstant($constant) { - if (strlen($constant) > 12) { + if (!strlen($constant) || strlen($constant) > 64) { return false; } - if (!preg_match('/^[a-z0-9]+\z/', $constant)) { + + // Alphanumeric, but not exclusively numeric + if (!preg_match('/^(?![0-9]*$)[a-zA-Z0-9]+$/', $constant)) { return false; } return true; } - /** * @task validate */ @@ -250,10 +251,9 @@ final class ManiphestTaskStatus extends ManiphestConstants { if (!self::isValidStatusConstant($key)) { throw new Exception( pht( - 'Key "%s" is not a valid status constant. Status constants must '. - 'be 1-12 characters long and contain only lowercase letters (a-z) '. - 'and digits (0-9). For example, "%s" or "%s" are reasonable '. - 'choices.', + 'Key "%s" is not a valid status constant. Status constants '. + 'must be 1-64 alphanumeric characters and cannot be exclusively '. + 'digits. For example, "%s" or "%s" are reasonable choices.', $key, 'open', 'closed')); diff --git a/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php b/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php index 20f4c438a7..140c86d6dd 100644 --- a/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php +++ b/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php @@ -10,10 +10,15 @@ final class ManiphestTaskStatusTestCase extends PhabricatorTestCase { 'duplicate2' => true, '' => false, - 'longlonglonglong' => false, + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' => + false, '.' => false, - 'ABCD' => false, + ' ' => false, + 'ABCD' => true, 'a b c ' => false, + '1' => false, + '111' => false, + '11a' => true, ); foreach ($map as $input => $expect) { diff --git a/src/applications/maniphest/controller/ManiphestBatchEditController.php b/src/applications/maniphest/controller/ManiphestBatchEditController.php index 90fcda28ec..f2ec4b433f 100644 --- a/src/applications/maniphest/controller/ManiphestBatchEditController.php +++ b/src/applications/maniphest/controller/ManiphestBatchEditController.php @@ -173,7 +173,7 @@ final class ManiphestBatchEditController extends ManiphestController { 'a', array( 'href' => '#', - 'class' => 'button green', + 'class' => 'button button-green', 'sigil' => 'add-action', 'mustcapture' => true, ), diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index f16281691b..3cc420a4c6 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -93,7 +93,7 @@ final class ManiphestReportController extends ManiphestController { ORDER BY x.dateCreated ASC', $table->getTableName(), $joins, - ManiphestTransaction::TYPE_STATUS); + ManiphestTaskStatusTransaction::TRANSACTIONTYPE); $stats = array(); $day_buckets = array(); diff --git a/src/applications/maniphest/controller/ManiphestSubpriorityController.php b/src/applications/maniphest/controller/ManiphestSubpriorityController.php index 25920212c8..8869b6a327 100644 --- a/src/applications/maniphest/controller/ManiphestSubpriorityController.php +++ b/src/applications/maniphest/controller/ManiphestSubpriorityController.php @@ -40,14 +40,17 @@ final class ManiphestSubpriorityController extends ManiphestController { $is_end = false); } + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head(idx($keyword_map, $pri)); + $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) - ->setNewValue($pri); + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) + ->setNewValue($keyword); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY) + ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) ->setNewValue($sub); $editor = id(new ManiphestTransactionEditor()) diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index 514090dda8..ecfb723f97 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -36,6 +36,7 @@ final class ManiphestTaskDetailController extends ManiphestController { ManiphestTaskHasMockEdgeType::EDGECONST, PhabricatorObjectMentionedByObjectEdgeType::EDGECONST, PhabricatorObjectMentionsObjectEdgeType::EDGECONST, + ManiphestTaskHasDuplicateTaskEdgeType::EDGECONST, ); $phid = $task->getPHID(); @@ -159,6 +160,7 @@ final class ManiphestTaskDetailController extends ManiphestController { $related_tabs[] = $this->newMocksTab($task, $query); $related_tabs[] = $this->newMentionsTab($task, $query); + $related_tabs[] = $this->newDuplicatesTab($task, $query); $tab_view = null; @@ -233,13 +235,19 @@ final class ManiphestTaskDetailController extends ManiphestController { ManiphestTaskPoints::getPointsLabel()); $tag = id(new PHUITagView()) ->setName($points_name) - ->setShade('blue') + ->setColor(PHUITagView::COLOR_BLUE) ->setType(PHUITagView::TYPE_SHADE); $view->addTag($tag); } } + $subtype = $task->newSubtypeObject(); + if ($subtype && $subtype->hasTagView()) { + $subtype_tag = $subtype->newTagView(); + $view->addTag($subtype_tag); + } + return $view; } @@ -277,7 +285,7 @@ final class ManiphestTaskDetailController extends ManiphestController { $can_create = (bool)$edit_config; $can_reassign = $edit_engine->hasEditAccessToTransaction( - ManiphestTransaction::TYPE_OWNER); + ManiphestTaskOwnerTransaction::TRANSACTIONTYPE); if ($can_create) { $form_key = $edit_config->getIdentifier(); @@ -547,6 +555,32 @@ final class ManiphestTaskDetailController extends ManiphestController { ->appendChild($view); } + private function newDuplicatesTab( + ManiphestTask $task, + PhabricatorEdgeQuery $edge_query) { + + $in_type = ManiphestTaskHasDuplicateTaskEdgeType::EDGECONST; + $in_phids = $edge_query->getDestinationPHIDs(array(), array($in_type)); + + $viewer = $this->getViewer(); + $in_handles = $viewer->loadHandles($in_phids); + $in_handles = $this->getCompleteHandles($in_handles); + + $view = new PHUIPropertyListView(); + + if (!count($in_handles)) { + return null; + } + + $view->addProperty( + pht('Duplicates Merged Here'), $in_handles->renderList()); + + return id(new PHUITabView()) + ->setName(pht('Duplicates')) + ->setKey('duplicates') + ->appendChild($view); + } + private function getCompleteHandles(PhabricatorHandleList $handles) { $phids = array(); diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index 54889aea4a..5e77e0cb0d 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -77,6 +77,8 @@ final class ManiphestEditEngine $status_map = $this->getTaskStatusMap($object); $priority_map = $this->getTaskPriorityMap($object); + $alias_map = ManiphestTaskPriority::getTaskPriorityAliasMap(); + if ($object->isClosed()) { $default_status = ManiphestTaskStatus::getDefaultStatus(); } else { @@ -150,7 +152,7 @@ EODOCS ->setConduitDescription(pht('Create as a subtask of another task.')) ->setConduitTypeDescription(pht('PHID of the parent task.')) ->setAliases(array('parentPHID')) - ->setTransactionType(ManiphestTransaction::TYPE_PARENT) + ->setTransactionType(ManiphestTaskParentTransaction::TRANSACTIONTYPE) ->setHandleParameterType(new ManiphestTaskListHTTPParameterType()) ->setSingleValue(null) ->setIsReorderable(false) @@ -179,7 +181,7 @@ EODOCS ->setDescription(pht('Name of the task.')) ->setConduitDescription(pht('Rename the task.')) ->setConduitTypeDescription(pht('New task name.')) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getTitle()), id(new PhabricatorUsersEditField()) @@ -190,7 +192,7 @@ EODOCS ->setConduitDescription(pht('Reassign the task.')) ->setConduitTypeDescription( pht('New task owner, or `null` to unassign.')) - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setSingleValue($object->getOwnerPHID()) ->setCommentActionLabel(pht('Assign / Claim')) @@ -202,7 +204,7 @@ EODOCS ->setDescription(pht('Status of the task.')) ->setConduitDescription(pht('Change the task status.')) ->setConduitTypeDescription(pht('New task status constant.')) - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setValue($object->getStatus()) ->setOptions($status_map) @@ -215,12 +217,13 @@ EODOCS ->setDescription(pht('Priority of the task.')) ->setConduitDescription(pht('Change the priority of the task.')) ->setConduitTypeDescription(pht('New task priority constant.')) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) - ->setValue($object->getPriority()) + ->setValue($object->getPriorityKeyword()) ->setOptions($priority_map) - ->setCommentActionLabel(pht('Change Priority')) - ->setCanApplyWithoutEditCapability(true), + ->setOptionAliases($alias_map) + ->setCanApplyWithoutEditCapability(true) + ->setCommentActionLabel(pht('Change Priority')), ); if (ManiphestTaskPoints::getIsEnabled()) { @@ -233,7 +236,7 @@ EODOCS ->setDescription(pht('Point value of the task.')) ->setConduitDescription(pht('Change the task point value.')) ->setConduitTypeDescription(pht('New task point value.')) - ->setTransactionType(ManiphestTransaction::TYPE_POINTS) + ->setTransactionType(ManiphestTaskPointsTransaction::TRANSACTIONTYPE) ->setIsCopyable(true) ->setValue($object->getPoints()) ->setCommentActionLabel($action_label); @@ -245,12 +248,62 @@ EODOCS ->setDescription(pht('Task description.')) ->setConduitDescription(pht('Update the task description.')) ->setConduitTypeDescription(pht('New task description.')) - ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setTransactionType(ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE) ->setValue($object->getDescription()) ->setPreviewPanel( id(new PHUIRemarkupPreviewPanel()) ->setHeader(pht('Description Preview'))); + $parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; + $subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; + + $src_phid = $object->getPHID(); + if ($src_phid) { + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(array($src_phid)) + ->withEdgeTypes( + array( + $parent_type, + $subtask_type, + )); + $edge_query->execute(); + + $parent_phids = $edge_query->getDestinationPHIDs( + array($src_phid), + array($parent_type)); + + $subtask_phids = $edge_query->getDestinationPHIDs( + array($src_phid), + array($subtask_type)); + } else { + $parent_phids = array(); + $subtask_phids = array(); + } + + $fields[] = id(new PhabricatorHandlesEditField()) + ->setKey('parents') + ->setLabel(pht('Parents')) + ->setDescription(pht('Parent tasks.')) + ->setConduitDescription(pht('Change the parents of this task.')) + ->setConduitTypeDescription(pht('List of parent task PHIDs.')) + ->setUseEdgeTransactions(true) + ->setIsConduitOnly(true) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $parent_type) + ->setValue($parent_phids); + + $fields[] = id(new PhabricatorHandlesEditField()) + ->setKey('subtasks') + ->setLabel(pht('Subtasks')) + ->setDescription(pht('Subtasks.')) + ->setConduitDescription(pht('Change the subtasks of this task.')) + ->setConduitTypeDescription(pht('List of subtask PHIDs.')) + ->setUseEdgeTransactions(true) + ->setIsConduitOnly(true) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $subtask_type) + ->setValue($parent_phids); + return $fields; } @@ -292,29 +345,29 @@ EODOCS private function getTaskPriorityMap(ManiphestTask $task) { $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); + $priority_keywords = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); $current_priority = $task->getPriority(); + $results = array(); + + foreach ($priority_map as $priority => $priority_name) { + $disabled = ManiphestTaskPriority::isDisabledPriority($priority); + if ($disabled && !($priority == $current_priority)) { + continue; + } + + $keyword = head(idx($priority_keywords, $priority)); + $results[$keyword] = $priority_name; + } // If the current value isn't a legitimate one, put it in the dropdown - // anyway so saving the form doesn't cause a side effects. + // anyway so saving the form doesn't cause any side effects. if (idx($priority_map, $current_priority) === null) { - $priority_map[$current_priority] = pht( + $results[ManiphestTaskPriority::UNKNOWN_PRIORITY_KEYWORD] = pht( '', $current_priority); } - foreach ($priority_map as $priority => $priority_name) { - // Always keep the current priority. - if ($priority == $current_priority) { - continue; - } - - if (ManiphestTaskPriority::isDisabledPriority($priority)) { - unset($priority_map[$priority]); - continue; - } - } - - return $priority_map; + return $results; } protected function newEditResponse( diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index 7270e99eac..7f6ca93ae6 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -18,18 +18,6 @@ final class ManiphestTransactionEditor $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_EDGE; - $types[] = ManiphestTransaction::TYPE_PRIORITY; - $types[] = ManiphestTransaction::TYPE_STATUS; - $types[] = ManiphestTransaction::TYPE_TITLE; - $types[] = ManiphestTransaction::TYPE_DESCRIPTION; - $types[] = ManiphestTransaction::TYPE_OWNER; - $types[] = ManiphestTransaction::TYPE_SUBPRIORITY; - $types[] = ManiphestTransaction::TYPE_MERGED_INTO; - $types[] = ManiphestTransaction::TYPE_MERGED_FROM; - $types[] = ManiphestTransaction::TYPE_UNBLOCK; - $types[] = ManiphestTransaction::TYPE_PARENT; - $types[] = ManiphestTransaction::TYPE_COVER_IMAGE; - $types[] = ManiphestTransaction::TYPE_POINTS; $types[] = PhabricatorTransactions::TYPE_COLUMNS; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -37,47 +25,19 @@ final class ManiphestTransactionEditor return $types; } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this task.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_PRIORITY: - if ($this->getIsNewObject()) { - return null; - } - return (int)$object->getPriority(); - case ManiphestTransaction::TYPE_STATUS: - if ($this->getIsNewObject()) { - return null; - } - return $object->getStatus(); - case ManiphestTransaction::TYPE_TITLE: - if ($this->getIsNewObject()) { - return null; - } - return $object->getTitle(); - case ManiphestTransaction::TYPE_DESCRIPTION: - if ($this->getIsNewObject()) { - return null; - } - return $object->getDescription(); - case ManiphestTransaction::TYPE_OWNER: - return nonempty($object->getOwnerPHID(), null); - case ManiphestTransaction::TYPE_SUBPRIORITY: - return $object->getSubpriority(); - case ManiphestTransaction::TYPE_COVER_IMAGE: - return $object->getCoverImageFilePHID(); - case ManiphestTransaction::TYPE_POINTS: - $points = $object->getPoints(); - if ($points !== null) { - $points = (double)$points; - } - return $points; - case ManiphestTransaction::TYPE_MERGED_INTO: - case ManiphestTransaction::TYPE_MERGED_FROM: - return null; - case ManiphestTransaction::TYPE_PARENT: case PhabricatorTransactions::TYPE_COLUMNS: return null; } @@ -88,31 +48,8 @@ final class ManiphestTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_PRIORITY: - return (int)$xaction->getNewValue(); - case ManiphestTransaction::TYPE_OWNER: - return nonempty($xaction->getNewValue(), null); - case ManiphestTransaction::TYPE_STATUS: - case ManiphestTransaction::TYPE_TITLE: - case ManiphestTransaction::TYPE_DESCRIPTION: - case ManiphestTransaction::TYPE_SUBPRIORITY: - case ManiphestTransaction::TYPE_MERGED_INTO: - case ManiphestTransaction::TYPE_MERGED_FROM: - case ManiphestTransaction::TYPE_UNBLOCK: - case ManiphestTransaction::TYPE_COVER_IMAGE: - return $xaction->getNewValue(); - case ManiphestTransaction::TYPE_PARENT: case PhabricatorTransactions::TYPE_COLUMNS: return $xaction->getNewValue(); - case ManiphestTransaction::TYPE_POINTS: - $value = $xaction->getNewValue(); - if (!strlen($value)) { - $value = null; - } - if ($value !== null) { - $value = (double)$value; - } - return $value; } } @@ -136,72 +73,6 @@ final class ManiphestTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_PRIORITY: - return $object->setPriority($xaction->getNewValue()); - case ManiphestTransaction::TYPE_STATUS: - return $object->setStatus($xaction->getNewValue()); - case ManiphestTransaction::TYPE_TITLE: - return $object->setTitle($xaction->getNewValue()); - case ManiphestTransaction::TYPE_DESCRIPTION: - return $object->setDescription($xaction->getNewValue()); - case ManiphestTransaction::TYPE_OWNER: - $phid = $xaction->getNewValue(); - - // Update the "ownerOrdering" column to contain the full name of the - // owner, if the task is assigned. - - $handle = null; - if ($phid) { - $handle = id(new PhabricatorHandleQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($phid)) - ->executeOne(); - } - - if ($handle) { - $object->setOwnerOrdering($handle->getName()); - } else { - $object->setOwnerOrdering(null); - } - - return $object->setOwnerPHID($phid); - case ManiphestTransaction::TYPE_SUBPRIORITY: - $object->setSubpriority($xaction->getNewValue()); - return; - case ManiphestTransaction::TYPE_MERGED_INTO: - $object->setStatus(ManiphestTaskStatus::getDuplicateStatus()); - return; - case ManiphestTransaction::TYPE_COVER_IMAGE: - $file_phid = $xaction->getNewValue(); - - if ($file_phid) { - $file = id(new PhabricatorFileQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($file_phid)) - ->executeOne(); - } else { - $file = null; - } - - if (!$file || !$file->isTransformableImage()) { - $object->setProperty('cover.filePHID', null); - $object->setProperty('cover.thumbnailPHID', null); - return; - } - - $xform_key = PhabricatorFileThumbnailTransform::TRANSFORM_WORKCARD; - - $xform = PhabricatorFileTransform::getTransformByKey($xform_key) - ->executeTransform($file); - - $object->setProperty('cover.filePHID', $file->getPHID()); - $object->setProperty('cover.thumbnailPHID', $xform->getPHID()); - return; - case ManiphestTransaction::TYPE_POINTS: - $object->setPoints($xaction->getNewValue()); - return; - case ManiphestTransaction::TYPE_MERGED_FROM: - case ManiphestTransaction::TYPE_PARENT: case PhabricatorTransactions::TYPE_COLUMNS: return; } @@ -212,22 +83,11 @@ final class ManiphestTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_PARENT: - $parent_phid = $xaction->getNewValue(); - $parent_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; - $task_phid = $object->getPHID(); - - id(new PhabricatorEdgeEditor()) - ->addEdge($parent_phid, $parent_type, $task_phid) - ->save(); - break; case PhabricatorTransactions::TYPE_COLUMNS: foreach ($xaction->getNewValue() as $move) { $this->applyBoardMove($object, $move); } break; - default: - break; } } @@ -240,7 +100,7 @@ final class ManiphestTransactionEditor $unblock_xaction = null; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $unblock_xaction = $xaction; break; } @@ -265,7 +125,8 @@ final class ManiphestTransactionEditor foreach ($blocked_tasks as $blocked_task) { $parent_xaction = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_UNBLOCK) + ->setTransactionType( + ManiphestTaskUnblockTransaction::TRANSACTIONTYPE) ->setOldValue(array($object->getPHID() => $old)) ->setNewValue(array($object->getPHID() => $new)); @@ -398,7 +259,7 @@ final class ManiphestTransactionEditor protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { - return $this->shouldSendMail($object, $xactions); + return true; } protected function supportsSearch() { @@ -426,11 +287,11 @@ final class ManiphestTransactionEditor parent::requireCapabilities($object, $xaction); $app_capability_map = array( - ManiphestTransaction::TYPE_PRIORITY => + ManiphestTaskPriorityTransaction::TRANSACTIONTYPE => ManiphestEditPriorityCapability::CAPABILITY, - ManiphestTransaction::TYPE_STATUS => + ManiphestTaskStatusTransaction::TRANSACTIONTYPE => ManiphestEditStatusCapability::CAPABILITY, - ManiphestTransaction::TYPE_OWNER => + ManiphestTaskOwnerTransaction::TRANSACTIONTYPE => ManiphestEditAssignCapability::CAPABILITY, PhabricatorTransactions::TYPE_EDIT_POLICY => ManiphestEditPoliciesCapability::CAPABILITY, @@ -482,7 +343,7 @@ final class ManiphestTransactionEditor $copy = parent::adjustObjectForPolicyChecks($object, $xactions); foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: $copy->setOwnerPHID($xaction->getNewValue()); break; default: @@ -534,8 +395,7 @@ final class ManiphestTransactionEditor */ public static function getAdjacentSubpriority( ManiphestTask $dst, - $is_after, - $allow_recursion = true) { + $is_after) { $query = id(new ManiphestTaskQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) @@ -557,76 +417,25 @@ final class ManiphestTransactionEditor // If we find an adjacent task, we average the two subpriorities and // return the result. if ($adjacent) { - $epsilon = 0.01; + $epsilon = 1.0; // If the adjacent task has a subpriority that is identical or very - // close to the task we're looking at, we're going to move it and all - // tasks with the same subpriority a little farther down the subpriority - // scale. - if ($allow_recursion && - (abs($adjacent->getSubpriority() - $base) < $epsilon)) { - $conn_w = $adjacent->establishConnection('w'); + // close to the task we're looking at, we're going to spread out all + // the nearby tasks. - $min = ($adjacent->getSubpriority() - ($epsilon)); - $max = ($adjacent->getSubpriority() + ($epsilon)); - - // Get all of the tasks with the similar subpriorities to the adjacent - // task, including the adjacent task itself. - $query = id(new ManiphestTaskQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPriorities(array($adjacent->getPriority())) - ->withSubpriorityBetween($min, $max); - - if (!$is_after) { - $query->setOrderVector(array('-priority', '-subpriority', '-id')); + $adjacent_sub = $adjacent->getSubpriority(); + if ((abs($adjacent_sub - $base) < $epsilon)) { + $base = self::disperseBlock( + $dst, + $epsilon * 2); + if ($is_after) { + $sub = $base - $epsilon; } else { - $query->setOrderVector(array('priority', 'subpriority', 'id')); - } - - $shift_all = $query->execute(); - $shift_last = last($shift_all); - - // Select the most extreme subpriority in the result set as the - // base value. - $shift_base = head($shift_all)->getSubpriority(); - - // Find the subpriority before or after the task at the end of the - // block. - list($shift_pri, $shift_sub) = self::getAdjacentSubpriority( - $shift_last, - $is_after, - $allow_recursion = false); - - $delta = ($shift_sub - $shift_base); - $count = count($shift_all); - - $shift = array(); - $cursor = 1; - foreach ($shift_all as $shift_task) { - $shift_target = $shift_base + (($cursor / $count) * $delta); - $cursor++; - - queryfx( - $conn_w, - 'UPDATE %T SET subpriority = %f WHERE id = %d', - $adjacent->getTableName(), - $shift_target, - $shift_task->getID()); - - // If we're shifting the adjacent task, update it. - if ($shift_task->getID() == $adjacent->getID()) { - $adjacent->setSubpriority($shift_target); - } - - // If we're shifting the original target task, update the base - // subpriority. - if ($shift_task->getID() == $dst->getID()) { - $base = $shift_target; - } + $sub = $base + $epsilon; } + } else { + $sub = ($adjacent_sub + $base) / 2; } - - $sub = ($adjacent->getSubpriority() + $base) / 2; } else { // Otherwise, we take a step away from the target's subpriority and // use that. @@ -640,155 +449,154 @@ final class ManiphestTransactionEditor return array($dst->getPriority(), $sub); } - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { + /** + * Distribute a cluster of tasks with similar subpriorities. + */ + private static function disperseBlock( + ManiphestTask $task, + $spacing) { - $errors = parent::validateTransaction($object, $type, $xactions); + $conn = $task->establishConnection('w'); - switch ($type) { - case ManiphestTransaction::TYPE_TITLE: - $missing = $this->validateIsEmptyTextField( - $object->getTitle(), - $xactions); + // Find a block of subpriority space which is, on average, sparse enough + // to hold all the tasks that are inside it with a reasonable level of + // separation between them. - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Task title is required.'), - nonempty(last($xactions), null)); + // We'll start by looking near the target task for a range of numbers + // which has more space available than tasks. For example, if the target + // task has subpriority 33 and we want to separate each task by at least 1, + // we might start by looking in the range [23, 43]. - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case ManiphestTransaction::TYPE_PARENT: - $with_effect = array(); - foreach ($xactions as $xaction) { - $task_phid = $xaction->getNewValue(); - if (!$task_phid) { - continue; - } - - $with_effect[] = $xaction; - - $task = id(new ManiphestTaskQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($task_phid)) - ->executeOne(); - if (!$task) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Parent task identifier "%s" does not identify a visible '. - 'task.', - $task_phid), - $xaction); - } - } - - if ($with_effect && !$this->getIsNewObject()) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'You can only select a parent task when creating a '. - 'transaction for the first time.'), - last($with_effect)); - } - break; - case ManiphestTransaction::TYPE_OWNER: - foreach ($xactions as $xaction) { - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - if (!strlen($new)) { - continue; - } - - if ($new === $old) { - continue; - } - - $assignee_list = id(new PhabricatorPeopleQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($new)) - ->execute(); - if (!$assignee_list) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'User "%s" is not a valid user.', - $new), - $xaction); - } - } - break; - case ManiphestTransaction::TYPE_COVER_IMAGE: - foreach ($xactions as $xaction) { - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - if (!$new) { - continue; - } - - if ($new === $old) { - continue; - } - - $file = id(new PhabricatorFileQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($new)) - ->executeOne(); - if (!$file) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('File "%s" is not valid.', $new), - $xaction); - continue; - } - - if (!$file->isTransformableImage()) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('File "%s" is not a valid image file.', $new), - $xaction); - continue; - } - } - break; - - case ManiphestTransaction::TYPE_POINTS: - foreach ($xactions as $xaction) { - $new = $xaction->getNewValue(); - if (strlen($new) && !is_numeric($new)) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('Points value must be numeric or empty.'), - $xaction); - continue; - } - - if ((double)$new < 0) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('Points value must be nonnegative.'), - $xaction); - continue; - } - } + // If we find fewer than 20 tasks there, we have room to reassign them + // with the desired level of separation. We space them out, then we're + // done. + + // However: if we find more than 20 tasks, we don't have enough room to + // distribute them. We'll widen our search and look in a bigger range, + // maybe [13, 53]. This range has more space, so if we find fewer than + // 40 tasks in this range we can spread them out. If we still find too + // many tasks, we keep widening the search. + + $base = $task->getSubpriority(); + + $scale = 4.0; + while (true) { + $range = ($spacing * $scale) / 2.0; + $min = ($base - $range); + $max = ($base + $range); + + $result = queryfx_one( + $conn, + 'SELECT COUNT(*) N FROM %T WHERE priority = %d AND + subpriority BETWEEN %f AND %f', + $task->getTableName(), + $task->getPriority(), + $min, + $max); + + $count = $result['N']; + if ($count < $scale) { + // We have found a block which we can make sparse enough, so bail and + // continue below with our selection. break; + } + // This block had too many tasks for its size, so try again with a + // bigger block. + $scale *= 2.0; } - return $errors; + $rows = queryfx_all( + $conn, + 'SELECT id FROM %T WHERE priority = %d AND + subpriority BETWEEN %f AND %f + ORDER BY priority, subpriority, id', + $task->getTableName(), + $task->getPriority(), + $min, + $max); + + $task_id = $task->getID(); + $result = null; + + // NOTE: In strict mode (which we encourage enabling) we can't structure + // this bulk update as an "INSERT ... ON DUPLICATE KEY UPDATE" unless we + // provide default values for ALL of the columns that don't have defaults. + + // This is gross, but we may be moving enough rows that individual + // queries are unreasonably slow. An alternate construction which might + // be worth evaluating is to use "CASE". Another approach is to disable + // strict mode for this query. + + $extra_columns = array( + 'phid' => '""', + 'authorPHID' => '""', + 'status' => '""', + 'priority' => 0, + 'title' => '""', + 'originalTitle' => '""', + 'description' => '""', + 'dateCreated' => 0, + 'dateModified' => 0, + 'mailKey' => '""', + 'viewPolicy' => '""', + 'editPolicy' => '""', + 'ownerOrdering' => '""', + 'spacePHID' => '""', + 'bridgedObjectPHID' => '""', + 'properties' => '""', + 'points' => 0, + 'subtype' => '""', + ); + + $defaults = implode(', ', $extra_columns); + + $sql = array(); + $offset = 0; + + // Often, we'll have more room than we need in the range. Distribute the + // tasks evenly over the whole range so that we're less likely to end up + // with tasks spaced exactly the minimum distance apart, which may + // get shifted again later. We have one fewer space to distribute than we + // have tasks. + $divisor = (double)(count($rows) - 1.0); + if ($divisor > 0) { + $available_distance = (($max - $min) / $divisor); + } else { + $available_distance = 0.0; + } + + foreach ($rows as $row) { + $subpriority = $min + ($offset * $available_distance); + + // If this is the task that we're spreading out relative to, keep track + // of where it is ending up so we can return the new subpriority. + $id = $row['id']; + if ($id == $task_id) { + $result = $subpriority; + } + + $sql[] = qsprintf( + $conn, + '(%d, %Q, %f)', + $id, + $defaults, + $subpriority); + + $offset++; + } + + foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { + queryfx( + $conn, + 'INSERT INTO %T (id, %Q, subpriority) VALUES %Q + ON DUPLICATE KEY UPDATE subpriority = VALUES(subpriority)', + $task->getTableName(), + implode(', ', array_keys($extra_columns)), + $chunk); + } + + return $result; } protected function validateAllTransactions( @@ -817,7 +625,8 @@ final class ManiphestTransactionEditor $any_assign = false; foreach ($xactions as $xaction) { - if ($xaction->getTransactionType() == ManiphestTransaction::TYPE_OWNER) { + if ($xaction->getTransactionType() == + ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) { $any_assign = true; break; } @@ -828,7 +637,7 @@ final class ManiphestTransactionEditor $new_status = null; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $new_status = $xaction->getNewValue(); break; } @@ -849,7 +658,7 @@ final class ManiphestTransactionEditor // Don't claim the task if the status is configured to not claim. if ($actor_phid && $is_claim) { $results[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setNewValue($actor_phid); } } @@ -892,7 +701,7 @@ final class ManiphestTransactionEditor $this->moreValidationErrors[] = $error; } break; - case ManiphestTransaction::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: // If this is a no-op update, don't expand it. $old_value = $object->getOwnerPHID(); $new_value = $xaction->getNewValue(); @@ -917,20 +726,6 @@ final class ManiphestTransactionEditor return $results; } - protected function extractFilePHIDsFromCustomTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - $phids = parent::extractFilePHIDsFromCustomTransaction($object, $xaction); - - switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_COVER_IMAGE: - $phids[] = $xaction->getNewValue(); - break; - } - - return $phids; - } - private function buildMoveTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { diff --git a/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php index 00f539d4a7..fffd0bac7d 100644 --- a/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php +++ b/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php @@ -40,7 +40,7 @@ abstract class ManiphestTaskAssignHeraldAction } $xaction = $adapter->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) ->setNewValue($phid); $adapter->queueTransaction($xaction); diff --git a/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php index c055266a44..2e2db4b62d 100644 --- a/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php +++ b/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php @@ -39,12 +39,15 @@ final class ManiphestTaskPriorityHeraldAction return; } + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head(idx($keyword_map, $priority)); + $xaction = $adapter->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) - ->setNewValue($priority); + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) + ->setNewValue($keyword); $adapter->queueTransaction($xaction); - $this->logEffect(self::DO_PRIORITY, $priority); + $this->logEffect(self::DO_PRIORITY, $keyword); } public function getHeraldActionStandardType() { diff --git a/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php index 26e212d9c7..c1ff3bcdc7 100644 --- a/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php +++ b/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php @@ -40,7 +40,7 @@ final class ManiphestTaskStatusHeraldAction } $xaction = $adapter->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setNewValue($status); $adapter->queueTransaction($xaction); diff --git a/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php b/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php index 404a36af8f..3fc1957b4f 100644 --- a/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php +++ b/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php @@ -22,15 +22,15 @@ final class PhabricatorManiphestTaskTestDataGenerator $template = new ManiphestTransaction(); // Accumulate Transactions $changes = array(); - $changes[ManiphestTransaction::TYPE_TITLE] = + $changes[ManiphestTaskTitleTransaction::TRANSACTIONTYPE] = $this->generateTitle(); - $changes[ManiphestTransaction::TYPE_DESCRIPTION] = + $changes[ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE] = $this->generateDescription(); - $changes[ManiphestTransaction::TYPE_OWNER] = + $changes[ManiphestTaskOwnerTransaction::TRANSACTIONTYPE] = $this->loadOwnerPHID(); - $changes[ManiphestTransaction::TYPE_STATUS] = + $changes[ManiphestTaskStatusTransaction::TRANSACTIONTYPE] = $this->generateTaskStatus(); - $changes[ManiphestTransaction::TYPE_PRIORITY] = + $changes[ManiphestTaskPriorityTransaction::TRANSACTIONTYPE] = $this->generateTaskPriority(); $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] = array('=' => $this->getCCPHIDs()); @@ -100,7 +100,10 @@ final class PhabricatorManiphestTaskTestDataGenerator } public function generateTaskPriority() { - return array_rand(ManiphestTaskPriority::getTaskPriorityMap()); + $pri = array_rand(ManiphestTaskPriority::getTaskPriorityMap()); + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head(idx($keyword_map, $pri)); + return $keyword; } public function generateTaskSubPriority() { diff --git a/src/applications/maniphest/mail/ManiphestReplyHandler.php b/src/applications/maniphest/mail/ManiphestReplyHandler.php index bc9b2456d4..cc2bdff9d0 100644 --- a/src/applications/maniphest/mail/ManiphestReplyHandler.php +++ b/src/applications/maniphest/mail/ManiphestReplyHandler.php @@ -25,11 +25,16 @@ final class ManiphestReplyHandler if ($is_new) { $xactions[] = $this->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(PhabricatorTransactions::TYPE_CREATE) + ->setNewValue(true); + + $xactions[] = $this->newTransaction() + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue(nonempty($mail->getSubject(), pht('Untitled Task'))); $xactions[] = $this->newTransaction() - ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE) ->setNewValue($body); $actor_phid = $actor->getPHID(); diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index 5c43963ed5..2ef0c90b84 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -17,8 +17,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $dateCreatedBefore; private $dateModifiedAfter; private $dateModifiedBefore; - private $subpriorityMin; - private $subpriorityMax; private $bridgedObjectPHIDs; private $hasOpenParents; private $hasOpenSubtasks; @@ -112,12 +110,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $this; } - public function withSubpriorityBetween($min, $max) { - $this->subpriorityMin = $min; - $this->subpriorityMax = $max; - return $this; - } - public function withSubscribers(array $subscribers) { $this->subscriberPHIDs = $subscribers; return $this; @@ -287,6 +279,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { break; } + $data = $this->didLoadRawRows($data); $tasks = $task_dao->loadAllFromArray($data); switch ($this->groupBy) { @@ -440,20 +433,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { $this->subpriorities); } - if ($this->subpriorityMin !== null) { - $where[] = qsprintf( - $conn, - 'task.subpriority >= %f', - $this->subpriorityMin); - } - - if ($this->subpriorityMax !== null) { - $where[] = qsprintf( - $conn, - 'task.subpriority <= %f', - $this->subpriorityMax); - } - if ($this->bridgedObjectPHIDs !== null) { $where[] = qsprintf( $conn, diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php index 0de7349e85..393451a2fc 100644 --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -250,8 +250,6 @@ final class ManiphestTaskSearchEngine $group = idx($this->getGroupValues(), $group); if ($group) { $query->setGroupBy($group); - } else { - $query->setGroupBy(head($this->getGroupValues())); } if ($this->taskTypeKey) { diff --git a/src/applications/maniphest/relationship/ManiphestTaskRelationship.php b/src/applications/maniphest/relationship/ManiphestTaskRelationship.php index 928d49e87d..04f78b8523 100644 --- a/src/applications/maniphest/relationship/ManiphestTaskRelationship.php +++ b/src/applications/maniphest/relationship/ManiphestTaskRelationship.php @@ -19,7 +19,8 @@ abstract class ManiphestTaskRelationship protected function newMergeIntoTransactions(ManiphestTask $task) { return array( id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_MERGED_INTO) + ->setTransactionType( + ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE) ->setNewValue($task->getPHID()), ); } @@ -34,7 +35,8 @@ abstract class ManiphestTaskRelationship ->setNewValue(array('+' => $subscriber_phids)); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_MERGED_FROM) + ->setTransactionType( + ManiphestTaskMergedFromTransaction::TRANSACTIONTYPE) ->setNewValue(mpull($tasks, 'getPHID')); return $xactions; diff --git a/src/applications/maniphest/search/ManiphestTaskFerretEngine.php b/src/applications/maniphest/search/ManiphestTaskFerretEngine.php new file mode 100644 index 0000000000..45b90471ea --- /dev/null +++ b/src/applications/maniphest/search/ManiphestTaskFerretEngine.php @@ -0,0 +1,27 @@ + array( 'ownerPHID' => 'phid?', - 'status' => 'text12', + 'status' => 'text64', 'priority' => 'uint32', 'title' => 'sort', 'originalTitle' => 'text', @@ -245,6 +246,17 @@ final class ManiphestTask extends ManiphestDAO ); } + public function getPriorityKeyword() { + $priority = $this->getPriority(); + + $keyword = ManiphestTaskPriority::getKeywordForTaskPriority($priority); + if ($keyword !== null) { + return $keyword; + } + + return ManiphestTaskPriority::UNKNOWN_PRIORITY_KEYWORD; + } + private function comparePriorityTo(ManiphestTask $other) { $upri = $this->getPriority(); $vpri = $other->getPriority(); @@ -540,6 +552,11 @@ final class ManiphestTask extends ManiphestDAO ); } + public function newSubtypeObject() { + $subtype_key = $this->getEditEngineSubtype(); + $subtype_map = $this->newEditEngineSubtypeMap(); + return idx($subtype_map, $subtype_key); + } /* -( PhabricatorFulltextInterface )--------------------------------------- */ @@ -587,4 +604,12 @@ final class ManiphestTask extends ManiphestDAO return new ManiphestTaskEditEngineLock(); } + +/* -( PhabricatorFerretInterface )----------------------------------------- */ + + + public function newFerretEngine() { + return new ManiphestTaskFerretEngine(); + } + } diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php index b9b8560b34..6301241050 100644 --- a/src/applications/maniphest/storage/ManiphestTransaction.php +++ b/src/applications/maniphest/storage/ManiphestTransaction.php @@ -1,25 +1,7 @@ getTransactionType()) { - case self::TYPE_EDGE: - case self::TYPE_UNBLOCK: + case ManiphestTaskEdgeTransaction::TRANSACTIONTYPE: + case ManiphestTaskUnblockTransaction::TRANSACTIONTYPE: return false; } return parent::shouldGenerateOldValue(); } - protected function newRemarkupChanges() { - $changes = array(); + public function shouldHideForFeed() { + // NOTE: Modular transactions don't currently support this, and it has + // very few callsites, and it's publish-time rather than display-time. + // This should probably become a supported, display-time behavior. For + // discussion, see T12787. - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - $changes[] = $this->newRemarkupChange() - ->setOldValue($this->getOldValue()) - ->setNewValue($this->getNewValue()); - break; + // 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; } - return $changes; + return parent::shouldHideForFeed(); } public function getRequiredHandlePHIDs() { @@ -75,7 +63,7 @@ final class ManiphestTransaction $old = $this->getOldValue(); switch ($this->getTransactionType()) { - case self::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: if ($new) { $phids[] = $new; } @@ -84,13 +72,13 @@ final class ManiphestTransaction $phids[] = $old; } break; - case self::TYPE_MERGED_INTO: + case ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE: $phids[] = $new; break; - case self::TYPE_MERGED_FROM: + case ManiphestTaskMergedFromTransaction::TRANSACTIONTYPE: $phids = array_merge($phids, $new); break; - case self::TYPE_EDGE: + case ManiphestTaskEdgeTransaction::TRANSACTIONTYPE: $phids = array_mergev( array( $phids, @@ -98,7 +86,7 @@ final class ManiphestTransaction array_keys(nonempty($new, array())), )); break; - case self::TYPE_ATTACH: + case ManiphestTaskAttachTransaction::TRANSACTIONTYPE: $old = nonempty($old, array()); $new = nonempty($new, array()); $phids = array_mergev( @@ -108,12 +96,12 @@ final class ManiphestTransaction array_keys(idx($old, 'FILE', array())), )); break; - case self::TYPE_UNBLOCK: + case ManiphestTaskUnblockTransaction::TRANSACTIONTYPE: foreach (array_keys($new) as $phid) { $phids[] = $phid; } break; - case self::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $commit_phid = $this->getMetadataValue('commitPHID'); if ($commit_phid) { $phids[] = $commit_phid; @@ -124,275 +112,27 @@ final class ManiphestTransaction return $phids; } - public function shouldHide() { - switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_EDGE: - $commit_phid = $this->getMetadataValue('commitPHID'); - $edge_type = $this->getMetadataValue('edge:type'); - - if ($edge_type == ManiphestTaskHasCommitEdgeType::EDGECONST) { - if ($commit_phid) { - return true; - } - } - break; - case self::TYPE_DESCRIPTION: - case self::TYPE_PRIORITY: - case self::TYPE_STATUS: - if ($this->getOldValue() === null) { - return true; - } else { - return false; - } - break; - case self::TYPE_SUBPRIORITY: - case self::TYPE_PARENT: - return true; - case self::TYPE_COVER_IMAGE: - // At least for now, don't show these. - return true; - case self::TYPE_POINTS: - if (!ManiphestTaskPoints::getIsEnabled()) { - return true; - } - } - - return parent::shouldHide(); - } - - public function shouldHideForMail(array $xactions) { - switch ($this->getTransactionType()) { - case self::TYPE_POINTS: - return true; - } - - return parent::shouldHideForMail($xactions); - } - - 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; - case self::TYPE_POINTS: - return true; - } - - return parent::shouldHideForFeed(); - } - - public function getActionStrength() { - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - return 1.4; - case self::TYPE_STATUS: - return 1.3; - case self::TYPE_OWNER: - return 1.2; - case self::TYPE_PRIORITY: - return 1.1; - } - - return parent::getActionStrength(); - } - - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_OWNER: - if ($this->getAuthorPHID() == $new) { - return 'green'; - } else if (!$new) { - return 'black'; - } else if (!$old) { - return 'green'; - } else { - return 'green'; - } - - case self::TYPE_STATUS: - $color = ManiphestTaskStatus::getStatusColor($new); - if ($color !== null) { - return $color; - } - - if (ManiphestTaskStatus::isOpenStatus($new)) { - return 'green'; - } else { - return 'indigo'; - } - - case self::TYPE_PRIORITY: - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return 'green'; - } else if ($old > $new) { - return 'grey'; - } else { - return 'yellow'; - } - - case self::TYPE_MERGED_FROM: - return 'orange'; - - case self::TYPE_MERGED_INTO: - return 'indigo'; - } - - return parent::getColor(); - } - public function getActionName() { $old = $this->getOldValue(); $new = $this->getNewValue(); - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht('Created'); - } - - return pht('Retitled'); - - case self::TYPE_STATUS: - $action = ManiphestTaskStatus::getStatusActionName($new); - if ($action) { - return $action; - } - - $old_closed = ManiphestTaskStatus::isClosedStatus($old); - $new_closed = ManiphestTaskStatus::isClosedStatus($new); - - if ($new_closed && !$old_closed) { - return pht('Closed'); - } else if (!$new_closed && $old_closed) { - return pht('Reopened'); - } else { - return pht('Changed Status'); - } - - case self::TYPE_DESCRIPTION: - return pht('Edited'); - - case self::TYPE_OWNER: - if ($this->getAuthorPHID() == $new) { - return pht('Claimed'); - } else if (!$new) { - return pht('Unassigned'); - } else if (!$old) { - return pht('Assigned'); - } else { - return pht('Reassigned'); - } - case PhabricatorTransactions::TYPE_COLUMNS: return pht('Changed Project Column'); - - case self::TYPE_PRIORITY: - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return pht('Triaged'); - } else if ($old > $new) { - return pht('Lowered Priority'); - } else { - return pht('Raised Priority'); - } - - case self::TYPE_EDGE: - case self::TYPE_ATTACH: - return pht('Attached'); - - case self::TYPE_UNBLOCK: - $old_status = head($old); - $new_status = head($new); - - $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); - $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); - - if ($old_closed && !$new_closed) { - return pht('Block'); - } else if (!$old_closed && $new_closed) { - return pht('Unblock'); - } else { - return pht('Blocker'); - } - - case self::TYPE_MERGED_INTO: - case self::TYPE_MERGED_FROM: - return pht('Merged'); - } return parent::getActionName(); } public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - switch ($this->getTransactionType()) { - case self::TYPE_OWNER: - return 'fa-user'; - - case self::TYPE_TITLE: - if ($old === null) { - return 'fa-pencil'; - } - - return 'fa-pencil'; - - case self::TYPE_STATUS: - $action = ManiphestTaskStatus::getStatusIcon($new); - if ($action !== null) { - return $action; - } - - if (ManiphestTaskStatus::isClosedStatus($new)) { - return 'fa-check'; - } else { - return 'fa-pencil'; - } - - case self::TYPE_DESCRIPTION: - return 'fa-pencil'; - case PhabricatorTransactions::TYPE_COLUMNS: return 'fa-columns'; - - case self::TYPE_MERGED_INTO: - return 'fa-check'; - case self::TYPE_MERGED_FROM: - return 'fa-compress'; - - case self::TYPE_PRIORITY: - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return 'fa-arrow-right'; - } else if ($old > $new) { - return 'fa-arrow-down'; - } else { - return 'fa-arrow-up'; - } - - case self::TYPE_EDGE: - case self::TYPE_ATTACH: - return 'fa-thumb-tack'; - - case self::TYPE_UNBLOCK: - return 'fa-shield'; - } return parent::getIcon(); } - public function getTitle() { $author_phid = $this->getAuthorPHID(); @@ -400,244 +140,13 @@ final class ManiphestTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_CREATE: - return pht( - '%s created this task.', - $this->renderHandleLink($author_phid)); case PhabricatorTransactions::TYPE_SUBTYPE: return pht( '%s changed the subtype of this task from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderSubtypeName($old), $this->renderSubtypeName($new)); - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s created this task.', - $this->renderHandleLink($author_phid)); - } - return pht( - '%s changed the title from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - - case self::TYPE_DESCRIPTION: - return pht( - '%s edited the task description.', - $this->renderHandleLink($author_phid)); - - case self::TYPE_STATUS: - $old_closed = ManiphestTaskStatus::isClosedStatus($old); - $new_closed = ManiphestTaskStatus::isClosedStatus($new); - - $old_name = ManiphestTaskStatus::getTaskStatusName($old); - $new_name = ManiphestTaskStatus::getTaskStatusName($new); - - $commit_phid = $this->getMetadataValue('commitPHID'); - - if ($new_closed && !$old_closed) { - if ($new == ManiphestTaskStatus::getDuplicateStatus()) { - if ($commit_phid) { - return pht( - '%s closed this task as a duplicate by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s closed this task as a duplicate.', - $this->renderHandleLink($author_phid)); - } - } else { - if ($commit_phid) { - return pht( - '%s closed this task as "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s closed this task as "%s".', - $this->renderHandleLink($author_phid), - $new_name); - } - } - } else if (!$new_closed && $old_closed) { - if ($commit_phid) { - return pht( - '%s reopened this task as "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s reopened this task as "%s".', - $this->renderHandleLink($author_phid), - $new_name); - } - } else { - if ($commit_phid) { - return pht( - '%s changed the task status from "%s" to "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $old_name, - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s changed the task status from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old_name, - $new_name); - } - } - - case self::TYPE_UNBLOCK: - $blocker_phid = key($new); - $old_status = head($old); - $new_status = head($new); - - $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); - $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); - - $old_name = ManiphestTaskStatus::getTaskStatusName($old_status); - $new_name = ManiphestTaskStatus::getTaskStatusName($new_status); - - if ($this->getMetadataValue('blocker.new')) { - return pht( - '%s created subtask %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid)); - } else if ($old_closed && !$new_closed) { - return pht( - '%s reopened subtask %s as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $new_name); - } else if (!$old_closed && $new_closed) { - return pht( - '%s closed subtask %s as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $new_name); - } else { - return pht( - '%s changed the status of subtask %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $old_name, - $new_name); - } - - case self::TYPE_OWNER: - if ($author_phid == $new) { - return pht( - '%s claimed this task.', - $this->renderHandleLink($author_phid)); - } else if (!$new) { - return pht( - '%s removed %s as the assignee of this task.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old)); - } else if (!$old) { - return pht( - '%s assigned this task to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); - } else { - return pht( - '%s reassigned this task from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - - case self::TYPE_PRIORITY: - $old_name = ManiphestTaskPriority::getTaskPriorityName($old); - $new_name = ManiphestTaskPriority::getTaskPriorityName($new); - - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return pht( - '%s triaged this task as "%s" priority.', - $this->renderHandleLink($author_phid), - $new_name); - } else if ($old > $new) { - return pht( - '%s lowered the priority of this task from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old_name, - $new_name); - } else { - return pht( - '%s raised the priority of this task from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old_name, - $new_name); - } - - case self::TYPE_ATTACH: - $old = nonempty($old, array()); - $new = nonempty($new, array()); - $new = array_keys(idx($new, 'FILE', array())); - $old = array_keys(idx($old, 'FILE', array())); - - $added = array_diff($new, $old); - $removed = array_diff($old, $new); - if ($added && !$removed) { - return pht( - '%s attached %s file(s): %s.', - $this->renderHandleLink($author_phid), - phutil_count($added), - $this->renderHandleList($added)); - } else if ($removed && !$added) { - return pht( - '%s detached %s file(s): %s.', - $this->renderHandleLink($author_phid), - phutil_count($removed), - $this->renderHandleList($removed)); - } else { - return pht( - '%s changed file(s), attached %s: %s; detached %s: %s.', - $this->renderHandleLink($author_phid), - phutil_count($added), - $this->renderHandleList($added), - phutil_count($removed), - $this->renderHandleList($removed)); - } - - case self::TYPE_MERGED_INTO: - return pht( - '%s closed this task as a duplicate of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); break; - - case self::TYPE_MERGED_FROM: - return pht( - '%s merged %s task(s): %s.', - $this->renderHandleLink($author_phid), - phutil_count($new), - $this->renderHandleList($new)); - break; - - case self::TYPE_POINTS: - if ($old === null) { - return pht( - '%s set the point value for this task to %s.', - $this->renderHandleLink($author_phid), - $new); - } else if ($new === null) { - return pht( - '%s removed the point value for this task.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s changed the point value for this task from %s to %s.', - $this->renderHandleLink($author_phid), - $old, - $new); - } - } return parent::getTitle(); @@ -651,236 +160,6 @@ final class ManiphestTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - - return pht( - '%s renamed %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); - - case self::TYPE_DESCRIPTION: - return pht( - '%s edited the description of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - case self::TYPE_STATUS: - $old_closed = ManiphestTaskStatus::isClosedStatus($old); - $new_closed = ManiphestTaskStatus::isClosedStatus($new); - - $old_name = ManiphestTaskStatus::getTaskStatusName($old); - $new_name = ManiphestTaskStatus::getTaskStatusName($new); - - $commit_phid = $this->getMetadataValue('commitPHID'); - - if ($new_closed && !$old_closed) { - if ($new == ManiphestTaskStatus::getDuplicateStatus()) { - if ($commit_phid) { - return pht( - '%s closed %s as a duplicate by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s closed %s as a duplicate.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - } else { - if ($commit_phid) { - return pht( - '%s closed %s as "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s closed %s as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name); - } - } - } else if (!$new_closed && $old_closed) { - if ($commit_phid) { - return pht( - '%s reopened %s as "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s reopened %s as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name); - } - } else { - if ($commit_phid) { - return pht( - '%s changed the status of %s from "%s" to "%s" by committing %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name, - $this->renderHandleLink($commit_phid)); - } else { - return pht( - '%s changed the status of %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name); - } - } - - case self::TYPE_UNBLOCK: - $blocker_phid = key($new); - $old_status = head($old); - $new_status = head($new); - - $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); - $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); - - $old_name = ManiphestTaskStatus::getTaskStatusName($old_status); - $new_name = ManiphestTaskStatus::getTaskStatusName($new_status); - - if ($old_closed && !$new_closed) { - return pht( - '%s reopened %s, a subtask of %s, as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $this->renderHandleLink($object_phid), - $new_name); - } else if (!$old_closed && $new_closed) { - return pht( - '%s closed %s, a subtask of %s, as "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $this->renderHandleLink($object_phid), - $new_name); - } else { - return pht( - '%s changed the status of %s, a subtask of %s, '. - 'from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($blocker_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name); - } - - case self::TYPE_OWNER: - if ($author_phid == $new) { - return pht( - '%s claimed %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else if (!$new) { - return pht( - '%s placed %s up for grabs.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else if (!$old) { - return pht( - '%s assigned %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($new)); - } else { - return pht( - '%s reassigned %s from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - - case self::TYPE_PRIORITY: - $old_name = ManiphestTaskPriority::getTaskPriorityName($old); - $new_name = ManiphestTaskPriority::getTaskPriorityName($new); - - if ($old == ManiphestTaskPriority::getDefaultPriority()) { - return pht( - '%s triaged %s as "%s" priority.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new_name); - } else if ($old > $new) { - return pht( - '%s lowered the priority of %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name); - } else { - return pht( - '%s raised the priority of %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old_name, - $new_name); - } - - case self::TYPE_ATTACH: - $old = nonempty($old, array()); - $new = nonempty($new, array()); - $new = array_keys(idx($new, 'FILE', array())); - $old = array_keys(idx($old, 'FILE', array())); - - $added = array_diff($new, $old); - $removed = array_diff($old, $new); - if ($added && !$removed) { - return pht( - '%s attached %d file(s) of %s: %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - count($added), - $this->renderHandleList($added)); - } else if ($removed && !$added) { - return pht( - '%s detached %d file(s) of %s: %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - count($removed), - $this->renderHandleList($removed)); - } else { - return pht( - '%s changed file(s) for %s, attached %d: %s; detached %d: %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - count($added), - $this->renderHandleList($added), - count($removed), - $this->renderHandleList($removed)); - } - - case self::TYPE_MERGED_INTO: - return pht( - '%s merged task %s into %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($new)); - - case self::TYPE_MERGED_FROM: - return pht( - '%s merged %s task(s) %s into %s.', - $this->renderHandleLink($author_phid), - phutil_count($new), - $this->renderHandleList($new), - $this->renderHandleLink($object_phid)); - case PhabricatorTransactions::TYPE_SUBTYPE: return pht( '%s changed the subtype of %s from "%s" to "%s".', @@ -893,39 +172,14 @@ final class ManiphestTransaction return parent::getTitleForFeed(); } - private function renderSubtypeName($value) { - $object = $this->getObject(); - $map = $object->newEditEngineSubtypeMap(); - if (!isset($map[$value])) { - return $value; - } - - return $map[$value]->getName(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); - } - public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { - case self::TYPE_MERGED_INTO: - case self::TYPE_STATUS: + case ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_STATUS; break; - case self::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_OWNER; break; case PhabricatorTransactions::TYPE_SUBSCRIBERS: @@ -941,10 +195,10 @@ final class ManiphestTransaction break; } break; - case self::TYPE_PRIORITY: + case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_PRIORITY; break; - case self::TYPE_UNBLOCK: + case ManiphestTaskUnblockTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_UNBLOCK; break; case PhabricatorTransactions::TYPE_COLUMNS: @@ -961,17 +215,26 @@ final class ManiphestTransaction } public function getNoEffectDescription() { - switch ($this->getTransactionType()) { - case self::TYPE_STATUS: + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: return pht('The task already has the selected status.'); - case self::TYPE_OWNER: + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: return pht('The task already has the selected owner.'); - case self::TYPE_PRIORITY: + case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE: return pht('The task already has the selected priority.'); } return parent::getNoEffectDescription(); } + public function renderSubtypeName($value) { + $object = $this->getObject(); + $map = $object->newEditEngineSubtypeMap(); + if (!isset($map[$value])) { + return $value; + } + + return $map[$value]->getName(); + } + } diff --git a/src/applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php b/src/applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php index 2678e946b4..c061c694e4 100644 --- a/src/applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php +++ b/src/applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php @@ -32,6 +32,7 @@ final class ManiphestTaskSubtypeDatasource $result = id(new PhabricatorTypeaheadResult()) ->setIcon($subtype->getIcon()) + ->setColor($subtype->getColor()) ->setPHID($key) ->setName($subtype->getName()); diff --git a/src/applications/maniphest/view/ManiphestTaskListView.php b/src/applications/maniphest/view/ManiphestTaskListView.php index 8e6cb3ab9a..5899be0c07 100644 --- a/src/applications/maniphest/view/ManiphestTaskListView.php +++ b/src/applications/maniphest/view/ManiphestTaskListView.php @@ -57,6 +57,9 @@ final class ManiphestTaskListView extends ManiphestView { Javelin::initBehavior('maniphest-list-editor'); } + $subtype_map = id(new ManiphestTask()) + ->newEditEngineSubtypeMap(); + foreach ($this->tasks as $task) { $item = id(new PHUIObjectItemView()) ->setUser($this->getUser()) @@ -105,6 +108,13 @@ final class ManiphestTaskListView extends ManiphestView { $item->addSigil('maniphest-task'); } + $subtype = $task->newSubtypeObject(); + if ($subtype && $subtype->hasTagView()) { + $subtype_tag = $subtype->newTagView() + ->setSlimShady(true); + $item->addAttribute($subtype_tag); + } + $project_handles = array_select_keys( $handles, array_reverse($task->getProjectPHIDs())); diff --git a/src/applications/maniphest/view/ManiphestTaskResultListView.php b/src/applications/maniphest/view/ManiphestTaskResultListView.php index 9e28e89d8d..0144df0e33 100644 --- a/src/applications/maniphest/view/ManiphestTaskResultListView.php +++ b/src/applications/maniphest/view/ManiphestTaskResultListView.php @@ -196,7 +196,7 @@ final class ManiphestTaskResultListView extends ManiphestView { array( 'href' => '#', 'mustcapture' => true, - 'class' => 'grey button', + 'class' => 'button button-grey', 'id' => 'batch-select-all', ), pht('Select All')); @@ -206,7 +206,7 @@ final class ManiphestTaskResultListView extends ManiphestView { array( 'href' => '#', 'mustcapture' => true, - 'class' => 'grey button', + 'class' => 'button button-grey', 'id' => 'batch-select-none', ), pht('Clear Selection')); @@ -224,7 +224,7 @@ final class ManiphestTaskResultListView extends ManiphestView { 'a', array( 'href' => '/maniphest/export/'.$saved_query->getQueryKey().'/', - 'class' => 'grey button', + 'class' => 'button button-grey', ), pht('Export to Excel')); diff --git a/src/applications/maniphest/xaction/ManiphestTaskAttachTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskAttachTransaction.php new file mode 100644 index 0000000000..e794c03bdc --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskAttachTransaction.php @@ -0,0 +1,91 @@ +getOldValue(); + $new = $this->getNewValue(); + + $old = nonempty($old, array()); + $new = nonempty($new, array()); + $new = array_keys(idx($new, 'FILE', array())); + $old = array_keys(idx($old, 'FILE', array())); + + $added = array_diff($new, $old); + $removed = array_diff($old, $new); + if ($added && !$removed) { + return pht( + '%s attached %s file(s): %s.', + $this->renderAuthor(), + phutil_count($added), + $this->renderHandleList($added)); + } else if ($removed && !$added) { + return pht( + '%s detached %s file(s): %s.', + $this->renderAuthor(), + phutil_count($removed), + $this->renderHandleList($removed)); + } else { + return pht( + '%s changed file(s), attached %s: %s; detached %s: %s.', + $this->renderAuthor(), + phutil_count($added), + $this->renderHandleList($added), + phutil_count($removed), + $this->renderHandleList($removed)); + } + + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old = nonempty($old, array()); + $new = nonempty($new, array()); + $new = array_keys(idx($new, 'FILE', array())); + $old = array_keys(idx($old, 'FILE', array())); + + $added = array_diff($new, $old); + $removed = array_diff($old, $new); + if ($added && !$removed) { + return pht( + '%s attached %d file(s) of %s: %s', + $this->renderAuthor(), + $this->renderObject(), + count($added), + $this->renderHandleList($added)); + } else if ($removed && !$added) { + return pht( + '%s detached %d file(s) of %s: %s', + $this->renderAuthor(), + $this->renderObject(), + count($removed), + $this->renderHandleList($removed)); + } else { + return pht( + '%s changed file(s) for %s, attached %d: %s; detached %d: %s', + $this->renderAuthor(), + $this->renderObject(), + count($added), + $this->renderHandleList($added), + count($removed), + $this->renderHandleList($removed)); + } + } + + public function getIcon() { + return 'fa-thumb-tack'; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php new file mode 100644 index 0000000000..eb29c711f8 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php @@ -0,0 +1,106 @@ +getCoverImageFilePHID(); + } + + public function applyInternalEffects($object, $value) { + $file_phid = $value; + + if ($file_phid) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($file_phid)) + ->executeOne(); + } else { + $file = null; + } + + if (!$file || !$file->isTransformableImage()) { + $object->setProperty('cover.filePHID', null); + $object->setProperty('cover.thumbnailPHID', null); + return; + } + + $xform_key = PhabricatorFileThumbnailTransform::TRANSFORM_WORKCARD; + $xform = PhabricatorFileTransform::getTransformByKey($xform_key) + ->executeTransform($file); + + $object->setProperty('cover.filePHID', $file->getPHID()); + $object->setProperty('cover.thumbnailPHID', $xform->getPHID()); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s set the cover image to %s.', + $this->renderAuthor(), + $this->renderHandle($new)); + } + + return pht( + '%s updated the cover image to %s.', + $this->renderAuthor(), + $this->renderHandle($new)); + + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s added a cover image to %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + return pht( + '%s updated the cover image for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + $viewer = $this->getActor(); + + foreach ($xactions as $xaction) { + $file_phid = $xaction->getNewValue(); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + + if (!$file) { + $errors[] = $this->newInvalidError( + pht('"%s" is not a valid file PHID.', + $file_phid)); + } else { + if (!$file->isViewableImage()) { + $mime_type = $file->getMimeType(); + $errors[] = $this->newInvalidError( + pht('File mime type of "%s" is not a valid viewable image.', + $mime_type)); + } + } + + } + + return $errors; + } + + public function getIcon() { + return 'fa-image'; + } + + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php new file mode 100644 index 0000000000..009327ed9b --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php @@ -0,0 +1,61 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getActionName() { + return pht('Edited'); + } + + public function getTitle() { + return pht( + '%s updated the task description.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the task description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO TASK DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php new file mode 100644 index 0000000000..18f9a1da1f --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php @@ -0,0 +1,31 @@ +getMetadataValue('commitPHID'); + $edge_type = $this->getMetadataValue('edge:type'); + + if ($edge_type == ManiphestTaskHasCommitEdgeType::EDGECONST) { + if ($commit_phid) { + return true; + } + } + } + + public function getActionName() { + return pht('Attached'); + } + + public function getIcon() { + return 'fa-thumb-tack'; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php new file mode 100644 index 0000000000..a4a0440604 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php @@ -0,0 +1,45 @@ +getNewValue(); + + return pht( + '%s merged %s task(s): %s.', + $this->renderAuthor(), + phutil_count($new), + $this->renderHandleList($new)); + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + return pht( + '%s merged %s task(s) %s into %s.', + $this->renderAuthor(), + phutil_count($new), + $this->renderHandleList($new), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-compress'; + } + + public function getColor() { + return 'orange'; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php new file mode 100644 index 0000000000..cd0cad6a39 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php @@ -0,0 +1,47 @@ +setStatus(ManiphestTaskStatus::getDuplicateStatus()); + } + + public function getActionName() { + return pht('Merged'); + } + + public function getTitle() { + $new = $this->getNewValue(); + + return pht( + '%s closed this task as a duplicate of %s.', + $this->renderAuthor(), + $this->renderHandle($new)); + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + return pht( + '%s merged task %s into %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($new)); + } + + public function getIcon() { + return 'fa-check'; + } + + public function getColor() { + return 'indigo'; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php new file mode 100644 index 0000000000..d510fe8fbc --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php @@ -0,0 +1,158 @@ +getOwnerPHID(), null); + } + + public function applyInternalEffects($object, $value) { + // Update the "ownerOrdering" column to contain the full name of the + // owner, if the task is assigned. + + $handle = null; + if ($value) { + $handle = id(new PhabricatorHandleQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($value)) + ->executeOne(); + } + + if ($handle) { + $object->setOwnerOrdering($handle->getName()); + } else { + $object->setOwnerOrdering(null); + } + + $object->setOwnerPHID($value); + } + + public function getActionStrength() { + return 1.2; + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($this->getAuthorPHID() == $new) { + return pht('Claimed'); + } else if (!$new) { + return pht('Unassigned'); + } else if (!$old) { + return pht('Assigned'); + } else { + return pht('Reassigned'); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($this->getAuthorPHID() == $new) { + return pht( + '%s claimed this task.', + $this->renderAuthor()); + } else if (!$new) { + return pht( + '%s removed %s as the assignee of this task.', + $this->renderAuthor(), + $this->renderHandle($old)); + } else if (!$old) { + return pht( + '%s assigned this task to %s.', + $this->renderAuthor(), + $this->renderHandle($new)); + } else { + return pht( + '%s reassigned this task from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($old), + $this->renderHandle($new)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($this->getAuthorPHID() == $new) { + return pht( + '%s claimed %s.', + $this->renderAuthor(), + $this->renderObject()); + } else if (!$new) { + return pht( + '%s placed %s up for grabs.', + $this->renderAuthor(), + $this->renderObject()); + } else if (!$old) { + return pht( + '%s assigned %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($new)); + } else { + return pht( + '%s reassigned %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($old), + $this->renderHandle($new)); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + if (!strlen($new)) { + continue; + } + + if ($new === $old) { + continue; + } + + $assignee_list = id(new PhabricatorPeopleQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($new)) + ->execute(); + + if (!$assignee_list) { + $errors[] = $this->newInvalidError( + pht('User "%s" is not a valid user.', + $new)); + } + } + return $errors; + } + + public function getIcon() { + return 'fa-user'; + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($this->getAuthorPHID() == $new) { + return 'green'; + } else if (!$new) { + return 'black'; + } else if (!$old) { + return 'green'; + } else { + return 'green'; + } + + } + + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskParentTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskParentTransaction.php new file mode 100644 index 0000000000..d703adc8b5 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskParentTransaction.php @@ -0,0 +1,60 @@ +getPHID(); + + id(new PhabricatorEdgeEditor()) + ->addEdge($parent_phid, $parent_type, $task_phid) + ->save(); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $with_effect = array(); + foreach ($xactions as $xaction) { + $task_phid = $xaction->getNewValue(); + if (!$task_phid) { + continue; + } + + $with_effect[] = $xaction; + + $task = id(new ManiphestTaskQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($task_phid)) + ->executeOne(); + if (!$task) { + $errors[] = $this->newInvalidError( + pht( + 'Parent task identifier "%s" does not identify a visible '. + 'task.', + $task_phid)); + } + } + + if ($with_effect && !$this->isNewObject()) { + $errors[] = $this->newInvalidError( + pht( + 'You can only select a parent task when creating a '. + 'transaction for the first time.')); + } + + return $errors; + } +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php new file mode 100644 index 0000000000..8953664f27 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php @@ -0,0 +1,110 @@ +getValueForPoints($object->getPoints()); + } + + public function generateNewValue($object, $value) { + return $this->getValueForPoints($value); + } + + public function applyInternalEffects($object, $value) { + $object->setPoints($value); + } + + public function shouldHide() { + if (!ManiphestTaskPoints::getIsEnabled()) { + return true; + } + return false; + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s set the point value for this task to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else if ($new === null) { + return pht( + '%s removed the point value for this task.', + $this->renderAuthor()); + } else { + return pht( + '%s changed the point value for this task from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s set the point value for %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewValue()); + } else if ($new === null) { + return pht( + '%s removed the point value for %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s changed the point value for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + if (strlen($new) && !is_numeric($new)) { + $errors[] = $this->newInvalidError( + pht('Points value must be numeric or empty.')); + continue; + } + + if ((double)$new < 0) { + $errors[] = $this->newInvalidError( + pht('Points value must be nonnegative.')); + continue; + } + } + + return $errors; + } + + public function getIcon() { + return 'fa-calculator'; + } + + private function getValueForPoints($value) { + if (!strlen($value)) { + $value = null; + } + if ($value !== null) { + $value = (double)$value; + } + return $value; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php new file mode 100644 index 0000000000..2ed7c4e15b --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php @@ -0,0 +1,178 @@ +isNewObject()) { + return null; + } + return $object->getPriority(); + } + + public function generateNewValue($object, $value) { + // `$value` is supposed to be a keyword, but if the priority + // assigned to a task has been removed from the config, + // no such keyword will be available. Other edits to the task + // should still be allowed, even if the priority is no longer + // valid, so treat this as a no-op. + if ($value === ManiphestTaskPriority::UNKNOWN_PRIORITY_KEYWORD) { + return $object->getPriority(); + } + + return (string)ManiphestTaskPriority::getTaskPriorityFromKeyword($value); + } + + public function applyInternalEffects($object, $value) { + $object->setPriority($value); + } + + public function getActionStrength() { + return 1.1; + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return pht('Triaged'); + } else if ($old > $new) { + return pht('Lowered Priority'); + } else { + return pht('Raised Priority'); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old_name = ManiphestTaskPriority::getTaskPriorityName($old); + $new_name = ManiphestTaskPriority::getTaskPriorityName($new); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return pht( + '%s triaged this task as %s priority.', + $this->renderAuthor(), + $this->renderValue($new_name)); + } else if ($old > $new) { + return pht( + '%s lowered the priority of this task from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } else { + return pht( + '%s raised the priority of this task from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old_name = ManiphestTaskPriority::getTaskPriorityName($old); + $new_name = ManiphestTaskPriority::getTaskPriorityName($new); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return pht( + '%s triaged %s as %s priority.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($new_name)); + } else if ($old > $new) { + return pht( + '%s lowered the priority of %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } else { + return pht( + '%s raised the priority of %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + public function getIcon() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return 'fa-arrow-right'; + } else if ($old > $new) { + return 'fa-arrow-down'; + } else { + return 'fa-arrow-up'; + } + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old == ManiphestTaskPriority::getDefaultPriority()) { + return 'green'; + } else if ($old > $new) { + return 'grey'; + } else { + return 'yellow'; + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $content_source = $this->getEditor()->getContentSource(); + $is_web = ($content_source instanceof PhabricatorWebContentSource); + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + + // If this is a legitimate keyword like "low" or "high", this transaction + // is fine and apply normally. + $keyword = ManiphestTaskPriority::getTaskPriorityFromKeyword($value); + if ($keyword !== null) { + continue; + } + + // If this is the magic "don't change things" value for editing tasks + // with an obsolete priority constant in the database, let it through if + // this is a web edit. + if ($value === ManiphestTaskPriority::UNKNOWN_PRIORITY_KEYWORD) { + if ($is_web) { + continue; + } + } + + $keyword_list = array(); + foreach (ManiphestTaskPriority::getTaskPriorityMap() as $pri => $name) { + $keyword = ManiphestTaskPriority::getKeywordForTaskPriority($pri); + if ($keyword === null) { + continue; + } + $keyword_list[] = $keyword; + } + + $errors[] = $this->newInvalidError( + pht( + 'Task priority "%s" is not a valid task priority. Use a priority '. + 'keyword to choose a task priority: %s.', + $value, + implode(', ', $keyword_list)), + $xaction); + } + + return $errors; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php new file mode 100644 index 0000000000..5e1cd44611 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php @@ -0,0 +1,232 @@ +isNewObject()) { + return null; + } + return $object->getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function shouldHide() { + if ($this->getOldValue() === null) { + return true; + } else { + return false; + } + } + + public function getActionStrength() { + return 1.3; + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $action = ManiphestTaskStatus::getStatusActionName($new); + if ($action) { + return $action; + } + + $old_closed = ManiphestTaskStatus::isClosedStatus($old); + $new_closed = ManiphestTaskStatus::isClosedStatus($new); + + if ($new_closed && !$old_closed) { + return pht('Closed'); + } else if (!$new_closed && $old_closed) { + return pht('Reopened'); + } else { + return pht('Changed Status'); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old); + $new_closed = ManiphestTaskStatus::isClosedStatus($new); + + $old_name = ManiphestTaskStatus::getTaskStatusName($old); + $new_name = ManiphestTaskStatus::getTaskStatusName($new); + + $commit_phid = $this->getMetadataValue('commitPHID'); + + if ($new_closed && !$old_closed) { + if ($new == ManiphestTaskStatus::getDuplicateStatus()) { + if ($commit_phid) { + return pht( + '%s closed this task as a duplicate by committing %s.', + $this->renderAuthor(), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s closed this task as a duplicate.', + $this->renderAuthor()); + } + } else { + if ($commit_phid) { + return pht( + '%s closed this task as %s by committing %s.', + $this->renderAuthor(), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s closed this task as %s.', + $this->renderAuthor(), + $this->renderValue($new_name)); + } + } + } else if (!$new_closed && $old_closed) { + if ($commit_phid) { + return pht( + '%s reopened this task as %s by committing %s.', + $this->renderAuthor(), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s reopened this task as %s.', + $this->renderAuthor(), + $this->renderValue($new_name)); + } + } else { + if ($commit_phid) { + return pht( + '%s changed the task status from %s to %s by committing %s.', + $this->renderAuthor(), + $this->renderValue($old_name), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s changed the task status from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old); + $new_closed = ManiphestTaskStatus::isClosedStatus($new); + + $old_name = ManiphestTaskStatus::getTaskStatusName($old); + $new_name = ManiphestTaskStatus::getTaskStatusName($new); + + $commit_phid = $this->getMetadataValue('commitPHID'); + + if ($new_closed && !$old_closed) { + if ($new == ManiphestTaskStatus::getDuplicateStatus()) { + if ($commit_phid) { + return pht( + '%s closed %s as a duplicate by committing %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s closed %s as a duplicate.', + $this->renderAuthor(), + $this->renderObject()); + } + } else { + if ($commit_phid) { + return pht( + '%s closed %s as %s by committing %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s closed %s as %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($new_name)); + } + } + } else if (!$new_closed && $old_closed) { + if ($commit_phid) { + return pht( + '%s reopened %s as %s by committing %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s reopened %s as "%s".', + $this->renderAuthor(), + $this->renderObject(), + $new_name); + } + } else { + if ($commit_phid) { + return pht( + '%s changed the status of %s from %s to %s by committing %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name), + $this->renderHandle($commit_phid)); + } else { + return pht( + '%s changed the status of %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + } + + public function getIcon() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $action = ManiphestTaskStatus::getStatusIcon($new); + if ($action !== null) { + return $action; + } + + if (ManiphestTaskStatus::isClosedStatus($new)) { + return 'fa-check'; + } else { + return 'fa-pencil'; + } + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $color = ManiphestTaskStatus::getStatusColor($new); + if ($color !== null) { + return $color; + } + + if (ManiphestTaskStatus::isOpenStatus($new)) { + return 'green'; + } else { + return 'indigo'; + } + + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php new file mode 100644 index 0000000000..49d227b7f1 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php @@ -0,0 +1,21 @@ +getSubpriority(); + } + + public function applyInternalEffects($object, $value) { + $object->setSubpriority($value); + } + + public function shouldHide() { + return true; + } + + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php new file mode 100644 index 0000000000..506817b0fc --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php @@ -0,0 +1,75 @@ +getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setTitle($value); + } + + public function getActionStrength() { + return 1.4; + } + + public function getActionName() { + $old = $this->getOldValue(); + + if (!strlen($old)) { + return pht('Created'); + } + + return pht('Retitled'); + } + + public function getTitle() { + $old = $this->getOldValue(); + + if (!strlen($old)) { + return pht( + '%s created this task.', + $this->renderAuthor()); + } + + return pht( + '%s renamed this task from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getTitle(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Tasks must have a title.')); + } + + return $errors; + } + +} diff --git a/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php b/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php new file mode 100644 index 0000000000..c59de163c6 --- /dev/null +++ b/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php @@ -0,0 +1,6 @@ +getOldValue(); + $new = $this->getNewValue(); + + $old_status = head($old); + $new_status = head($new); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); + $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); + + if ($old_closed && !$new_closed) { + return pht('Block'); + } else if (!$old_closed && $new_closed) { + return pht('Unblock'); + } else { + return pht('Blocker'); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $blocker_phid = key($new); + $old_status = head($old); + $new_status = head($new); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); + $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); + + $old_name = ManiphestTaskStatus::getTaskStatusName($old_status); + $new_name = ManiphestTaskStatus::getTaskStatusName($new_status); + + if ($this->getMetadataValue('blocker.new')) { + return pht( + '%s created subtask %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid)); + } else if ($old_closed && !$new_closed) { + return pht( + '%s reopened subtask %s as %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderValue($new_name)); + } else if (!$old_closed && $new_closed) { + return pht( + '%s closed subtask %s as %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderValue($new_name)); + } else { + return pht( + '%s changed the status of subtask %s from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + $blocker_phid = key($new); + $old_status = head($old); + $new_status = head($new); + + $old_closed = ManiphestTaskStatus::isClosedStatus($old_status); + $new_closed = ManiphestTaskStatus::isClosedStatus($new_status); + + $old_name = ManiphestTaskStatus::getTaskStatusName($old_status); + $new_name = ManiphestTaskStatus::getTaskStatusName($new_status); + + if ($old_closed && !$new_closed) { + return pht( + '%s reopened %s, a subtask of %s, as %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderObject(), + $this->renderValue($new_name)); + } else if (!$old_closed && $new_closed) { + return pht( + '%s closed %s, a subtask of %s, as %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderObject(), + $this->renderValue($new_name)); + } else { + return pht( + '%s changed the status of %s, a subtask of %s, '. + 'from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($blocker_phid), + $this->renderObject(), + $this->renderValue($old_name), + $this->renderValue($new_name)); + } + } + + public function getIcon() { + return 'fa-shield'; + } + + +} diff --git a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php index 709558eaa2..8be6d73a1d 100644 --- a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php +++ b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php @@ -38,6 +38,11 @@ final class PhabricatorApplicationDetailViewController $header->setStatus('fa-ban', 'dark', pht('Uninstalled')); } + $timeline = $this->buildTransactionTimeline( + $selected, + new PhabricatorApplicationApplicationTransactionQuery()); + $timeline->setShouldTerminate(true); + $curtain = $this->buildCurtain($selected); $details = $this->buildPropertySectionView($selected); $policies = $this->buildPolicyView($selected); @@ -61,6 +66,7 @@ final class PhabricatorApplicationDetailViewController ->setMainColumn(array( $policies, $panels, + $timeline, )) ->addPropertySection(pht('Details'), $details); diff --git a/src/applications/meta/controller/PhabricatorApplicationEditController.php b/src/applications/meta/controller/PhabricatorApplicationEditController.php index ed51405db9..f7c2eac81e 100644 --- a/src/applications/meta/controller/PhabricatorApplicationEditController.php +++ b/src/applications/meta/controller/PhabricatorApplicationEditController.php @@ -30,8 +30,14 @@ final class PhabricatorApplicationEditController ->execute(); if ($request->isFormPost()) { - $result = array(); + $xactions = array(); + + $template = $application->getApplicationTransactionTemplate(); foreach ($application->getCapabilities() as $capability) { + if (!$application->isCapabilityEditable($capability)) { + continue; + } + $old = $application->getPolicy($capability); $new = $request->getStr('policy:'.$capability); @@ -40,67 +46,32 @@ final class PhabricatorApplicationEditController continue; } - if (empty($policies[$new])) { - // Not a standard policy, check for a custom policy. - $policy = id(new PhabricatorPolicyQuery()) - ->setViewer($user) - ->withPHIDs(array($new)) - ->executeOne(); - if (!$policy) { - // Not a custom policy either. Can't set the policy to something - // invalid, so skip this. - continue; - } - } - - if ($new == PhabricatorPolicies::POLICY_PUBLIC) { - $capobj = PhabricatorPolicyCapability::getCapabilityByKey( - $capability); - if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) { - // Can't set non-public policies to public. - continue; - } - } - - $result[$capability] = $new; + $xactions[] = id(clone $template) + ->setTransactionType( + PhabricatorApplicationPolicyChangeTransaction::TRANSACTIONTYPE) + ->setMetadataValue( + PhabricatorApplicationPolicyChangeTransaction::METADATA_ATTRIBUTE, + $capability) + ->setNewValue($new); } - if ($result) { - $key = 'phabricator.application-settings'; - $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); - $value = $config_entry->getValue(); + $editor = id(new PhabricatorApplicationEditor()) + ->setActor($user) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); - $phid = $application->getPHID(); - if (empty($value[$phid])) { - $value[$application->getPHID()] = array(); - } - if (empty($value[$phid]['policy'])) { - $value[$phid]['policy'] = array(); - } - - $value[$phid]['policy'] = $result + $value[$phid]['policy']; - - // Don't allow users to make policy edits which would lock them out of - // applications, since they would be unable to undo those actions. - PhabricatorEnv::overrideConfig($key, $value); - PhabricatorPolicyFilter::mustRetainCapability( - $user, - $application, - PhabricatorPolicyCapability::CAN_VIEW); - - PhabricatorPolicyFilter::mustRetainCapability( - $user, - $application, - PhabricatorPolicyCapability::CAN_EDIT); - - PhabricatorConfigEditor::storeNewValue( - $user, - $config_entry, - $value, - PhabricatorContentSource::newFromRequest($request)); + try { + $editor->applyTransactions($application, $xactions); + return id(new AphrontRedirectResponse())->setURI($view_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; } - return id(new AphrontRedirectResponse())->setURI($view_uri); + return $this->newDialog() + ->setTitle(pht('Validation Failed')) + ->setValidationException($validation_exception) + ->addCancelButton($view_uri); } $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( diff --git a/src/applications/meta/editor/PhabricatorApplicationEditEngine.php b/src/applications/meta/editor/PhabricatorApplicationEditEngine.php new file mode 100644 index 0000000000..7cad5d9242 --- /dev/null +++ b/src/applications/meta/editor/PhabricatorApplicationEditEngine.php @@ -0,0 +1,64 @@ +getName()); + } + + protected function getObjectEditShortText($object) { + return $object->getName(); + } + + protected function getObjectCreateShortText() { + return pht('Create Application'); + } + + protected function getObjectName() { + return pht('Application'); + } + + protected function getObjectViewURI($object) { + return $object->getViewURI(); + } + + protected function buildCustomEditFields($object) { + return array(); + } + +} diff --git a/src/applications/meta/editor/PhabricatorApplicationEditor.php b/src/applications/meta/editor/PhabricatorApplicationEditor.php new file mode 100644 index 0000000000..83003b4c27 --- /dev/null +++ b/src/applications/meta/editor/PhabricatorApplicationEditor.php @@ -0,0 +1,46 @@ +setTag('a') + ->setIcon('fa-gears') ->setHref('/applications/view/'.get_class($application).'/') ->setText(pht('Configure')) ->setColor(PHUIButtonView::GREY); $name = $application->getName(); - if ($application->isPrototype()) { - $name = $name.' '.pht('(Prototype)'); - } $item = id(new PHUIObjectItemView()) ->setHeader($name) ->setImageIcon($icon) - ->setSubhead($description) - ->setLaunchButton($configure); + ->setSideColumn($configure); + + if (!$application->isFirstParty()) { + $tag = id(new PHUITagView()) + ->setName(pht('Extension')) + ->setIcon('fa-puzzle-piece') + ->setColor(PHUITagView::COLOR_BLUE) + ->setType(PHUITagView::TYPE_SHADE) + ->setSlimShady(true); + $item->addAttribute($tag); + } + + if ($application->isPrototype()) { + $prototype_tag = id(new PHUITagView()) + ->setName(pht('Prototype')) + ->setIcon('fa-exclamation-circle') + ->setColor(PHUITagView::COLOR_ORANGE) + ->setType(PHUITagView::TYPE_SHADE) + ->setSlimShady(true); + $item->addAttribute($prototype_tag); + } + + $item->addAttribute($description); if ($application->getBaseURI() && $application->isInstalled()) { $item->setHref($application->getBaseURI()); @@ -242,10 +261,6 @@ final class PhabricatorAppSearchEngine $item->setDisabled(true); } - if (!$application->isFirstParty()) { - $item->addAttribute(pht('Extension')); - } - $list->addItem($item); } diff --git a/src/applications/meta/storage/PhabricatorApplicationApplicationTransaction.php b/src/applications/meta/storage/PhabricatorApplicationApplicationTransaction.php index fc7136345e..8fbe20e0fa 100644 --- a/src/applications/meta/storage/PhabricatorApplicationApplicationTransaction.php +++ b/src/applications/meta/storage/PhabricatorApplicationApplicationTransaction.php @@ -11,10 +11,6 @@ final class PhabricatorApplicationApplicationTransaction return PhabricatorApplicationApplicationPHIDType::TYPECONST; } - public function getApplicationTransactionCommentObject() { - return new PhabricatorApplicationTransactionComment(); - } - public function getBaseTransactionClass() { return 'PhabricatorApplicationTransactionType'; } diff --git a/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php b/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php new file mode 100644 index 0000000000..dca2f5a795 --- /dev/null +++ b/src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php @@ -0,0 +1,197 @@ +getCapabilityName(); + return $application->getPolicy($capability); + } + + public function applyInternalEffects($object, $value) { + $application = $object; + $user = $this->getActor(); + + $key = 'phabricator.application-settings'; + $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); + $current_value = $config_entry->getValue(); + + $phid = $application->getPHID(); + if (empty($current_value[$phid])) { + $current_value[$application->getPHID()] = array(); + } + if (empty($current_value[$phid]['policy'])) { + $current_value[$phid]['policy'] = array(); + } + + $new = array($this->getCapabilityName() => $value); + $current_value[$phid]['policy'] = $new + $current_value[$phid]['policy']; + + $editor = $this->getEditor(); + $content_source = $editor->getContentSource(); + PhabricatorConfigEditor::storeNewValue( + $user, + $config_entry, + $current_value, + $content_source); + } + + public function getTitle() { + $old = $this->renderPolicy($this->getOldValue()); + $new = $this->renderPolicy($this->getNewValue()); + + return pht( + '%s changed the "%s" policy from "%s" to "%s".', + $this->renderAuthor(), + $this->renderCapability(), + $old, + $new); + } + + public function getTitleForFeed() { + $old = $this->renderPolicy($this->getOldValue()); + $new = $this->renderPolicy($this->getNewValue()); + + return pht( + '%s changed the "%s" policy for application %s from "%s" to "%s".', + $this->renderAuthor(), + $this->renderCapability(), + $this->renderObject(), + $old, + $new); + } + + public function validateTransactions($object, array $xactions) { + $user = $this->getActor(); + $application = $object; + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($user) + ->setObject($application) + ->execute(); + + $errors = array(); + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + $capability = $xaction->getMetadataValue(self::METADATA_ATTRIBUTE); + + if (empty($policies[$new])) { + // Not a standard policy, check for a custom policy. + $policy = id(new PhabricatorPolicyQuery()) + ->setViewer($user) + ->withPHIDs(array($new)) + ->executeOne(); + if (!$policy) { + $errors[] = $this->newInvalidError( + pht('Policy does not exist.')); + continue; + } + } else { + $policy = idx($policies, $new); + } + + if (!$policy->isValidPolicyForEdit()) { + $errors[] = $this->newInvalidError( + pht('Can\'t set the policy to a policy you can\'t view!')); + continue; + } + + if ($new == PhabricatorPolicies::POLICY_PUBLIC) { + $capobj = PhabricatorPolicyCapability::getCapabilityByKey( + $capability); + if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) { + $errors[] = $this->newInvalidError( + pht('Can\'t set non-public policies to public.')); + continue; + } + } + + if (!$application->isCapabilityEditable($capability)) { + $errors[] = $this->newInvalidError( + pht('Capability "%s" is not editable for this application.', + $capability)); + continue; + } + } + + // If we're changing these policies, the viewer needs to still be able to + // view or edit the application under the new policy. + $validate_map = array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + $validate_map = array_fill_keys($validate_map, array()); + + foreach ($xactions as $xaction) { + $capability = $xaction->getMetadataValue(self::METADATA_ATTRIBUTE); + if (!isset($validate_map[$capability])) { + continue; + } + + $validate_map[$capability][] = $xaction; + } + + foreach ($validate_map as $capability => $cap_xactions) { + if (!$cap_xactions) { + continue; + } + + $editor = $this->getEditor(); + $policy_errors = $editor->validatePolicyTransaction( + $object, + $cap_xactions, + self::TRANSACTIONTYPE, + $capability); + + foreach ($policy_errors as $error) { + $errors[] = $error; + } + } + + return $errors; + } + + private function renderPolicy($name) { + $policies = $this->getAllPolicies(); + if (empty($policies[$name])) { + // Not a standard policy, check for a custom policy. + $policy = id(new PhabricatorPolicyQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(array($name)) + ->executeOne(); + $policies[$name] = $policy; + } + + $policy = idx($policies, $name); + return $this->renderValue($policy->getFullName()); + } + + private function getAllPolicies() { + if (!$this->policies) { + $viewer = $this->getViewer(); + $application = $this->getObject(); + $this->policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->setObject($application) + ->execute(); + } + + return $this->policies; + } + + private function renderCapability() { + $application = $this->getObject(); + $capability = $this->getCapabilityName(); + return $application->getCapabilityLabel($capability); + } + + private function getCapabilityName() { + return $this->getMetadataValue(self::METADATA_ATTRIBUTE); + } + +} diff --git a/src/applications/metamta/PhabricatorMetaMTAWorker.php b/src/applications/metamta/PhabricatorMetaMTAWorker.php index af655f4e0f..dcc6a8dc5e 100644 --- a/src/applications/metamta/PhabricatorMetaMTAWorker.php +++ b/src/applications/metamta/PhabricatorMetaMTAWorker.php @@ -13,10 +13,6 @@ final class PhabricatorMetaMTAWorker protected function doWork() { $message = $this->loadMessage(); - if (!$message) { - throw new PhabricatorWorkerPermanentFailureException( - pht('Unable to load message!')); - } if ($message->getStatus() != PhabricatorMailOutboundStatus::STATUS_QUEUE) { return; @@ -32,7 +28,18 @@ final class PhabricatorMetaMTAWorker private function loadMessage() { $message_id = $this->getTaskData(); - return id(new PhabricatorMetaMTAMail())->load($message_id); + $message = id(new PhabricatorMetaMTAMail()) + ->load($message_id); + + if (!$message) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to load mail message (with ID "%s") while preparing to '. + 'deliver it.', + $message_id)); + } + + return $message; } public function renderForDisplay(PhabricatorUser $viewer) { diff --git a/src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php b/src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php index 0890ac3004..cfe6491fe0 100644 --- a/src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php +++ b/src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php @@ -86,7 +86,7 @@ final class PhabricatorMailImplementationMailgunAdapter $from = idx($this->params, 'from'); if (idx($this->params, 'from-name')) { - $params['from'] = "{$this->params['from-name']} <{$from}>"; + $params['from'] = "\"{$this->params['from-name']}\" <{$from}>"; } else { $params['from'] = $from; } diff --git a/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php b/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php index a2d32e3360..afde9fee23 100644 --- a/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php +++ b/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php @@ -341,7 +341,7 @@ final class PhabricatorMetaMTAApplicationEmailPanel $button_edit = javelin_tag( 'a', array( - 'class' => 'button small grey', + 'class' => 'button small button-grey', 'href' => $uri->alter('edit', $email->getID()), 'sigil' => 'workflow', ), @@ -350,7 +350,7 @@ final class PhabricatorMetaMTAApplicationEmailPanel $button_remove = javelin_tag( 'a', array( - 'class' => 'button small grey', + 'class' => 'button small button-grey', 'href' => $uri->alter('delete', $email->getID()), 'sigil' => 'workflow', ), diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php index d6ae9b51e0..65ffe888d9 100644 --- a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php +++ b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php @@ -372,16 +372,16 @@ final class PhabricatorMetaMTAMail } $editor->save(); - // Queue a task to send this mail. - $mailer_task = PhabricatorWorker::scheduleTask( - 'PhabricatorMetaMTAWorker', - $this->getID(), - array( - 'priority' => PhabricatorWorker::PRIORITY_ALERTS, - )); - $this->saveTransaction(); + // Queue a task to send this mail. + $mailer_task = PhabricatorWorker::scheduleTask( + 'PhabricatorMetaMTAWorker', + $this->getID(), + array( + 'priority' => PhabricatorWorker::PRIORITY_ALERTS, + )); + return $result; } diff --git a/src/applications/notification/builder/PhabricatorNotificationBuilder.php b/src/applications/notification/builder/PhabricatorNotificationBuilder.php index 12e4b57bfb..5691755cc7 100644 --- a/src/applications/notification/builder/PhabricatorNotificationBuilder.php +++ b/src/applications/notification/builder/PhabricatorNotificationBuilder.php @@ -145,13 +145,16 @@ final class PhabricatorNotificationBuilder extends Phobject { $dict = array(); $viewer = $this->user; - $desktop_key = PhabricatorDesktopNotificationsSetting::SETTINGKEY; - $desktop_enabled = $viewer->getUserSetting($desktop_key); + $key = PhabricatorNotificationsSetting::SETTINGKEY; + $setting = $viewer->getUserSetting($key); + $desktop_ready = PhabricatorNotificationsSetting::desktopReady($setting); + $web_ready = PhabricatorNotificationsSetting::webReady($setting); foreach ($stories as $story) { if ($story instanceof PhabricatorApplicationTransactionFeedStory) { $dict[] = array( - 'desktopReady' => $desktop_enabled, + 'desktopReady' => $desktop_ready, + 'webReady' => $web_ready, 'title' => $story->renderText(), 'body' => $story->renderTextBody(), 'href' => $story->getURI(), @@ -159,7 +162,8 @@ final class PhabricatorNotificationBuilder extends Phobject { ); } else if ($story instanceof PhabricatorNotificationTestFeedStory) { $dict[] = array( - 'desktopReady' => $desktop_enabled, + 'desktopReady' => $desktop_ready, + 'webReady' => $web_ready, 'title' => pht('Test Notification'), 'body' => $story->renderText(), 'href' => null, @@ -168,6 +172,7 @@ final class PhabricatorNotificationBuilder extends Phobject { } else { $dict[] = array( 'desktopReady' => false, + 'webReady' => false, 'title' => null, 'body' => null, 'href' => null, diff --git a/src/applications/notification/config/PhabricatorNotificationServersConfigOptionType.php b/src/applications/notification/config/PhabricatorNotificationServersConfigType.php similarity index 85% rename from src/applications/notification/config/PhabricatorNotificationServersConfigOptionType.php rename to src/applications/notification/config/PhabricatorNotificationServersConfigType.php index 07cdf7eece..5f0c1f7e2f 100644 --- a/src/applications/notification/config/PhabricatorNotificationServersConfigOptionType.php +++ b/src/applications/notification/config/PhabricatorNotificationServersConfigType.php @@ -1,19 +1,17 @@ $spec) { if (!is_array($spec)) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration is not valid: each entry in '. 'the list must be a dictionary describing a service, but '. @@ -38,7 +36,7 @@ final class PhabricatorNotificationServersConfigOptionType 'disabled' => 'optional bool', )); } catch (Exception $ex) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration has an invalid service '. 'specification (at index "%s"): %s.', @@ -64,7 +62,7 @@ final class PhabricatorNotificationServersConfigOptionType } break; default: - throw new Exception( + throw $this->newException( pht( 'Notification server configuration describes an invalid '. 'host ("%s", at index "%s") with an unrecognized type ("%s"). '. @@ -81,7 +79,7 @@ final class PhabricatorNotificationServersConfigOptionType case 'https': break; default: - throw new Exception( + throw $this->newException( pht( 'Notification server configuration describes an invalid '. 'host ("%s", at index "%s") with an invalid protocol ("%s"). '. @@ -95,7 +93,7 @@ final class PhabricatorNotificationServersConfigOptionType $path = idx($spec, 'path'); if ($type == 'admin' && strlen($path)) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration describes an invalid host '. '("%s", at index "%s"). This is an "admin" service but it has a '. @@ -108,7 +106,7 @@ final class PhabricatorNotificationServersConfigOptionType // mistakes. $key = "{$host}:{$port}"; if (isset($map[$key])) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration is invalid: it describes the '. 'same host and port ("%s") multiple times. Each host and port '. @@ -120,7 +118,7 @@ final class PhabricatorNotificationServersConfigOptionType if ($value) { if (!$has_admin) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration is invalid: it does not '. 'specify any enabled servers with type "admin". Notifications '. @@ -128,7 +126,7 @@ final class PhabricatorNotificationServersConfigOptionType } if (!$has_client) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration is invalid: it does not '. 'specify any enabled servers with type "client". Notifications '. diff --git a/src/applications/notification/controller/PhabricatorNotificationClearController.php b/src/applications/notification/controller/PhabricatorNotificationClearController.php index a826ae0a6b..1d6c7f7b97 100644 --- a/src/applications/notification/controller/PhabricatorNotificationClearController.php +++ b/src/applications/notification/controller/PhabricatorNotificationClearController.php @@ -8,6 +8,17 @@ final class PhabricatorNotificationClearController $chrono_key = $request->getStr('chronoKey'); if ($request->isDialogFormPost()) { + $should_clear = true; + } else { + try { + $request->validateCSRF(); + $should_clear = true; + } catch (AphrontMalformedRequestException $ex) { + $should_clear = false; + } + } + + if ($should_clear) { $table = new PhabricatorFeedStoryNotification(); queryfx( diff --git a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php index af3e8bcff0..8ef6286225 100644 --- a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php +++ b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php @@ -42,6 +42,7 @@ final class PhabricatorNotificationIndividualController 'pertinent' => true, 'primaryObjectPHID' => $story->getPrimaryObjectPHID(), 'desktopReady' => $data['desktopReady'], + 'webReady' => $data['webReady'], 'href' => $data['href'], 'icon' => $data['icon'], 'title' => $data['title'], diff --git a/src/applications/notification/controller/PhabricatorNotificationPanelController.php b/src/applications/notification/controller/PhabricatorNotificationPanelController.php index be7eeb914e..3e30ead9e7 100644 --- a/src/applications/notification/controller/PhabricatorNotificationPanelController.php +++ b/src/applications/notification/controller/PhabricatorNotificationPanelController.php @@ -9,7 +9,7 @@ final class PhabricatorNotificationPanelController $query = id(new PhabricatorNotificationQuery()) ->setViewer($viewer) ->withUserPHIDs(array($viewer->getPHID())) - ->setLimit(15); + ->setLimit(10); $stories = $query->execute(); diff --git a/src/applications/notification/query/PhabricatorNotificationSearchEngine.php b/src/applications/notification/query/PhabricatorNotificationSearchEngine.php index 11b935eb50..0ee7327bfc 100644 --- a/src/applications/notification/query/PhabricatorNotificationSearchEngine.php +++ b/src/applications/notification/query/PhabricatorNotificationSearchEngine.php @@ -85,12 +85,12 @@ final class PhabricatorNotificationSearchEngine $viewer = $this->requireViewer(); $image = id(new PHUIIconView()) - ->setIcon('fa-eye-slash'); + ->setIcon('fa-bell-o'); $button = id(new PHUIButtonView()) ->setTag('a') ->addSigil('workflow') - ->setColor(PHUIButtonView::SIMPLE) + ->setColor(PHUIButtonView::GREY) ->setIcon($image) ->setText(pht('Mark All Read')); diff --git a/src/applications/nuance/application/PhabricatorNuanceApplication.php b/src/applications/nuance/application/PhabricatorNuanceApplication.php index cf18bc389d..dc18a6585f 100644 --- a/src/applications/nuance/application/PhabricatorNuanceApplication.php +++ b/src/applications/nuance/application/PhabricatorNuanceApplication.php @@ -18,11 +18,6 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication { return true; } - public function isLaunchable() { - // Try to hide this even more for now. - return false; - } - public function getBaseURI() { return '/nuance/'; } @@ -51,6 +46,9 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication { $this->getQueryRoutePattern() => 'NuanceQueueListController', $this->getEditRoutePattern('edit/') => 'NuanceQueueEditController', 'view/(?P[1-9]\d*)/' => 'NuanceQueueViewController', + 'work/(?P[1-9]\d*)/' => 'NuanceQueueWorkController', + 'action/(?P[1-9]\d*)/(?P[^/]+)/(?P[1-9]\d*)/' + => 'NuanceItemActionController', ), ), '/action/' => array( diff --git a/src/applications/nuance/command/NuanceCommandImplementation.php b/src/applications/nuance/command/NuanceCommandImplementation.php new file mode 100644 index 0000000000..2b47e40654 --- /dev/null +++ b/src/applications/nuance/command/NuanceCommandImplementation.php @@ -0,0 +1,113 @@ +actor = $actor; + return $this; + } + + final public function getActor() { + return $this->actor; + } + + abstract public function getCommandName(); + abstract public function canApplyToItem(NuanceItem $item); + + public function canApplyImmediately( + NuanceItem $item, + NuanceItemCommand $command) { + return false; + } + + abstract protected function executeCommand( + NuanceItem $item, + NuanceItemCommand $command); + + final public function applyCommand( + NuanceItem $item, + NuanceItemCommand $command) { + + $command_key = $command->getCommand(); + $implementation_key = $this->getCommandKey(); + if ($command_key !== $implementation_key) { + throw new Exception( + pht( + 'This command implementation("%s") can not apply a command of a '. + 'different type ("%s").', + $implementation_key, + $command_key)); + } + + if (!$this->canApplyToItem($item)) { + throw new Exception( + pht( + 'This command implementation ("%s") can not be applied to an '. + 'item of type "%s".', + $implementation_key, + $item->getItemType())); + } + + $this->transactionQueue = array(); + + $command_type = NuanceItemCommandTransaction::TRANSACTIONTYPE; + $command_xaction = $this->newTransaction($command_type); + + $result = $this->executeCommand($item, $command); + + $xactions = $this->transactionQueue; + $this->transactionQueue = array(); + + $command_xaction->setNewValue( + array( + 'command' => $command->getCommand(), + 'parameters' => $command->getParameters(), + 'result' => $result, + )); + + // TODO: Maybe preserve the actor's original content source? + $source = PhabricatorContentSource::newForSource( + PhabricatorDaemonContentSource::SOURCECONST); + + $actor = $this->getActor(); + + id(new NuanceItemEditor()) + ->setActor($actor) + ->setActingAsPHID($command->getAuthorPHID()) + ->setContentSource($source) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) + ->applyTransactions($item, $xactions); + } + + final public function getCommandKey() { + return $this->getPhobjectClassConstant('COMMANDKEY'); + } + + final public static function getAllCommands() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getCommandKey') + ->execute(); + } + + protected function newTransaction($type) { + $xaction = id(new NuanceItemTransaction()) + ->setTransactionType($type); + + $this->transactionQueue[] = $xaction; + + return $xaction; + } + + protected function newStatusTransaction($status) { + return $this->newTransaction(NuanceItemStatusTransaction::TRANSACTIONTYPE) + ->setNewValue($status); + } + +} diff --git a/src/applications/nuance/command/NuanceItemCommandSpec.php b/src/applications/nuance/command/NuanceItemCommandSpec.php new file mode 100644 index 0000000000..d449e54833 --- /dev/null +++ b/src/applications/nuance/command/NuanceItemCommandSpec.php @@ -0,0 +1,37 @@ +commandKey = $command_key; + return $this; + } + + public function getCommandKey() { + return $this->commandKey; + } + + public function setName($name) { + $this->name = $name; + return $this; + } + + public function getName() { + return $this->name; + } + + public function setIcon($icon) { + $this->icon = $icon; + return $this; + } + + public function getIcon() { + return $this->icon; + } + +} diff --git a/src/applications/nuance/command/NuanceTrashCommand.php b/src/applications/nuance/command/NuanceTrashCommand.php new file mode 100644 index 0000000000..b5ac851539 --- /dev/null +++ b/src/applications/nuance/command/NuanceTrashCommand.php @@ -0,0 +1,29 @@ +getImplementation(); + return ($type instanceof NuanceFormItemType); + } + + public function canApplyImmediately( + NuanceItem $item, + NuanceItemCommand $command) { + return true; + } + + protected function executeCommand( + NuanceItem $item, + NuanceItemCommand $command) { + $this->newStatusTransaction(NuanceItem::STATUS_CLOSED); + } + +} diff --git a/src/applications/nuance/controller/NuanceItemActionController.php b/src/applications/nuance/controller/NuanceItemActionController.php index c64ac5f6ac..39ae8fd995 100644 --- a/src/applications/nuance/controller/NuanceItemActionController.php +++ b/src/applications/nuance/controller/NuanceItemActionController.php @@ -6,6 +6,14 @@ final class NuanceItemActionController extends NuanceController { $viewer = $this->getViewer(); $id = $request->getURIData('id'); + if (!$request->validateCSRF()) { + return new Aphront400Response(); + } + + // NOTE: This controller can be reached from an individual item (usually + // by a user) or while working through a queue (usually by staff). When + // a command originates from a queue, the URI will have a queue ID. + $item = id(new NuanceItemQuery()) ->setViewer($viewer) ->withIDs(array($id)) @@ -14,13 +22,100 @@ final class NuanceItemActionController extends NuanceController { return new Aphront404Response(); } + $cancel_uri = $item->getURI(); + + $queue_id = $request->getURIData('queueID'); + $queue = null; + if ($queue_id) { + $queue = id(new NuanceQueueQuery()) + ->setViewer($viewer) + ->withIDs(array($queue_id)) + ->executeOne(); + if (!$queue) { + return new Aphront404Response(); + } + + $item_queue = $item->getQueue(); + if (!$item_queue || ($item_queue->getPHID() != $queue->getPHID())) { + return $this->newDialog() + ->setTitle(pht('Wrong Queue')) + ->appendParagraph( + pht( + 'You are trying to act on this item from the wrong queue: it '. + 'is currently in a different queue.')) + ->addCancelButton($cancel_uri); + } + } + $action = $request->getURIData('action'); $impl = $item->getImplementation(); $impl->setViewer($viewer); $impl->setController($this); - return $impl->buildActionResponse($item, $action); + $executors = NuanceCommandImplementation::getAllCommands(); + $executor = idx($executors, $action); + if (!$executor) { + return new Aphront404Response(); + } + + $executor = id(clone $executor) + ->setActor($viewer); + + if (!$executor->canApplyToItem($item)) { + return $this->newDialog() + ->setTitle(pht('Command Not Supported')) + ->appendParagraph( + pht( + 'This item does not support the specified command ("%s").', + $action)) + ->addCancelButton($cancel_uri); + } + + $command = NuanceItemCommand::initializeNewCommand() + ->setItemPHID($item->getPHID()) + ->setAuthorPHID($viewer->getPHID()) + ->setCommand($action); + + if ($queue) { + $command->setQueuePHID($queue->getPHID()); + } + + $command->save(); + + // If this command can be applied immediately, try to apply it now. + + // In most cases, local commands (like closing an item) can be applied + // immediately. + + // Commands that require making a call to a remote system (for example, + // to reply to a tweet or close a remote object) are usually done in the + // background so the user doesn't have to wait for the operation to + // complete before they can continue work. + + $did_apply = false; + $immediate = $executor->canApplyImmediately($item, $command); + if ($immediate) { + // TODO: Move this stuff to a new Engine, and have the controller and + // worker both call into the Engine. + $worker = new NuanceItemUpdateWorker(array()); + $did_apply = $worker->executeCommands($item, array($command)); + } + + // If this can't be applied immediately or we were unable to get a lock + // fast enough, do the update in the background instead. + if (!$did_apply) { + $item->scheduleUpdate(); + } + + if ($queue) { + $done_uri = $queue->getWorkURI(); + } else { + $done_uri = $item->getURI(); + } + + return id(new AphrontRedirectResponse()) + ->setURI($done_uri); } } diff --git a/src/applications/nuance/controller/NuanceItemViewController.php b/src/applications/nuance/controller/NuanceItemViewController.php index 7ef5d06682..a902dc3b06 100644 --- a/src/applications/nuance/controller/NuanceItemViewController.php +++ b/src/applications/nuance/controller/NuanceItemViewController.php @@ -26,14 +26,12 @@ final class NuanceItemViewController extends NuanceController { $curtain = $this->buildCurtain($item); $content = $this->buildContent($item); - $commands = $this->buildCommands($item); $timeline = $this->buildTransactionTimeline( $item, new NuanceItemTransactionQuery()); $main = array( - $commands, $content, $timeline, ); @@ -91,36 +89,4 @@ final class NuanceItemViewController extends NuanceController { return $impl->buildItemView($item); } - private function buildCommands(NuanceItem $item) { - $viewer = $this->getViewer(); - - $commands = id(new NuanceItemCommandQuery()) - ->setViewer($viewer) - ->withItemPHIDs(array($item->getPHID())) - ->execute(); - $commands = msort($commands, 'getID'); - - if (!$commands) { - return null; - } - - $rows = array(); - foreach ($commands as $command) { - $rows[] = array( - $command->getCommand(), - ); - } - - $table = id(new AphrontTableView($rows)) - ->setHeaders( - array( - pht('Command'), - )); - - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Pending Commands')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); - } - } diff --git a/src/applications/nuance/controller/NuanceQueueViewController.php b/src/applications/nuance/controller/NuanceQueueViewController.php index 8f4e85565a..f5284f6bfa 100644 --- a/src/applications/nuance/controller/NuanceQueueViewController.php +++ b/src/applications/nuance/controller/NuanceQueueViewController.php @@ -70,6 +70,14 @@ final class NuanceQueueViewController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Begin Work')) + ->setIcon('fa-play-circle-o') + ->setHref($this->getApplicationURI("queue/work/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + return $curtain; } diff --git a/src/applications/nuance/controller/NuanceQueueWorkController.php b/src/applications/nuance/controller/NuanceQueueWorkController.php new file mode 100644 index 0000000000..8703a69334 --- /dev/null +++ b/src/applications/nuance/controller/NuanceQueueWorkController.php @@ -0,0 +1,186 @@ +getViewer(); + + $queue = id(new NuanceQueueQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->executeOne(); + if (!$queue) { + return new Aphront404Response(); + } + + $title = $queue->getName(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Queues'), $this->getApplicationURI('queue/')); + $crumbs->addTextCrumb($queue->getName(), $queue->getURI()); + $crumbs->addTextCrumb(pht('Work')); + $crumbs->setBorder(true); + + // For now, just pick the first open item. + + $items = id(new NuanceItemQuery()) + ->setViewer($viewer) + ->withQueuePHIDs( + array( + $queue->getPHID(), + )) + ->withStatuses( + array( + NuanceItem::STATUS_OPEN, + )) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->setLimit(5) + ->execute(); + + if (!$items) { + return $this->newDialog() + ->setTitle(pht('Queue Empty')) + ->appendParagraph( + pht( + 'This queue has no open items which you have permission to '. + 'work on.')) + ->addCancelButton($queue->getURI()); + } + + $item = head($items); + + $curtain = $this->buildCurtain($queue, $item); + + $timeline = $this->buildTransactionTimeline( + $item, + new NuanceItemTransactionQuery()); + $timeline->setShouldTerminate(true); + + $impl = $item->getImplementation() + ->setViewer($viewer); + + $commands = $this->buildCommands($item); + $work_content = $impl->buildItemWorkView($item); + + $view = id(new PHUITwoColumnView()) + ->setCurtain($curtain) + ->setMainColumn( + array( + $commands, + $work_content, + $timeline, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } + + private function buildCurtain(NuanceQueue $queue, NuanceItem $item) { + $viewer = $this->getViewer(); + $id = $queue->getID(); + + $curtain = $this->newCurtainView(); + + $impl = $item->getImplementation(); + $commands = $impl->buildWorkCommands($item); + + foreach ($commands as $command) { + $command_key = $command->getCommandKey(); + + $item_id = $item->getID(); + + $action_uri = "queue/action/{$id}/{$command_key}/{$item_id}/"; + $action_uri = $this->getApplicationURI($action_uri); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName($command->getName()) + ->setIcon($command->getIcon()) + ->setHref($action_uri) + ->setWorkflow(true)); + } + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_LABEL) + ->setName(pht('Queue Actions'))); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Manage Queue')) + ->setIcon('fa-cog') + ->setHref($this->getApplicationURI("queue/view/{$id}/"))); + + return $curtain; + } + + private function buildCommands(NuanceItem $item) { + $viewer = $this->getViewer(); + + $commands = id(new NuanceItemCommandQuery()) + ->setViewer($viewer) + ->withItemPHIDs(array($item->getPHID())) + ->withStatuses( + array( + NuanceItemCommand::STATUS_ISSUED, + NuanceItemCommand::STATUS_EXECUTING, + NuanceItemCommand::STATUS_FAILED, + )) + ->execute(); + $commands = msort($commands, 'getID'); + + if (!$commands) { + return null; + } + + $rows = array(); + foreach ($commands as $command) { + $icon = $command->getStatusIcon(); + $color = $command->getStatusColor(); + + $rows[] = array( + $command->getID(), + id(new PHUIIconView()) + ->setIcon($icon, $color), + $viewer->renderHandle($command->getAuthorPHID()), + $command->getCommand(), + phabricator_datetime($command->getDateCreated(), $viewer), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('ID'), + null, + pht('Actor'), + pht('Command'), + pht('Date'), + )) + ->setColumnClasses( + array( + null, + 'icon', + null, + 'pri', + 'wide right', + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Pending Commands')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } + +} diff --git a/src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php b/src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php index d250b1d02d..151a5876ba 100644 --- a/src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php +++ b/src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php @@ -17,10 +17,9 @@ final class NuanceGitHubIssuesImportCursor $container_key = null; - return NuanceItem::initializeNewItem() + return NuanceItem::initializeNewItem(NuanceGitHubEventItemType::ITEMTYPE) ->setStatus(NuanceItem::STATUS_IMPORTING) ->setSourcePHID($source->getPHID()) - ->setItemType(NuanceGitHubEventItemType::ITEMTYPE) ->setItemKey($item_key) ->setItemContainerKey($container_key) ->setItemProperty('api.type', 'issue') diff --git a/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php index 72aca276d2..018b3aa8c1 100644 --- a/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php +++ b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php @@ -36,10 +36,9 @@ final class NuanceGitHubRepositoryImportCursor $container_key = "github.issue.{$issue_id}"; } - return NuanceItem::initializeNewItem() + return NuanceItem::initializeNewItem(NuanceGitHubEventItemType::ITEMTYPE) ->setStatus(NuanceItem::STATUS_IMPORTING) ->setSourcePHID($source->getPHID()) - ->setItemType(NuanceGitHubEventItemType::ITEMTYPE) ->setItemKey($item_key) ->setItemContainerKey($container_key) ->setItemProperty('api.type', 'repository') diff --git a/src/applications/nuance/editor/NuanceItemEditor.php b/src/applications/nuance/editor/NuanceItemEditor.php index 45288052c2..b41ca77563 100644 --- a/src/applications/nuance/editor/NuanceItemEditor.php +++ b/src/applications/nuance/editor/NuanceItemEditor.php @@ -14,104 +14,10 @@ final class NuanceItemEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = NuanceItemTransaction::TYPE_OWNER; - $types[] = NuanceItemTransaction::TYPE_SOURCE; - $types[] = NuanceItemTransaction::TYPE_REQUESTOR; - $types[] = NuanceItemTransaction::TYPE_PROPERTY; - $types[] = NuanceItemTransaction::TYPE_QUEUE; - $types[] = NuanceItemTransaction::TYPE_COMMAND; - - $types[] = PhabricatorTransactions::TYPE_EDGE; - $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceItemTransaction::TYPE_REQUESTOR: - return $object->getRequestorPHID(); - case NuanceItemTransaction::TYPE_SOURCE: - return $object->getSourcePHID(); - case NuanceItemTransaction::TYPE_OWNER: - return $object->getOwnerPHID(); - case NuanceItemTransaction::TYPE_QUEUE: - return $object->getQueuePHID(); - case NuanceItemTransaction::TYPE_PROPERTY: - $key = $xaction->getMetadataValue( - NuanceItemTransaction::PROPERTY_KEY); - return $object->getNuanceProperty($key); - case NuanceItemTransaction::TYPE_COMMAND: - return null; - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceItemTransaction::TYPE_REQUESTOR: - case NuanceItemTransaction::TYPE_SOURCE: - case NuanceItemTransaction::TYPE_OWNER: - case NuanceItemTransaction::TYPE_PROPERTY: - case NuanceItemTransaction::TYPE_QUEUE: - case NuanceItemTransaction::TYPE_COMMAND: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceItemTransaction::TYPE_REQUESTOR: - $object->setRequestorPHID($xaction->getNewValue()); - break; - case NuanceItemTransaction::TYPE_SOURCE: - $object->setSourcePHID($xaction->getNewValue()); - break; - case NuanceItemTransaction::TYPE_OWNER: - $object->setOwnerPHID($xaction->getNewValue()); - break; - case NuanceItemTransaction::TYPE_QUEUE: - $object->setQueuePHID($xaction->getNewValue()); - break; - case NuanceItemTransaction::TYPE_PROPERTY: - $key = $xaction->getMetadataValue( - NuanceItemTransaction::PROPERTY_KEY); - $object->setNuanceProperty($key, $xaction->getNewValue()); - break; - case NuanceItemTransaction::TYPE_COMMAND: - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceItemTransaction::TYPE_REQUESTOR: - case NuanceItemTransaction::TYPE_SOURCE: - case NuanceItemTransaction::TYPE_OWNER: - case NuanceItemTransaction::TYPE_PROPERTY: - case NuanceItemTransaction::TYPE_QUEUE: - case NuanceItemTransaction::TYPE_COMMAND: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - } diff --git a/src/applications/nuance/editor/NuanceQueueEditEngine.php b/src/applications/nuance/editor/NuanceQueueEditEngine.php index 049916adbe..12f5c7b517 100644 --- a/src/applications/nuance/editor/NuanceQueueEditEngine.php +++ b/src/applications/nuance/editor/NuanceQueueEditEngine.php @@ -75,7 +75,7 @@ final class NuanceQueueEditEngine ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the queue.')) - ->setTransactionType(NuanceQueueTransaction::TYPE_NAME) + ->setTransactionType(NuanceQueueNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getName()), ); diff --git a/src/applications/nuance/editor/NuanceQueueEditor.php b/src/applications/nuance/editor/NuanceQueueEditor.php index cb3ead2417..2a18188f98 100644 --- a/src/applications/nuance/editor/NuanceQueueEditor.php +++ b/src/applications/nuance/editor/NuanceQueueEditor.php @@ -14,89 +14,10 @@ final class NuanceQueueEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = NuanceQueueTransaction::TYPE_NAME; - $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceQueueTransaction::TYPE_NAME: - return $object->getName(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceQueueTransaction::TYPE_NAME: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceQueueTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceQueueTransaction::TYPE_NAME: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case NuanceQueueTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('A queue must have a name.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - } - - return $errors; - } - - } diff --git a/src/applications/nuance/editor/NuanceSourceEditEngine.php b/src/applications/nuance/editor/NuanceSourceEditEngine.php index 18d27863ac..eac751c3a5 100644 --- a/src/applications/nuance/editor/NuanceSourceEditEngine.php +++ b/src/applications/nuance/editor/NuanceSourceEditEngine.php @@ -96,14 +96,15 @@ final class NuanceSourceEditEngine ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the source.')) - ->setTransactionType(NuanceSourceTransaction::TYPE_NAME) + ->setTransactionType(NuanceSourceNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getName()), id(new PhabricatorDatasourceEditField()) ->setKey('defaultQueue') ->setLabel(pht('Default Queue')) ->setDescription(pht('Default queue.')) - ->setTransactionType(NuanceSourceTransaction::TYPE_DEFAULT_QUEUE) + ->setTransactionType( + NuanceSourceDefaultQueueTransaction::TRANSACTIONTYPE) ->setDatasource(new NuanceQueueDatasource()) ->setSingleValue($object->getDefaultQueuePHID()), ); diff --git a/src/applications/nuance/editor/NuanceSourceEditor.php b/src/applications/nuance/editor/NuanceSourceEditor.php index 5fbc02b962..b56b183f9e 100644 --- a/src/applications/nuance/editor/NuanceSourceEditor.php +++ b/src/applications/nuance/editor/NuanceSourceEditor.php @@ -18,111 +18,10 @@ final class NuanceSourceEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = NuanceSourceTransaction::TYPE_NAME; - $types[] = NuanceSourceTransaction::TYPE_DEFAULT_QUEUE; - - $types[] = PhabricatorTransactions::TYPE_EDGE; - $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceSourceTransaction::TYPE_NAME: - return $object->getName(); - case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE: - return $object->getDefaultQueuePHID(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceSourceTransaction::TYPE_NAME: - case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceSourceTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - break; - case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE: - $object->setDefaultQueuePHID($xaction->getNewValue()); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceSourceTransaction::TYPE_NAME: - case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case NuanceSourceTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Source name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case NuanceSourceTransaction::TYPE_DEFAULT_QUEUE: - foreach ($xactions as $xaction) { - if (!$xaction->getNewValue()) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Sources must have a default queue.'), - $xaction); - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - } - break; - } - - return $errors; - } - } diff --git a/src/applications/nuance/item/NuanceFormItemType.php b/src/applications/nuance/item/NuanceFormItemType.php new file mode 100644 index 0000000000..cbdde0e89c --- /dev/null +++ b/src/applications/nuance/item/NuanceFormItemType.php @@ -0,0 +1,54 @@ +newCommand('trash') + ->setIcon('fa-trash') + ->setName(pht('Throw In Trash')), + ); + } + + protected function newItemView(NuanceItem $item) { + $viewer = $this->getViewer(); + + $content = $item->getItemProperty('complaint'); + $content_view = id(new PHUIRemarkupView($viewer, $content)) + ->setContextObject($item); + + $content_section = id(new PHUIPropertyListView()) + ->addTextContent( + phutil_tag( + 'div', + array( + 'class' => 'phabricator-remarkup', + ), + $content_view)); + + $content_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Complaint')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($content_section); + + return array( + $content_box, + ); + } + + protected function handleAction(NuanceItem $item, $action) { + return null; + } + +} diff --git a/src/applications/nuance/item/NuanceGitHubEventItemType.php b/src/applications/nuance/item/NuanceGitHubEventItemType.php index 3fef8b3da7..b8bfb3ccab 100644 --- a/src/applications/nuance/item/NuanceGitHubEventItemType.php +++ b/src/applications/nuance/item/NuanceGitHubEventItemType.php @@ -329,6 +329,9 @@ final class NuanceGitHubEventItemType NuanceItem $item, NuanceItemCommand $command) { + // TODO: This code is no longer reachable, and has moved to + // CommandImplementation subclasses. + $action = $command->getCommand(); switch ($action) { case 'sync': @@ -390,12 +393,14 @@ final class NuanceGitHubEventItemType $state = $xobj->getProperty('task.state'); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType( + ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title) ->setDateCreated($created); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE) ->setNewValue($description) ->setDateCreated($created); diff --git a/src/applications/nuance/item/NuanceItemType.php b/src/applications/nuance/item/NuanceItemType.php index 74555494d0..3a65ad0195 100644 --- a/src/applications/nuance/item/NuanceItemType.php +++ b/src/applications/nuance/item/NuanceItemType.php @@ -32,6 +32,10 @@ abstract class NuanceItemType return $this->newItemView($item); } + final public function buildItemWorkView(NuanceItem $item) { + return $this->newItemView($item); + } + protected function newItemView(NuanceItem $item) { return null; } @@ -91,57 +95,15 @@ abstract class NuanceItemType } final public function buildActionResponse(NuanceItem $item, $action) { - $response = $this->handleAction($item, $action); - - if ($response === null) { - return new Aphront404Response(); - } - - return $response; + return $this->handleAction($item, $action); } protected function handleAction(NuanceItem $item, $action) { return null; } - final public function applyCommand( - NuanceItem $item, - NuanceItemCommand $command) { - - $result = $this->handleCommand($item, $command); - - if ($result === null) { - return; - } - - $xaction = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_COMMAND) - ->setNewValue( - array( - 'command' => $command->getCommand(), - 'parameters' => $command->getParameters(), - 'result' => $result, - )); - - $viewer = $this->getViewer(); - - // TODO: Maybe preserve the actor's original content source? - $source = PhabricatorContentSource::newForSource( - PhabricatorDaemonContentSource::SOURCECONST); - - $editor = id(new NuanceItemEditor()) - ->setActor($viewer) - ->setActingAsPHID($command->getAuthorPHID()) - ->setContentSource($source) - ->setContinueOnMissingFields(true) - ->setContinueOnNoEffect(true) - ->applyTransactions($item, array($xaction)); - } - - protected function handleCommand( - NuanceItem $item, - NuanceItemCommand $command) { - return null; + final public function buildWorkCommands(NuanceItem $item) { + return $this->newWorkCommands($item); } final protected function newContentSource( @@ -159,4 +121,8 @@ abstract class NuanceItemType return id(new PhabricatorNuanceApplication())->getPHID(); } + protected function newCommand($command_key) { + return id(new NuanceItemCommandSpec()) + ->setCommandKey($command_key); + } } diff --git a/src/applications/nuance/phid/NuanceItemPHIDType.php b/src/applications/nuance/phid/NuanceItemPHIDType.php index ee51633ea9..771b398419 100644 --- a/src/applications/nuance/phid/NuanceItemPHIDType.php +++ b/src/applications/nuance/phid/NuanceItemPHIDType.php @@ -33,7 +33,7 @@ final class NuanceItemPHIDType extends PhabricatorPHIDType { foreach ($handles as $phid => $handle) { $item = $objects[$phid]; - $handle->setName($item->getItemDisplayName()); + $handle->setName($item->getDisplayName()); $handle->setURI($item->getURI()); } } diff --git a/src/applications/nuance/query/NuanceItemCommandQuery.php b/src/applications/nuance/query/NuanceItemCommandQuery.php index cb20610187..27137cf8f6 100644 --- a/src/applications/nuance/query/NuanceItemCommandQuery.php +++ b/src/applications/nuance/query/NuanceItemCommandQuery.php @@ -5,6 +5,7 @@ final class NuanceItemCommandQuery private $ids; private $itemPHIDs; + private $statuses; public function withIDs(array $ids) { $this->ids = $ids; @@ -16,6 +17,11 @@ final class NuanceItemCommandQuery return $this; } + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + public function newResultObject() { return new NuanceItemCommand(); } @@ -41,6 +47,13 @@ final class NuanceItemCommandQuery $this->itemPHIDs); } + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn, + 'status IN (%Ls)', + $this->statuses); + } + return $where; } diff --git a/src/applications/nuance/query/NuanceItemQuery.php b/src/applications/nuance/query/NuanceItemQuery.php index 5bb0ec70ec..834e81ca72 100644 --- a/src/applications/nuance/query/NuanceItemQuery.php +++ b/src/applications/nuance/query/NuanceItemQuery.php @@ -6,9 +6,11 @@ final class NuanceItemQuery private $ids; private $phids; private $sourcePHIDs; + private $queuePHIDs; private $itemTypes; private $itemKeys; private $containerKeys; + private $statuses; public function withIDs(array $ids) { $this->ids = $ids; @@ -25,6 +27,11 @@ final class NuanceItemQuery return $this; } + public function withQueuePHIDs(array $queue_phids) { + $this->queuePHIDs = $queue_phids; + return $this; + } + public function withItemTypes(array $item_types) { $this->itemTypes = $item_types; return $this; @@ -35,6 +42,11 @@ final class NuanceItemQuery return $this; } + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + public function withItemContainerKeys(array $container_keys) { $this->containerKeys = $container_keys; return $this; @@ -49,13 +61,11 @@ final class NuanceItemQuery } protected function willFilterPage(array $items) { + $viewer = $this->getViewer(); $source_phids = mpull($items, 'getSourcePHID'); - // NOTE: We always load sources, even if the viewer can't formally see - // them. If they can see the item, they're allowed to be aware of the - // source in some sense. $sources = id(new NuanceSourceQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($viewer) ->withPHIDs($source_phids) ->execute(); $sources = mpull($sources, null, 'getPHID'); @@ -81,6 +91,43 @@ final class NuanceItemQuery $item->attachImplementation($type); } + $queue_phids = array(); + foreach ($items as $item) { + $queue_phid = $item->getQueuePHID(); + if ($queue_phid) { + $queue_phids[$queue_phid] = $queue_phid; + } + } + + if ($queue_phids) { + $queues = id(new NuanceQueueQuery()) + ->setViewer($viewer) + ->withPHIDs($queue_phids) + ->execute(); + $queues = mpull($queues, null, 'getPHID'); + } else { + $queues = array(); + } + + foreach ($items as $key => $item) { + $queue_phid = $item->getQueuePHID(); + + if (!$queue_phid) { + $item->attachQueue(null); + continue; + } + + $queue = idx($queues, $queue_phid); + + if (!$queue) { + unset($items[$key]); + $this->didRejectResult($item); + continue; + } + + $item->attachQueue($queue); + } + return $items; } @@ -94,6 +141,13 @@ final class NuanceItemQuery $this->sourcePHIDs); } + if ($this->queuePHIDs !== null) { + $where[] = qsprintf( + $conn, + 'queuePHID IN (%Ls)', + $this->queuePHIDs); + } + if ($this->ids !== null) { $where[] = qsprintf( $conn, @@ -108,6 +162,13 @@ final class NuanceItemQuery $this->phids); } + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn, + 'status IN (%Ls)', + $this->statuses); + } + if ($this->itemTypes !== null) { $where[] = qsprintf( $conn, diff --git a/src/applications/nuance/query/NuanceItemSearchEngine.php b/src/applications/nuance/query/NuanceItemSearchEngine.php index 2f0951b4e0..0868d7551a 100644 --- a/src/applications/nuance/query/NuanceItemSearchEngine.php +++ b/src/applications/nuance/query/NuanceItemSearchEngine.php @@ -72,6 +72,19 @@ final class NuanceItemSearchEngine $impl->getItemTypeDisplayIcon(), $impl->getItemTypeDisplayName()); + $queue = $item->getQueue(); + if ($queue) { + $view->addAttribute( + phutil_tag( + 'a', + array( + 'href' => $queue->getURI(), + ), + $queue->getName())); + } else { + $view->addAttribute(phutil_tag('em', array(), pht('Not in Queue'))); + } + $list->addItem($view); } diff --git a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php index e7c7212590..2eaf5781dc 100644 --- a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php +++ b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php @@ -39,6 +39,8 @@ final class NuancePhabricatorFormSourceDefinition $content_source = PhabricatorContentSource::newFromRequest($request); $item = $this->newItemFromProperties( + NuanceFormItemType::ITEMTYPE, + $viewer->getPHID(), $properties, $content_source); @@ -79,7 +81,7 @@ final class NuancePhabricatorFormSourceDefinition NuanceItem $item, PHUIPropertyListView $view) { - $complaint = $item->getNuanceProperty('complaint'); + $complaint = $item->getItemProperty('complaint'); $complaint = new PHUIRemarkupView($viewer, $complaint); $view->addSectionHeader( pht('Complaint'), 'fa-exclamation-circle'); diff --git a/src/applications/nuance/source/NuanceSourceDefinition.php b/src/applications/nuance/source/NuanceSourceDefinition.php index 99d79f8f31..2c4ebc65e5 100644 --- a/src/applications/nuance/source/NuanceSourceDefinition.php +++ b/src/applications/nuance/source/NuanceSourceDefinition.php @@ -149,6 +149,8 @@ abstract class NuanceSourceDefinition extends Phobject { } protected function newItemFromProperties( + $item_type, + $author_phid, array $properties, PhabricatorContentSource $content_source) { @@ -157,29 +159,31 @@ abstract class NuanceSourceDefinition extends Phobject { $actor = PhabricatorUser::getOmnipotentUser(); $source = $this->getSource(); - $item = NuanceItem::initializeNewItem(); + $item = NuanceItem::initializeNewItem($item_type); $xactions = array(); $xactions[] = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_SOURCE) + ->setTransactionType(NuanceItemSourceTransaction::TRANSACTIONTYPE) ->setNewValue($source->getPHID()); // TODO: Eventually, apply real routing rules. For now, just put everything // in the default queue for the source. $xactions[] = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_QUEUE) + ->setTransactionType(NuanceItemQueueTransaction::TRANSACTIONTYPE) ->setNewValue($source->getDefaultQueuePHID()); + // TODO: Maybe this should all be modular transactions now? foreach ($properties as $key => $property) { $xactions[] = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_PROPERTY) + ->setTransactionType(NuanceItemPropertyTransaction::TRANSACTIONTYPE) ->setMetadataValue(NuanceItemTransaction::PROPERTY_KEY, $key) ->setNewValue($property); } $editor = id(new NuanceItemEditor()) ->setActor($actor) + ->setActingAsPHID($author_phid) ->setContentSource($content_source); $editor->applyTransactions($item, $xactions); diff --git a/src/applications/nuance/storage/NuanceItem.php b/src/applications/nuance/storage/NuanceItem.php index 2eadcdb2d9..09a106ca7a 100644 --- a/src/applications/nuance/storage/NuanceItem.php +++ b/src/applications/nuance/storage/NuanceItem.php @@ -9,7 +9,6 @@ final class NuanceItem const STATUS_IMPORTING = 'importing'; const STATUS_ROUTING = 'routing'; const STATUS_OPEN = 'open'; - const STATUS_ASSIGNED = 'assigned'; const STATUS_CLOSED = 'closed'; protected $status; @@ -23,11 +22,16 @@ final class NuanceItem protected $data = array(); protected $mailKey; + private $queue = self::ATTACHABLE; private $source = self::ATTACHABLE; private $implementation = self::ATTACHABLE; - public static function initializeNewItem() { + public static function initializeNewItem($item_type) { + + // TODO: Validate that the type is valid, and construct and attach it. + return id(new NuanceItem()) + ->setItemType($item_type) ->setStatus(self::STATUS_OPEN); } @@ -42,7 +46,7 @@ final class NuanceItem 'requestorPHID' => 'phid?', 'queuePHID' => 'phid?', 'itemType' => 'text64', - 'itemKey' => 'text64', + 'itemKey' => 'text64?', 'itemContainerKey' => 'text64?', 'status' => 'text32', 'mailKey' => 'bytes20', @@ -172,6 +176,15 @@ final class NuanceItem return $this; } + public function getQueue() { + return $this->assertAttached($this->queue); + } + + public function attachQueue(NuanceQueue $queue = null) { + $this->queue = $queue; + return $this; + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/nuance/storage/NuanceItemCommand.php b/src/applications/nuance/storage/NuanceItemCommand.php index bda6860ff5..21d3792ff2 100644 --- a/src/applications/nuance/storage/NuanceItemCommand.php +++ b/src/applications/nuance/storage/NuanceItemCommand.php @@ -4,32 +4,86 @@ final class NuanceItemCommand extends NuanceDAO implements PhabricatorPolicyInterface { + const STATUS_ISSUED = 'issued'; + const STATUS_EXECUTING = 'executing'; + const STATUS_DONE = 'done'; + const STATUS_FAILED = 'failed'; + protected $itemPHID; protected $authorPHID; + protected $queuePHID; protected $command; - protected $parameters; + protected $status; + protected $parameters = array(); public static function initializeNewCommand() { - return new self(); + return id(new self()) + ->setStatus(self::STATUS_ISSUED); } protected function getConfiguration() { return array( - self::CONFIG_TIMESTAMPS => false, self::CONFIG_SERIALIZATION => array( 'parameters' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'command' => 'text64', + 'status' => 'text64', + 'queuePHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( - 'key_item' => array( - 'columns' => array('itemPHID'), + 'key_pending' => array( + 'columns' => array('itemPHID', 'status'), ), ), ) + parent::getConfiguration(); } + public static function getStatusMap() { + return array( + self::STATUS_ISSUED => array( + 'name' => pht('Issued'), + 'icon' => 'fa-clock-o', + 'color' => 'bluegrey', + ), + self::STATUS_EXECUTING => array( + 'name' => pht('Executing'), + 'icon' => 'fa-play', + 'color' => 'green', + ), + self::STATUS_DONE => array( + 'name' => pht('Done'), + 'icon' => 'fa-check', + 'color' => 'blue', + ), + self::STATUS_FAILED => array( + 'name' => pht('Failed'), + 'icon' => 'fa-times', + 'color' => 'red', + ), + ); + } + + private function getStatusSpec() { + $map = self::getStatusMap(); + return idx($map, $this->getStatus(), array()); + } + + public function getStatusIcon() { + $spec = $this->getStatusSpec(); + return idx($spec, 'icon', 'fa-question'); + } + + public function getStatusColor() { + $spec = $this->getStatusSpec(); + return idx($spec, 'color', 'indigo'); + } + + public function getStatusName() { + $spec = $this->getStatusSpec(); + return idx($spec, 'name', $this->getStatus()); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/nuance/storage/NuanceItemTransaction.php b/src/applications/nuance/storage/NuanceItemTransaction.php index 77471fcdb9..5579183a66 100644 --- a/src/applications/nuance/storage/NuanceItemTransaction.php +++ b/src/applications/nuance/storage/NuanceItemTransaction.php @@ -5,13 +5,6 @@ final class NuanceItemTransaction const PROPERTY_KEY = 'property.key'; - const TYPE_OWNER = 'nuance.item.owner'; - const TYPE_REQUESTOR = 'nuance.item.requestor'; - const TYPE_SOURCE = 'nuance.item.source'; - const TYPE_PROPERTY = 'nuance.item.property'; - const TYPE_QUEUE = 'nuance.item.queue'; - const TYPE_COMMAND = 'nuance.item.command'; - public function getApplicationTransactionType() { return NuanceItemPHIDType::TYPECONST; } @@ -20,61 +13,8 @@ final class NuanceItemTransaction return new NuanceItemTransactionComment(); } - public function shouldHide() { - $old = $this->getOldValue(); - $type = $this->getTransactionType(); - - switch ($type) { - case self::TYPE_REQUESTOR: - case self::TYPE_SOURCE: - return ($old === null); - } - - return parent::shouldHide(); - } - - public function getRequiredHandlePHIDs() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $type = $this->getTransactionType(); - - $phids = parent::getRequiredHandlePHIDs(); - switch ($type) { - case self::TYPE_QUEUE: - if ($old) { - $phids[] = $old; - } - if ($new) { - $phids[] = $new; - } - break; - } - - return $phids; - } - - public function getTitle() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $type = $this->getTransactionType(); - - $author_phid = $this->getAuthorPHID(); - - switch ($type) { - case self::TYPE_QUEUE: - return pht( - '%s routed this item to the %s queue.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); - case self::TYPE_COMMAND: - // TODO: Give item types a chance to render this properly. - return pht( - '%s applied command "%s" to this item.', - $this->renderHandleLink($author_phid), - idx($new, 'command')); - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'NuanceItemTransactionType'; } } diff --git a/src/applications/nuance/storage/NuanceQueue.php b/src/applications/nuance/storage/NuanceQueue.php index 9691a42e2f..f0ba5bb45c 100644 --- a/src/applications/nuance/storage/NuanceQueue.php +++ b/src/applications/nuance/storage/NuanceQueue.php @@ -43,6 +43,10 @@ final class NuanceQueue return '/nuance/queue/view/'.$this->getID().'/'; } + public function getWorkURI() { + return '/nuance/queue/work/'.$this->getID().'/'; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/nuance/storage/NuanceQueueTransaction.php b/src/applications/nuance/storage/NuanceQueueTransaction.php index 44309c2ae6..1561db80e5 100644 --- a/src/applications/nuance/storage/NuanceQueueTransaction.php +++ b/src/applications/nuance/storage/NuanceQueueTransaction.php @@ -2,8 +2,6 @@ final class NuanceQueueTransaction extends NuanceTransaction { - const TYPE_NAME = 'nuance.queue.name'; - public function getApplicationTransactionType() { return NuanceQueuePHIDType::TYPECONST; } @@ -12,26 +10,8 @@ final class NuanceQueueTransaction extends NuanceTransaction { return new NuanceQueueTransactionComment(); } - public function getTitle() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $type = $this->getTransactionType(); - - $author_phid = $this->getAuthorPHID(); - - switch ($type) { - case PhabricatorTransactions::TYPE_CREATE: - return pht( - '%s created this queue.', - $this->renderHandleLink($author_phid)); - case self::TYPE_NAME: - return pht( - '%s renamed this queue from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'NuanceQueueTransactionType'; } + } diff --git a/src/applications/nuance/storage/NuanceSourceTransaction.php b/src/applications/nuance/storage/NuanceSourceTransaction.php index 0b18c81184..0e264876dd 100644 --- a/src/applications/nuance/storage/NuanceSourceTransaction.php +++ b/src/applications/nuance/storage/NuanceSourceTransaction.php @@ -3,9 +3,6 @@ final class NuanceSourceTransaction extends NuanceTransaction { - const TYPE_NAME = 'source.name'; - const TYPE_DEFAULT_QUEUE = 'source.queue.default'; - public function getApplicationTransactionType() { return NuanceSourcePHIDType::TYPECONST; } @@ -14,63 +11,8 @@ final class NuanceSourceTransaction return new NuanceSourceTransactionComment(); } - public function shouldHide() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $type = $this->getTransactionType(); - - switch ($type) { - case self::TYPE_DEFAULT_QUEUE: - return !$old; - case self::TYPE_NAME: - return ($old === null); - } - - return parent::shouldHide(); - } - - public function getRequiredHandlePHIDs() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $type = $this->getTransactionType(); - - $phids = parent::getRequiredHandlePHIDs(); - switch ($type) { - case self::TYPE_DEFAULT_QUEUE: - if ($old) { - $phids[] = $old; - } - if ($new) { - $phids[] = $new; - } - break; - } - - return $phids; - } - - public function getTitle() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $type = $this->getTransactionType(); - $author_phid = $this->getAuthorPHID(); - - switch ($type) { - case self::TYPE_DEFAULT_QUEUE: - return pht( - '%s changed the default queue from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - case self::TYPE_NAME: - return pht( - '%s renamed this source from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'NuanceSourceTransactionType'; } } diff --git a/src/applications/nuance/storage/NuanceTransaction.php b/src/applications/nuance/storage/NuanceTransaction.php index 5e9ae656ef..0cdb98ff27 100644 --- a/src/applications/nuance/storage/NuanceTransaction.php +++ b/src/applications/nuance/storage/NuanceTransaction.php @@ -1,7 +1,7 @@ getTaskDataValue('itemPHID'); - $hash = PhabricatorHash::digestForIndex($item_phid); - $lock_key = "nuance.item.{$hash}"; - $lock = PhabricatorGlobalLock::newLock($lock_key); + $lock = $this->newLock($item_phid); $lock->lock(1); try { @@ -55,19 +53,109 @@ final class NuanceItemUpdateWorker private function applyCommands(NuanceItem $item) { $viewer = $this->getViewer(); - $impl = $item->getImplementation(); - $impl->setViewer($viewer); - $commands = id(new NuanceItemCommandQuery()) ->setViewer($viewer) ->withItemPHIDs(array($item->getPHID())) + ->withStatuses( + array( + NuanceItemCommand::STATUS_ISSUED, + )) ->execute(); $commands = msort($commands, 'getID'); + $this->executeCommandList($item, $commands); + } + + public function executeCommands(NuanceItem $item, array $commands) { + if (!$commands) { + return true; + } + + $item_phid = $item->getPHID(); + $viewer = $this->getViewer(); + + $lock = $this->newLock($item_phid); + try { + $lock->lock(1); + } catch (PhutilLockException $ex) { + return false; + } + + try { + $item = $this->loadItem($item_phid); + + // Reload commands now that we have a lock, to make sure we don't + // execute any commands twice by mistake. + $commands = id(new NuanceItemCommandQuery()) + ->setViewer($viewer) + ->withIDs(mpull($commands, 'getID')) + ->execute(); + + $this->executeCommandList($item, $commands); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } + + $lock->unlock(); + + return true; + } + + private function executeCommandList(NuanceItem $item, array $commands) { + $viewer = $this->getViewer(); + + $executors = NuanceCommandImplementation::getAllCommands(); foreach ($commands as $command) { - $impl->applyCommand($item, $command); - $command->delete(); + if ($command->getItemPHID() !== $item->getPHID()) { + throw new Exception( + pht('Trying to apply a command to the wrong item!')); + } + + if ($command->getStatus() !== NuanceItemCommand::STATUS_ISSUED) { + // Never execute commands which have already been issued. + continue; + } + + $command + ->setStatus(NuanceItemCommand::STATUS_EXECUTING) + ->save(); + + try { + $command_key = $command->getCommand(); + + $executor = idx($executors, $command_key); + if (!$executor) { + throw new Exception( + pht( + 'Unable to execute command "%s": this command does not have '. + 'a recognized command implementation.', + $command_key)); + } + + $executor = clone $executor; + + $executor + ->setActor($viewer) + ->applyCommand($item, $command); + + $command + ->setStatus(NuanceItemCommand::STATUS_DONE) + ->save(); + } catch (Exception $ex) { + $command + ->setStatus(NuanceItemCommand::STATUS_FAILED) + ->save(); + + throw $ex; + } } } + private function newLock($item_phid) { + $hash = PhabricatorHash::digestForIndex($item_phid); + $lock_key = "nuance.item.{$hash}"; + return PhabricatorGlobalLock::newLock($lock_key); + } + } diff --git a/src/applications/nuance/xaction/NuanceItemCommandTransaction.php b/src/applications/nuance/xaction/NuanceItemCommandTransaction.php new file mode 100644 index 0000000000..6aa1d6473f --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemCommandTransaction.php @@ -0,0 +1,22 @@ +getNewValue(); + $command_key = idx($spec, 'command', '???'); + + return pht( + '%s applied a "%s" command to this item.', + $this->renderAuthor(), + $command_key); + } + +} diff --git a/src/applications/nuance/xaction/NuanceItemOwnerTransaction.php b/src/applications/nuance/xaction/NuanceItemOwnerTransaction.php new file mode 100644 index 0000000000..381371c8f3 --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemOwnerTransaction.php @@ -0,0 +1,27 @@ +getOwnerPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setOwnerPHID($value); + } + + public function getTitle() { + + // TODO: Assign, unassign strings probably need variants. + + return pht( + '%s reassigned this item from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($this->getOldValue()), + $this->renderHandle($this->getNewValue())); + } + +} diff --git a/src/applications/nuance/xaction/NuanceItemPropertyTransaction.php b/src/applications/nuance/xaction/NuanceItemPropertyTransaction.php new file mode 100644 index 0000000000..3367a6a0cf --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemPropertyTransaction.php @@ -0,0 +1,27 @@ +getMetadataValue($property_key); + return $object->getItemProperty($key); + } + + public function applyInternalEffects($object, $value) { + $property_key = NuanceItemTransaction::PROPERTY_KEY; + $key = $this->getMetadataValue($property_key); + + $object->setItemProperty($key, $value); + } + + public function getTitle() { + return pht( + '%s set a property on this item.', + $this->renderAuthor()); + } + +} diff --git a/src/applications/nuance/xaction/NuanceItemQueueTransaction.php b/src/applications/nuance/xaction/NuanceItemQueueTransaction.php new file mode 100644 index 0000000000..8ccbdb7797 --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemQueueTransaction.php @@ -0,0 +1,25 @@ +getQueuePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setQueuePHID($value); + } + + public function getTitle() { + return pht( + '%s rerouted this item from %s to %s.', + $this->renderAuthor(), + $this->renderHandle($this->getOldValue()), + $this->renderHandle($this->getNewValue())); + } + + +} diff --git a/src/applications/nuance/xaction/NuanceItemRequestorTransaction.php b/src/applications/nuance/xaction/NuanceItemRequestorTransaction.php new file mode 100644 index 0000000000..a19ef45197 --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemRequestorTransaction.php @@ -0,0 +1,16 @@ +getRequestorPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setRequestorPHID($value); + } + +} diff --git a/src/applications/nuance/xaction/NuanceItemSourceTransaction.php b/src/applications/nuance/xaction/NuanceItemSourceTransaction.php new file mode 100644 index 0000000000..e637485990 --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemSourceTransaction.php @@ -0,0 +1,16 @@ +getSourcePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setSourcePHID($value); + } + +} diff --git a/src/applications/nuance/xaction/NuanceItemStatusTransaction.php b/src/applications/nuance/xaction/NuanceItemStatusTransaction.php new file mode 100644 index 0000000000..48c5a16e8c --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemStatusTransaction.php @@ -0,0 +1,25 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + return pht( + '%s changed the status of this item from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + +} diff --git a/src/applications/nuance/xaction/NuanceItemTransactionType.php b/src/applications/nuance/xaction/NuanceItemTransactionType.php new file mode 100644 index 0000000000..cc03b31ac4 --- /dev/null +++ b/src/applications/nuance/xaction/NuanceItemTransactionType.php @@ -0,0 +1,4 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this queue from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Queues must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Queue names must not be longer than %s character(s).', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/nuance/xaction/NuanceQueueTransactionType.php b/src/applications/nuance/xaction/NuanceQueueTransactionType.php new file mode 100644 index 0000000000..5662e3619f --- /dev/null +++ b/src/applications/nuance/xaction/NuanceQueueTransactionType.php @@ -0,0 +1,4 @@ +getDefaultQueuePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setDefaultQueuePHID($value); + } + + public function getTitle() { + return pht( + '%s changed the default queue for this source from %s to %s.', + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$object->getDefaultQueuePHID() && !$xactions) { + $errors[] = $this->newRequiredError( + pht('Sources must have a default queue.')); + } + + foreach ($xactions as $xaction) { + if (!$xaction->getNewValue()) { + $errors[] = $this->newRequiredError( + pht('Sources must have a default queue.')); + } + } + + return $errors; + } + +} diff --git a/src/applications/nuance/xaction/NuanceSourceNameTransaction.php b/src/applications/nuance/xaction/NuanceSourceNameTransaction.php new file mode 100644 index 0000000000..e33b60551c --- /dev/null +++ b/src/applications/nuance/xaction/NuanceSourceNameTransaction.php @@ -0,0 +1,47 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this source from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Sources must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Source names must not be longer than %s character(s).', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/nuance/xaction/NuanceSourceTransactionType.php b/src/applications/nuance/xaction/NuanceSourceTransactionType.php new file mode 100644 index 0000000000..5a26149cf8 --- /dev/null +++ b/src/applications/nuance/xaction/NuanceSourceTransactionType.php @@ -0,0 +1,4 @@ + $this->getPanelURI('?revoke='.$authorization->getID()), - 'class' => 'small grey button', + 'class' => 'small button button-grey', 'sigil' => 'workflow', ), pht('Revoke')); @@ -134,7 +134,8 @@ final class PhabricatorOAuthServerAuthorizationsSettingsPanel $panel = id(new PHUIObjectBoxView()) ->setHeader($header) - ->setTable($table); + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) + ->appendChild($table); return $panel; } diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php index b69faf5648..e5463b4cb4 100644 --- a/src/applications/owners/controller/PhabricatorOwnersPathsController.php +++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php @@ -124,7 +124,7 @@ final class PhabricatorOwnersPathsController 'a', array( 'href' => '#', - 'class' => 'button green', + 'class' => 'button button-green', 'sigil' => 'addpath', 'mustcapture' => true, ), diff --git a/src/applications/owners/engineextension/PhabricatorOwnersHovercardEngineExtension.php b/src/applications/owners/engineextension/PhabricatorOwnersHovercardEngineExtension.php index 744d3fbea2..39fe30ad28 100644 --- a/src/applications/owners/engineextension/PhabricatorOwnersHovercardEngineExtension.php +++ b/src/applications/owners/engineextension/PhabricatorOwnersHovercardEngineExtension.php @@ -64,7 +64,7 @@ final class PhabricatorOwnersHovercardEngineExtension if ($package->isArchived()) { $tag = id(new PHUITagView()) ->setName(pht('Archived')) - ->setShade(PHUITagView::COLOR_INDIGO) + ->setColor(PHUITagView::COLOR_INDIGO) ->setType(PHUITagView::TYPE_OBJECT); $hovercard->addTag($tag); } diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php index a1c10cd5e9..ce411aad35 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php @@ -398,13 +398,36 @@ final class PhabricatorOwnersPackageQuery } } + // At each strength level, drop weak packages if there are also strong + // packages of the same strength. + $strength_map = igroup($matches, 'strength'); + foreach ($strength_map as $strength => $package_list) { + $any_strong = false; + foreach ($package_list as $package_id => $package) { + if (!$package['weak']) { + $any_strong = true; + break; + } + } + if ($any_strong) { + foreach ($package_list as $package_id => $package) { + if ($package['weak']) { + unset($matches[$package_id]); + } + } + } + } + $matches = isort($matches, 'strength'); $matches = array_reverse($matches); - $first_id = null; + $strongest = null; foreach ($matches as $package_id => $match) { - if ($first_id === null) { - $first_id = $package_id; + if ($strongest === null) { + $strongest = $match['strength']; + } + + if ($match['strength'] === $strongest) { continue; } diff --git a/src/applications/owners/search/PhabricatorOwnersPackageFerretEngine.php b/src/applications/owners/search/PhabricatorOwnersPackageFerretEngine.php new file mode 100644 index 0000000000..684fe10969 --- /dev/null +++ b/src/applications/owners/search/PhabricatorOwnersPackageFerretEngine.php @@ -0,0 +1,18 @@ + false, self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( - 'name' => 'sort128', - 'originalName' => 'text255', + 'name' => 'sort', 'description' => 'text', 'primaryOwnerPHID' => 'phid?', 'auditingEnabled' => 'bool', @@ -137,9 +136,6 @@ final class PhabricatorOwnersPackage public function setName($name) { $this->name = $name; - if (!$this->getID()) { - $this->originalName = $name; - } return $this; } @@ -302,27 +298,54 @@ final class PhabricatorOwnersPackage // a more specific package. if ($weak) { foreach ($path_packages as $match => $packages) { + + // Group packages by length. + $length_map = array(); + foreach ($packages as $package_id => $package) { + $length_map[$package['length']][$package_id] = $package; + } + + // For each path length, remove all weak packages if there are any + // strong packages of the same length. This makes sure that if there + // are one or more strong claims on a particular path, only those + // claims stand. + foreach ($length_map as $package_list) { + $any_strong = false; + foreach ($package_list as $package_id => $package) { + if (!isset($weak[$package_id])) { + $any_strong = true; + break; + } + } + + if ($any_strong) { + foreach ($package_list as $package_id => $package) { + if (isset($weak[$package_id])) { + unset($packages[$package_id]); + } + } + } + } + $packages = isort($packages, 'length'); $packages = array_reverse($packages, true); - $first = null; + $best_length = null; foreach ($packages as $package_id => $package) { - // If this is the first package we've encountered, note it and - // continue. We're iterating over the packages from longest to - // shortest match, so this package always has the strongest claim - // on the path. - if ($first === null) { - $first = $package_id; + // If this is the first package we've encountered, note its length. + // We're iterating over the packages from longest to shortest match, + // so packages of this length always have the best claim on the path. + if ($best_length === null) { + $best_length = $package['length']; + } + + // If this package has the same length as the best length, its claim + // stands. + if ($package['length'] === $best_length) { continue; } - // If this is the first package we saw, its claim stands even if it - // is a weak package. - if ($first === $package_id) { - continue; - } - - // If this is a weak package and not the first package we saw, + // If this is a weak package and does not have the best length, // cede its claim to the stronger package. if (isset($weak[$package_id])) { unset($packages[$package_id]); @@ -593,6 +616,14 @@ final class PhabricatorOwnersPackage } +/* -( PhabricatorFerretInterface )----------------------------------------- */ + + + public function newFerretEngine() { + return new PhabricatorOwnersPackageFerretEngine(); + } + + /* -( PhabricatorNgramsInterface )----------------------------------------- */ diff --git a/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php b/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php index 7d24fc6e8d..f21f09b44e 100644 --- a/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php +++ b/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php @@ -100,6 +100,95 @@ final class PhabricatorOwnersPackageTestCase extends PhabricatorTestCase { PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths)); + // Test cases where multiple packages own the same path, with various + // dominion rules. + + $main_c = 'src/applications/main/main.c'; + + $rules = array( + // All claims strong. + array( + PhabricatorOwnersPackage::DOMINION_STRONG, + PhabricatorOwnersPackage::DOMINION_STRONG, + PhabricatorOwnersPackage::DOMINION_STRONG, + ), + // All claims weak. + array( + PhabricatorOwnersPackage::DOMINION_WEAK, + PhabricatorOwnersPackage::DOMINION_WEAK, + PhabricatorOwnersPackage::DOMINION_WEAK, + ), + // Mixture of strong and weak claims, strong first. + array( + PhabricatorOwnersPackage::DOMINION_STRONG, + PhabricatorOwnersPackage::DOMINION_STRONG, + PhabricatorOwnersPackage::DOMINION_WEAK, + ), + // Mixture of strong and weak claims, weak first. + array( + PhabricatorOwnersPackage::DOMINION_WEAK, + PhabricatorOwnersPackage::DOMINION_STRONG, + PhabricatorOwnersPackage::DOMINION_STRONG, + ), + ); + + foreach ($rules as $rule_idx => $rule) { + $rows = array( + array( + 'id' => 1, + 'excluded' => 0, + 'dominion' => $rule[0], + 'path' => $main_c, + ), + array( + 'id' => 2, + 'excluded' => 0, + 'dominion' => $rule[1], + 'path' => $main_c, + ), + array( + 'id' => 3, + 'excluded' => 0, + 'dominion' => $rule[2], + 'path' => $main_c, + ), + ); + + $paths = array( + $main_c => $pvalue, + ); + + // If one or more packages have strong dominion, they should own the + // path. If not, all the packages with weak dominion should own the + // path. + $strong = array(); + $weak = array(); + foreach ($rule as $idx => $dominion) { + if ($dominion == PhabricatorOwnersPackage::DOMINION_STRONG) { + $strong[] = $idx + 1; + } else { + $weak[] = $idx + 1; + } + } + + if ($strong) { + $expect = $strong; + } else { + $expect = $weak; + } + + $expect = array_fill_keys($expect, strlen($main_c)); + $actual = PhabricatorOwnersPackage::findLongestPathsPerPackage( + $rows, + $paths); + + ksort($actual); + + $this->assertEqual( + $expect, + $actual, + pht('Ruleset "%s" for Identical Ownership', $rule_idx)); + } } } diff --git a/src/applications/passphrase/controller/PassphraseCredentialConduitController.php b/src/applications/passphrase/controller/PassphraseCredentialConduitController.php index ce8f21f62d..3c95fd8459 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialConduitController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialConduitController.php @@ -50,7 +50,8 @@ final class PassphraseCredentialConduitController $xactions = array(); $xactions[] = id(new PassphraseCredentialTransaction()) - ->setTransactionType(PassphraseCredentialTransaction::TYPE_CONDUIT) + ->setTransactionType( + PassphraseCredentialConduitTransaction::TRANSACTIONTYPE) ->setNewValue(!$credential->getAllowConduit()); $editor = id(new PassphraseCredentialTransactionEditor()) diff --git a/src/applications/passphrase/controller/PassphraseCredentialCreateController.php b/src/applications/passphrase/controller/PassphraseCredentialCreateController.php index 87afe22cc6..b237ef378b 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialCreateController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialCreateController.php @@ -52,17 +52,12 @@ final class PassphraseCredentialCreateController extends PassphraseController { $crumbs->setBorder(true); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Credential')) + ->setHeaderText($title) ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-plus-square'); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter($box); return $this->newPage() diff --git a/src/applications/passphrase/controller/PassphraseCredentialDestroyController.php b/src/applications/passphrase/controller/PassphraseCredentialDestroyController.php index 8858d0ef6b..e1f0532f8a 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialDestroyController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialDestroyController.php @@ -32,7 +32,8 @@ final class PassphraseCredentialDestroyController $xactions = array(); $xactions[] = id(new PassphraseCredentialTransaction()) - ->setTransactionType(PassphraseCredentialTransaction::TYPE_DESTROY) + ->setTransactionType( + PassphraseCredentialDestroyTransaction::TRANSACTIONTYPE) ->setNewValue(1); $editor = id(new PassphraseCredentialTransactionEditor()) diff --git a/src/applications/passphrase/controller/PassphraseCredentialEditController.php b/src/applications/passphrase/controller/PassphraseCredentialEditController.php index bdb1802880..91c8d93883 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialEditController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialEditController.php @@ -117,12 +117,19 @@ final class PassphraseCredentialEditController extends PassphraseController { } if (!$errors) { - $type_name = PassphraseCredentialTransaction::TYPE_NAME; - $type_desc = PassphraseCredentialTransaction::TYPE_DESCRIPTION; - $type_username = PassphraseCredentialTransaction::TYPE_USERNAME; - $type_destroy = PassphraseCredentialTransaction::TYPE_DESTROY; - $type_secret_id = PassphraseCredentialTransaction::TYPE_SECRET_ID; - $type_is_locked = PassphraseCredentialTransaction::TYPE_LOCK; + $type_name = + PassphraseCredentialNameTransaction::TRANSACTIONTYPE; + $type_desc = + PassphraseCredentialDescriptionTransaction::TRANSACTIONTYPE; + $type_username = + PassphraseCredentialUsernameTransaction::TRANSACTIONTYPE; + $type_destroy = + PassphraseCredentialDestroyTransaction::TRANSACTIONTYPE; + $type_secret_id = + PassphraseCredentialSecretIDTransaction::TRANSACTIONTYPE; + $type_is_locked = + PassphraseCredentialLockTransaction::TRANSACTIONTYPE; + $type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY; $type_space = PhabricatorTransactions::TYPE_SPACE; @@ -237,15 +244,11 @@ final class PassphraseCredentialEditController extends PassphraseController { ->setValue($v_name) ->setError($e_name)) ->appendChild( - id(new AphrontFormTextAreaControl()) - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) + id(new PhabricatorRemarkupControl()) + ->setUser($viewer) ->setName('description') ->setLabel(pht('Description')) ->setValue($v_desc)) - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Credential Type')) - ->setValue($type->getCredentialTypeName())) ->appendChild( id(new AphrontFormDividerControl())) ->appendControl( @@ -315,10 +318,9 @@ final class PassphraseCredentialEditController extends PassphraseController { $crumbs->setBorder(true); if ($is_new) { - $title = pht('Create New Credential'); + $title = pht('New Credential: %s', $type->getCredentialTypeName()); $crumbs->addTextCrumb(pht('Create')); $cancel_uri = $this->getApplicationURI(); - $header_icon = 'fa-plus-square'; } else { $title = pht('Edit Credential: %s', $credential->getName()); $crumbs->addTextCrumb( @@ -326,7 +328,6 @@ final class PassphraseCredentialEditController extends PassphraseController { '/K'.$credential->getID()); $crumbs->addTextCrumb(pht('Edit')); $cancel_uri = '/K'.$credential->getID(); - $header_icon = 'fa-pencil'; } if ($request->isAjax()) { @@ -349,18 +350,13 @@ final class PassphraseCredentialEditController extends PassphraseController { ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Credential')) + ->setHeaderText($title) ->setFormErrors($errors) ->setValidationException($validation_exception) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter(array( $box, )); diff --git a/src/applications/passphrase/controller/PassphraseCredentialLockController.php b/src/applications/passphrase/controller/PassphraseCredentialLockController.php index 9832705427..b489851baa 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialLockController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialLockController.php @@ -40,11 +40,13 @@ final class PassphraseCredentialLockController $xactions = array(); $xactions[] = id(new PassphraseCredentialTransaction()) - ->setTransactionType(PassphraseCredentialTransaction::TYPE_CONDUIT) + ->setTransactionType( + PassphraseCredentialConduitTransaction::TRANSACTIONTYPE) ->setNewValue(0); $xactions[] = id(new PassphraseCredentialTransaction()) - ->setTransactionType(PassphraseCredentialTransaction::TYPE_LOCK) + ->setTransactionType( + PassphraseCredentialLockTransaction::TRANSACTIONTYPE) ->setNewValue(1); $editor = id(new PassphraseCredentialTransactionEditor()) diff --git a/src/applications/passphrase/controller/PassphraseCredentialRevealController.php b/src/applications/passphrase/controller/PassphraseCredentialRevealController.php index 4fb299b85e..3a40d253c9 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialRevealController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialRevealController.php @@ -67,7 +67,7 @@ final class PassphraseCredentialRevealController ->setDisableWorkflowOnCancel(true) ->addCancelButton($view_uri, pht('Done')); - $type_secret = PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET; + $type_secret = PassphraseCredentialLookedAtTransaction::TRANSACTIONTYPE; $xactions = array( id(new PassphraseCredentialTransaction()) ->setTransactionType($type_secret) diff --git a/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php b/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php index fd743f4aac..1e23c874cf 100644 --- a/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php +++ b/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php @@ -17,185 +17,15 @@ final class PassphraseCredentialTransactionEditor $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = PassphraseCredentialTransaction::TYPE_NAME; - $types[] = PassphraseCredentialTransaction::TYPE_DESCRIPTION; - $types[] = PassphraseCredentialTransaction::TYPE_USERNAME; - $types[] = PassphraseCredentialTransaction::TYPE_SECRET_ID; - $types[] = PassphraseCredentialTransaction::TYPE_DESTROY; - $types[] = PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET; - $types[] = PassphraseCredentialTransaction::TYPE_LOCK; - $types[] = PassphraseCredentialTransaction::TYPE_CONDUIT; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PassphraseCredentialTransaction::TYPE_NAME: - if ($this->getIsNewObject()) { - return null; - } - return $object->getName(); - case PassphraseCredentialTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case PassphraseCredentialTransaction::TYPE_USERNAME: - return $object->getUsername(); - case PassphraseCredentialTransaction::TYPE_SECRET_ID: - return $object->getSecretID(); - case PassphraseCredentialTransaction::TYPE_DESTROY: - return (int)$object->getIsDestroyed(); - case PassphraseCredentialTransaction::TYPE_LOCK: - return (int)$object->getIsLocked(); - case PassphraseCredentialTransaction::TYPE_CONDUIT: - return (int)$object->getAllowConduit(); - case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET: - return null; - } - - return parent::getCustomTransactionOldValue($object, $xaction); + public function getCreateObjectTitle($author, $object) { + return pht('%s created this credential.', $author); } - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PassphraseCredentialTransaction::TYPE_NAME: - case PassphraseCredentialTransaction::TYPE_DESCRIPTION: - case PassphraseCredentialTransaction::TYPE_USERNAME: - case PassphraseCredentialTransaction::TYPE_SECRET_ID: - case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET: - return $xaction->getNewValue(); - case PassphraseCredentialTransaction::TYPE_DESTROY: - case PassphraseCredentialTransaction::TYPE_LOCK: - return (int)$xaction->getNewValue(); - case PassphraseCredentialTransaction::TYPE_CONDUIT: - return (int)$xaction->getNewValue(); - } - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PassphraseCredentialTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - case PassphraseCredentialTransaction::TYPE_DESCRIPTION: - $object->setDescription($xaction->getNewValue()); - return; - case PassphraseCredentialTransaction::TYPE_USERNAME: - $object->setUsername($xaction->getNewValue()); - return; - case PassphraseCredentialTransaction::TYPE_SECRET_ID: - $old_id = $object->getSecretID(); - if ($old_id) { - $this->destroySecret($old_id); - } - $object->setSecretID($xaction->getNewValue()); - return; - case PassphraseCredentialTransaction::TYPE_DESTROY: - // When destroying a credential, wipe out its secret. - $is_destroyed = $xaction->getNewValue(); - $object->setIsDestroyed($is_destroyed); - if ($is_destroyed) { - $secret_id = $object->getSecretID(); - if ($secret_id) { - $this->destroySecret($secret_id); - $object->setSecretID(null); - } - } - return; - case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET: - return; - case PassphraseCredentialTransaction::TYPE_LOCK: - $object->setIsLocked((int)$xaction->getNewValue()); - return; - case PassphraseCredentialTransaction::TYPE_CONDUIT: - $object->setAllowConduit((int)$xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PassphraseCredentialTransaction::TYPE_NAME: - case PassphraseCredentialTransaction::TYPE_DESCRIPTION: - case PassphraseCredentialTransaction::TYPE_USERNAME: - case PassphraseCredentialTransaction::TYPE_SECRET_ID: - case PassphraseCredentialTransaction::TYPE_DESTROY: - case PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET: - case PassphraseCredentialTransaction::TYPE_LOCK: - case PassphraseCredentialTransaction::TYPE_CONDUIT: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - private function destroySecret($secret_id) { - $table = new PassphraseSecret(); - queryfx( - $table->establishConnection('w'), - 'DELETE FROM %T WHERE id = %d', - $table->getTableName(), - $secret_id); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PassphraseCredentialTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Credential name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case PassphraseCredentialTransaction::TYPE_USERNAME: - $credential_type = $object->getImplementation(); - if (!$credential_type->shouldRequireUsername()) { - break; - } - $missing = $this->validateIsEmptyTextField( - $object->getUsername(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Username is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - } - - return $errors; + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); } protected function supportsSearch() { diff --git a/src/applications/passphrase/query/PassphraseCredentialQuery.php b/src/applications/passphrase/query/PassphraseCredentialQuery.php index 3a78ac0d13..2518ad44e5 100644 --- a/src/applications/passphrase/query/PassphraseCredentialQuery.php +++ b/src/applications/passphrase/query/PassphraseCredentialQuery.php @@ -109,49 +109,49 @@ final class PassphraseCredentialQuery if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'c.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'c.phid IN (%Ls)', $this->phids); } if ($this->credentialTypes !== null) { $where[] = qsprintf( $conn, - 'credentialType in (%Ls)', + 'c.credentialType in (%Ls)', $this->credentialTypes); } if ($this->providesTypes !== null) { $where[] = qsprintf( $conn, - 'providesType IN (%Ls)', + 'c.providesType IN (%Ls)', $this->providesTypes); } if ($this->isDestroyed !== null) { $where[] = qsprintf( $conn, - 'isDestroyed = %d', + 'c.isDestroyed = %d', (int)$this->isDestroyed); } if ($this->allowConduit !== null) { $where[] = qsprintf( $conn, - 'allowConduit = %d', + 'c.allowConduit = %d', (int)$this->allowConduit); } if (strlen($this->nameContains)) { $where[] = qsprintf( $conn, - 'LOWER(name) LIKE %~', + 'LOWER(c.name) LIKE %~', phutil_utf8_strtolower($this->nameContains)); } @@ -162,4 +162,8 @@ final class PassphraseCredentialQuery return 'PhabricatorPassphraseApplication'; } + protected function getPrimaryTableAlias() { + return 'c'; + } + } diff --git a/src/applications/passphrase/search/PassphraseCredentialFerretEngine.php b/src/applications/passphrase/search/PassphraseCredentialFerretEngine.php new file mode 100644 index 0000000000..cb9aa99abb --- /dev/null +++ b/src/applications/passphrase/search/PassphraseCredentialFerretEngine.php @@ -0,0 +1,18 @@ +getOldValue(); - $new = $this->getNewValue(); - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($old === null); - case self::TYPE_LOCK: - return ($old === null); - case self::TYPE_USERNAME: - return !strlen($old); - case self::TYPE_LOOKEDATSECRET: - return false; - case self::TYPE_DESTROY: - // Don't show "undestroy" transactions because they're a bit confusing - // and redundant with restoring a secret. - if (!$new) { - return true; - } - } - return parent::shouldHide(); - } - - public function getTitle() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - $author_phid = $this->getAuthorPHID(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this credential.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s renamed this credential from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for this credential.', - $this->renderHandleLink($author_phid)); - case self::TYPE_USERNAME: - if (strlen($old)) { - return pht( - '%s changed the username for this credential from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } else { - return pht( - '%s set the username for this credential to "%s".', - $this->renderHandleLink($author_phid), - $new); - } - break; - case self::TYPE_SECRET_ID: - if ($old === null) { - return pht( - '%s attached a new secret to this credential.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s updated the secret for this credential.', - $this->renderHandleLink($author_phid)); - } - case self::TYPE_DESTROY: - return pht( - '%s destroyed the secret for this credential.', - $this->renderHandleLink($author_phid)); - case self::TYPE_LOOKEDATSECRET: - return pht( - '%s examined the secret plaintext for this credential.', - $this->renderHandleLink($author_phid)); - case self::TYPE_LOCK: - return pht( - '%s locked this credential.', - $this->renderHandleLink($author_phid)); - case self::TYPE_CONDUIT: - if ($old) { - return pht( - '%s disallowed Conduit API access to this credential.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s allowed Conduit API access to this credential.', - $this->renderHandleLink($author_phid)); - } - break; - } - - return parent::getTitle(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - json_encode($this->getOldValue()), - json_encode($this->getNewValue())); + public function getBaseTransactionClass() { + return 'PassphraseCredentialTransactionType'; } } diff --git a/src/applications/passphrase/view/PassphraseCredentialControl.php b/src/applications/passphrase/view/PassphraseCredentialControl.php index 015dce1f40..9c9706fe3f 100644 --- a/src/applications/passphrase/view/PassphraseCredentialControl.php +++ b/src/applications/passphrase/view/PassphraseCredentialControl.php @@ -53,13 +53,22 @@ final class PassphraseCredentialControl extends AphrontFormControl { if (strlen($current_phid) && empty($options_map[$current_phid])) { $viewer = $this->getViewer(); - $user_credential = id(new PassphraseCredentialQuery()) - ->setViewer($viewer) - ->withPHIDs(array($current_phid)) - ->executeOne(); - if (!$user_credential) { + $current_name = null; + try { + $user_credential = id(new PassphraseCredentialQuery()) + ->setViewer($viewer) + ->withPHIDs(array($current_phid)) + ->executeOne(); + + if ($user_credential) { + $current_name = pht( + '%s %s', + $user_credential->getMonogram(), + $user_credential->getName()); + } + } catch (PhabricatorPolicyException $policy_exception) { // Pull the credential with the ominipotent viewer so we can look up - // the ID and tell if it's restricted or invalid. + // the ID and provide the monogram. $omnipotent_credential = id(new PassphraseCredentialQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs(array($current_phid)) @@ -68,16 +77,13 @@ final class PassphraseCredentialControl extends AphrontFormControl { $current_name = pht( '%s (Restricted Credential)', $omnipotent_credential->getMonogram()); - } else { - $current_name = pht( - 'Invalid Credential ("%s")', - $current_phid); } - } else { + } + + if ($current_name === null) { $current_name = pht( - '%s %s', - $user_credential->getMonogram(), - $user_credential->getName()); + 'Invalid Credential ("%s")', + $current_phid); } $options_map = array( @@ -113,11 +119,12 @@ final class PassphraseCredentialControl extends AphrontFormControl { 'a', array( 'href' => '#', - 'class' => 'button grey', + 'class' => 'button button-grey mll', 'sigil' => 'passphrase-credential-add', 'mustcapture' => true, + 'style' => 'height: 20px;', // move aphront-form to tables ), - pht('Add Credential')); + pht('Add New Credential')); } else { $button = null; } diff --git a/src/applications/passphrase/xaction/PassphraseCredentialConduitTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialConduitTransaction.php new file mode 100644 index 0000000000..57d9828560 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialConduitTransaction.php @@ -0,0 +1,53 @@ +getAllowConduit(); + } + + public function applyInternalEffects($object, $value) { + $object->setAllowConduit((int)$value); + } + + public function getTitle() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s allowed Conduit API access to this credential.', + $this->renderAuthor()); + } else { + return pht( + '%s disallowed Conduit API access to this credential.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s allowed Conduit API access to credential %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s disallowed Conduit API access to credential %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + if ($new) { + return 'fa-tty'; + } else { + return 'fa-ban'; + } + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialDescriptionTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialDescriptionTransaction.php new file mode 100644 index 0000000000..ec989cf9c9 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialDescriptionTransaction.php @@ -0,0 +1,64 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function shouldHide() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return true; + } + return false; + } + + public function getTitle() { + return pht( + '%s updated the description for this credential.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the description for credential %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO CREDENTIAL DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialDestroyTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialDestroyTransaction.php new file mode 100644 index 0000000000..3983fd0bbc --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialDestroyTransaction.php @@ -0,0 +1,52 @@ +getIsDestroyed(); + } + + public function applyInternalEffects($object, $value) { + $is_destroyed = $value; + $object->setIsDestroyed($is_destroyed); + if ($is_destroyed) { + $secret_id = $object->getSecretID(); + if ($secret_id) { + $this->destroySecret($secret_id); + $object->setSecretID(null); + } + } + } + + public function shouldHide() { + $new = $this->getNewValue(); + if (!$new) { + return true; + } + } + + public function getTitle() { + return pht( + '%s destroyed the secret for this credential.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s destroyed the secret for credential %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-ban'; + } + + public function getColor() { + return 'red'; + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialLockTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialLockTransaction.php new file mode 100644 index 0000000000..64eba1da71 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialLockTransaction.php @@ -0,0 +1,41 @@ +getIsLocked(); + } + + public function applyInternalEffects($object, $value) { + $object->setIsLocked((int)$value); + } + + public function shouldHide() { + $new = $this->getNewValue(); + if ($new === null) { + return true; + } + return false; + } + + public function getTitle() { + return pht( + '%s locked this credential.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s locked credential %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-lock'; + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialLookedAtTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialLookedAtTransaction.php new file mode 100644 index 0000000000..3d8cb36f31 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialLookedAtTransaction.php @@ -0,0 +1,33 @@ +renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s examined the secret plaintext for credential %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-eye'; + } + + public function getColor() { + return 'blue'; + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialNameTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialNameTransaction.php new file mode 100644 index 0000000000..1afac71395 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialNameTransaction.php @@ -0,0 +1,71 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s created this credential.', + $this->renderAuthor()); + } else { + return pht( + '%s renamed this credential from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s renamed %s credential %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Credentials must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The name can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialSecretIDTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialSecretIDTransaction.php new file mode 100644 index 0000000000..8089943a96 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialSecretIDTransaction.php @@ -0,0 +1,52 @@ +getSecretID(); + } + + public function applyInternalEffects($object, $value) { + $old_id = $object->getSecretID(); + if ($old_id) { + $this->destroySecret($old_id); + } + $object->setSecretID($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if (!$old) { + return pht( + '%s attached a new secret to this credential.', + $this->renderAuthor()); + } else { + return pht( + '%s updated the secret for this credential.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s attached a new secret to %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s updated the secret for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + return 'fa-key'; + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialTransactionType.php b/src/applications/passphrase/xaction/PassphraseCredentialTransactionType.php new file mode 100644 index 0000000000..4e6aadb697 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialTransactionType.php @@ -0,0 +1,15 @@ +establishConnection('w'), + 'DELETE FROM %T WHERE id = %d', + $table->getTableName(), + $secret_id); + } + +} diff --git a/src/applications/passphrase/xaction/PassphraseCredentialUsernameTransaction.php b/src/applications/passphrase/xaction/PassphraseCredentialUsernameTransaction.php new file mode 100644 index 0000000000..fe07bd2990 --- /dev/null +++ b/src/applications/passphrase/xaction/PassphraseCredentialUsernameTransaction.php @@ -0,0 +1,56 @@ +getUsername(); + } + + public function applyInternalEffects($object, $value) { + $object->setUsername($value); + } + + public function getTitle() { + return pht( + '%s set the username for this credential to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s set the username for credential %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $credential_type = $object->getImplementation(); + if ($credential_type->shouldRequireUsername()) { + if ($this->isEmptyTextTransaction($object->getUsername(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('This credential must have a username.')); + } + } + + $max_length = $object->getColumnMaximumByteLength('username'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The username can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/people/application/PhabricatorPeopleApplication.php b/src/applications/people/application/PhabricatorPeopleApplication.php index f2bb70e423..dde82f1d3a 100644 --- a/src/applications/people/application/PhabricatorPeopleApplication.php +++ b/src/applications/people/application/PhabricatorPeopleApplication.php @@ -70,6 +70,8 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication { 'PhabricatorPeopleProfileTasksController', 'commits/(?P[1-9]\d*)/' => 'PhabricatorPeopleProfileCommitsController', + 'revisions/(?P[1-9]\d*)/' => + 'PhabricatorPeopleProfileRevisionsController', 'picture/(?P[1-9]\d*)/' => 'PhabricatorPeopleProfilePictureController', 'manage/(?P[1-9]\d*)/' => diff --git a/src/applications/people/config/PhabricatorUserConfigOptions.php b/src/applications/people/config/PhabricatorUserConfigOptions.php index 79be912699..3ef736c8ca 100644 --- a/src/applications/people/config/PhabricatorUserConfigOptions.php +++ b/src/applications/people/config/PhabricatorUserConfigOptions.php @@ -43,7 +43,7 @@ final class PhabricatorUserConfigOptions $this->newOption('user.fields', $custom_field_type, $default) ->setCustomData(id(new PhabricatorUser())->getCustomFieldBaseClass()) ->setDescription(pht('Select and reorder user profile fields.')), - $this->newOption('user.custom-field-definitions', 'map', array()) + $this->newOption('user.custom-field-definitions', 'wild', array()) ->setDescription(pht('Add new simple fields to user profiles.')), $this->newOption('user.require-real-name', 'bool', true) ->setDescription(pht('Always require real name for user profiles.')) diff --git a/src/applications/people/controller/PhabricatorPeopleCreateController.php b/src/applications/people/controller/PhabricatorPeopleCreateController.php index 04b2f4a8a4..c0b232645b 100644 --- a/src/applications/people/controller/PhabricatorPeopleCreateController.php +++ b/src/applications/people/controller/PhabricatorPeopleCreateController.php @@ -92,13 +92,9 @@ final class PhabricatorPeopleCreateController $crumbs->addTextCrumb($title); $crumbs->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-user'); - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('User')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); $guidance_context = new PhabricatorPeopleCreateGuidanceContext(); @@ -109,7 +105,6 @@ final class PhabricatorPeopleCreateController ->newInfoView(); $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter( array( $guidance, diff --git a/src/applications/people/controller/PhabricatorPeopleNewController.php b/src/applications/people/controller/PhabricatorPeopleNewController.php index 60f9f47b5d..9f52cf567a 100644 --- a/src/applications/people/controller/PhabricatorPeopleNewController.php +++ b/src/applications/people/controller/PhabricatorPeopleNewController.php @@ -132,12 +132,15 @@ final class PhabricatorPeopleNewController ->setUser($admin); if ($is_bot) { + $title = pht('Create New Bot'); $form->appendRemarkupInstructions( pht('You are creating a new **bot** user account.')); } else if ($is_list) { + $title = pht('Create New Mailing List'); $form->appendRemarkupInstructions( pht('You are creating a new **mailing list** user account.')); } else { + $title = pht('Create New User'); $form->appendRemarkupInstructions( pht('You are creating a new **standard** user account.')); } @@ -205,25 +208,17 @@ final class PhabricatorPeopleNewController "`bot@yourcompany.com` if you prefer.")); } - - $title = pht('Create New User'); - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('User')) + ->setHeaderText($title) ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); $crumbs->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-user'); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter($box); return $this->newPage() diff --git a/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php b/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php index f2adf4b23d..c18c5f4d96 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php @@ -13,10 +13,6 @@ final class PhabricatorPeopleProfileCommitsController ->needProfile(true) ->needProfileImage(true) ->needAvailability(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - )) ->executeOne(); if (!$user) { return new Aphront404Response(); @@ -60,26 +56,15 @@ final class PhabricatorPeopleProfileCommitsController $commits = id(new DiffusionCommitQuery()) ->setViewer($viewer) ->withAuthorPHIDs(array($user->getPHID())) - ->needAuditRequests(true) ->needCommitData(true) - ->needDrafts(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - )) ->setLimit(100) ->execute(); - $list = id(new PhabricatorAuditListView()) + $list = id(new DiffusionCommitListView()) ->setViewer($viewer) ->setCommits($commits) ->setNoDataString(pht('No recent commits.')); - $view = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Recent Commits')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($list); - - return $view; + return $list; } } diff --git a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php index cff833aa51..5cab924255 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php @@ -190,7 +190,7 @@ final class PhabricatorPeopleProfilePictureController $button = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $spec['tip'], diff --git a/src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php b/src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php new file mode 100644 index 0000000000..adb2a60e5d --- /dev/null +++ b/src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php @@ -0,0 +1,82 @@ +getViewer(); + $id = $request->getURIData('id'); + + $user = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->needProfile(true) + ->needProfileImage(true) + ->needAvailability(true) + ->executeOne(); + if (!$user) { + return new Aphront404Response(); + } + + $class = 'PhabricatorDifferentialApplication'; + if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) { + return new Aphront404Response(); + } + + $this->setUser($user); + $title = array(pht('Recent Revisions'), $user->getUsername()); + $header = $this->buildProfileHeader(); + $commits = $this->buildRevisionsView($user); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Recent Revisions')); + $crumbs->setBorder(true); + + $nav = $this->getProfileMenu(); + $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_REVISIONS); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->addClass('project-view-home') + ->addClass('project-view-people-home') + ->setFooter(array( + $commits, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($nav) + ->appendChild($view); + } + + private function buildRevisionsView(PhabricatorUser $user) { + $viewer = $this->getViewer(); + + $revisions = id(new DifferentialRevisionQuery()) + ->setViewer($viewer) + ->withAuthors(array($user->getPHID())) + ->needFlags(true) + ->needDrafts(true) + ->needReviewers(true) + ->setLimit(100) + ->execute(); + + $list = id(new DifferentialRevisionListView()) + ->setUser($viewer) + ->setNoBox(true) + ->setRevisions($revisions) + ->setNoDataString(pht('No recent revisions.')); + + $object_phids = $list->getRequiredHandlePHIDs(); + $handles = $this->loadViewerHandles($object_phids); + $list->setHandles($handles); + + $view = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Recent Revisions')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($list); + + return $view; + } +} diff --git a/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php b/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php index 52c88d89fa..b843af8fc7 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php @@ -13,10 +13,6 @@ final class PhabricatorPeopleProfileTasksController ->needProfile(true) ->needProfileImage(true) ->needAvailability(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - )) ->executeOne(); if (!$user) { return new Aphront404Response(); @@ -65,6 +61,7 @@ final class PhabricatorPeopleProfileTasksController ->withStatuses($open) ->needProjectPHIDs(true) ->setLimit(100) + ->setGroupBy(ManiphestTaskQuery::GROUP_PRIORITY) ->execute(); $handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index 9db106081d..5dce872c4b 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -28,8 +28,21 @@ final class PhabricatorPeopleProfileViewController $name = $user->getUsername(); $feed = $this->buildPeopleFeed($user, $viewer); + + $view_all = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon( + id(new PHUIIconView()) + ->setIcon('fa-list-ul')) + ->setText(pht('View All')) + ->setHref('/feed/?userPHIDs='.$user->getPHID()); + + $feed_header = id(new PHUIHeaderView()) + ->setHeader(pht('Recent Activity')) + ->addActionLink($view_all); + $feed = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Recent Activity')) + ->setHeader($feed_header) ->addClass('project-view-feed') ->appendChild($feed); @@ -229,7 +242,7 @@ final class PhabricatorPeopleProfileViewController $viewer) { $query = new PhabricatorFeedQuery(); - $query->setFilterPHIDs( + $query->withFilterPHIDs( array( $user->getPHID(), )); diff --git a/src/applications/people/engine/PhabricatorPeopleProfileMenuEngine.php b/src/applications/people/engine/PhabricatorPeopleProfileMenuEngine.php index e5f25eb4a0..6d6d239f5b 100644 --- a/src/applications/people/engine/PhabricatorPeopleProfileMenuEngine.php +++ b/src/applications/people/engine/PhabricatorPeopleProfileMenuEngine.php @@ -9,6 +9,7 @@ final class PhabricatorPeopleProfileMenuEngine const ITEM_BADGES = 'people.badges'; const ITEM_TASKS = 'people.tasks'; const ITEM_COMMITS = 'people.commits'; + const ITEM_REVISIONS = 'people.revisions'; protected function isMenuEngineConfigurable() { return false; @@ -52,6 +53,16 @@ final class PhabricatorPeopleProfileMenuEngine ->setMenuItemKey(PhabricatorPeopleTasksProfileMenuItem::MENUITEMKEY); } + $have_differential = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorDifferentialApplication', + $viewer); + if ($have_differential) { + $items[] = $this->newItem() + ->setBuiltinKey(self::ITEM_REVISIONS) + ->setMenuItemKey( + PhabricatorPeopleRevisionsProfileMenuItem::MENUITEMKEY); + } + $have_diffusion = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorDiffusionApplication', $viewer); diff --git a/src/applications/people/markup/PhabricatorMentionRemarkupRule.php b/src/applications/people/markup/PhabricatorMentionRemarkupRule.php index 2de15702ca..4f7fab6267 100644 --- a/src/applications/people/markup/PhabricatorMentionRemarkupRule.php +++ b/src/applications/people/markup/PhabricatorMentionRemarkupRule.php @@ -150,7 +150,9 @@ final class PhabricatorMentionRemarkupRule extends PhutilRemarkupRule { $tag->addClass('phabricator-remarkup-mention-nopermission'); } - if (!$user->isResponsive()) { + if ($user->getIsDisabled()) { + $tag->setDotColor(PHUITagView::COLOR_GREY); + } else if (!$user->isResponsive()) { $tag->setDotColor(PHUITagView::COLOR_VIOLET); } else { if ($user->getAwayUntil()) { diff --git a/src/applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php b/src/applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php new file mode 100644 index 0000000000..499fc1d7f4 --- /dev/null +++ b/src/applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php @@ -0,0 +1,59 @@ +getMenuItemProperty('name'); + + if (strlen($name)) { + return $name; + } + + return $this->getDefaultName(); + } + + public function buildEditEngineFields( + PhabricatorProfileMenuItemConfiguration $config) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setPlaceholder($this->getDefaultName()) + ->setValue($config->getMenuItemProperty('name')), + ); + } + + protected function newNavigationMenuItems( + PhabricatorProfileMenuItemConfiguration $config) { + + $user = $config->getProfileObject(); + $id = $user->getID(); + + $item = $this->newItem() + ->setHref("/people/revisions/{$id}/") + ->setName($this->getDisplayName($config)) + ->setIcon('fa-gear'); + + return array( + $item, + ); + } + +} diff --git a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php index b2a456cd51..f0512e91f1 100644 --- a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php +++ b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php @@ -61,7 +61,9 @@ final class PhabricatorPeopleUserPHIDType extends PhabricatorPHIDType { } $availability = null; - if (!$user->isResponsive()) { + if ($user->getIsDisabled()) { + $availability = PhabricatorObjectHandle::AVAILABILITY_DISABLED; + } else if (!$user->isResponsive()) { $availability = PhabricatorObjectHandle::AVAILABILITY_NOEMAIL; } else { $until = $user->getAwayUntil(); diff --git a/src/applications/people/query/PhabricatorPeopleSearchEngine.php b/src/applications/people/query/PhabricatorPeopleSearchEngine.php index e800a8ee1a..fa363c4428 100644 --- a/src/applications/people/query/PhabricatorPeopleSearchEngine.php +++ b/src/applications/people/query/PhabricatorPeopleSearchEngine.php @@ -265,6 +265,7 @@ final class PhabricatorPeopleSearchEngine if ($user->getIsDisabled()) { $item->addIcon('fa-ban', pht('Disabled')); + $item->setDisabled(true); } if (!$is_approval) { diff --git a/src/applications/people/search/PhabricatorUserFerretEngine.php b/src/applications/people/search/PhabricatorUserFerretEngine.php new file mode 100644 index 0000000000..c29d4001db --- /dev/null +++ b/src/applications/people/search/PhabricatorUserFerretEngine.php @@ -0,0 +1,25 @@ +setType(PHUITagView::TYPE_SHADE); if ($tag_shade !== null) { - $tag->setShade($tag_shade); + $tag->setColor($tag_shade); } $body = array(); diff --git a/src/applications/phame/application/PhabricatorPhameApplication.php b/src/applications/phame/application/PhabricatorPhameApplication.php index 677872ba1e..f9ee2831b2 100644 --- a/src/applications/phame/application/PhabricatorPhameApplication.php +++ b/src/applications/phame/application/PhabricatorPhameApplication.php @@ -6,6 +6,10 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { return pht('Phame'); } + public function getMenuName() { + return pht('Blogs'); + } + public function getBaseURI() { return '/phame/'; } @@ -15,7 +19,7 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { } public function getShortDescription() { - return pht('Blog'); + return pht('Internal and External Blogs'); } public function getTitleGlyph() { diff --git a/src/applications/phame/controller/blog/PhameBlogArchiveController.php b/src/applications/phame/controller/blog/PhameBlogArchiveController.php index 4c8d4a4a63..8b63bad9ff 100644 --- a/src/applications/phame/controller/blog/PhameBlogArchiveController.php +++ b/src/applications/phame/controller/blog/PhameBlogArchiveController.php @@ -32,7 +32,7 @@ final class PhameBlogArchiveController $xactions = array(); $xactions[] = id(new PhameBlogTransaction()) - ->setTransactionType(PhameBlogTransaction::TYPE_STATUS) + ->setTransactionType(PhameBlogStatusTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); id(new PhameBlogEditor()) diff --git a/src/applications/phame/controller/blog/PhameBlogHeaderPictureController.php b/src/applications/phame/controller/blog/PhameBlogHeaderPictureController.php index ab026fecbf..0106b9571d 100644 --- a/src/applications/phame/controller/blog/PhameBlogHeaderPictureController.php +++ b/src/applications/phame/controller/blog/PhameBlogHeaderPictureController.php @@ -61,7 +61,7 @@ final class PhameBlogHeaderPictureController $xactions = array(); $xactions[] = id(new PhameBlogTransaction()) - ->setTransactionType(PhameBlogTransaction::TYPE_HEADERIMAGE) + ->setTransactionType(PhameBlogHeaderImageTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new PhameBlogEditor()) diff --git a/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php b/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php index 3368046414..4e69034253 100644 --- a/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php +++ b/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php @@ -76,7 +76,8 @@ final class PhameBlogProfilePictureController $xactions = array(); $xactions[] = id(new PhameBlogTransaction()) - ->setTransactionType(PhameBlogTransaction::TYPE_PROFILEIMAGE) + ->setTransactionType( + PhameBlogProfileImageTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new PhameBlogEditor()) @@ -132,7 +133,7 @@ final class PhameBlogProfilePictureController $button = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $spec['tip'], diff --git a/src/applications/phame/controller/post/PhamePostArchiveController.php b/src/applications/phame/controller/post/PhamePostArchiveController.php index 5a3b32944a..093e7019bf 100644 --- a/src/applications/phame/controller/post/PhamePostArchiveController.php +++ b/src/applications/phame/controller/post/PhamePostArchiveController.php @@ -26,7 +26,7 @@ final class PhamePostArchiveController extends PhamePostController { $new_value = PhameConstants::VISIBILITY_ARCHIVED; $xactions[] = id(new PhamePostTransaction()) - ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) + ->setTransactionType(PhamePostVisibilityTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); id(new PhamePostEditor()) @@ -42,8 +42,9 @@ final class PhamePostArchiveController extends PhamePostController { $title = pht('Archive Post'); $body = pht( - 'This post will revert to archived status and no longer be visible '. - 'to other users or members of this blog.'); + 'If you archive this post, it will only be visible to users who can '. + 'edit %s.', + $viewer->renderHandle($post->getBlogPHID())); $button = pht('Archive Post'); return $this->newDialog() diff --git a/src/applications/phame/controller/post/PhamePostHeaderPictureController.php b/src/applications/phame/controller/post/PhamePostHeaderPictureController.php index 2e60c9ba71..60075cc077 100644 --- a/src/applications/phame/controller/post/PhamePostHeaderPictureController.php +++ b/src/applications/phame/controller/post/PhamePostHeaderPictureController.php @@ -61,7 +61,7 @@ final class PhamePostHeaderPictureController $xactions = array(); $xactions[] = id(new PhamePostTransaction()) - ->setTransactionType(PhamePostTransaction::TYPE_HEADERIMAGE) + ->setTransactionType(PhamePostHeaderImageTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new PhamePostEditor()) diff --git a/src/applications/phame/controller/post/PhamePostMoveController.php b/src/applications/phame/controller/post/PhamePostMoveController.php index 3e09a9ba2d..d088b260b5 100644 --- a/src/applications/phame/controller/post/PhamePostMoveController.php +++ b/src/applications/phame/controller/post/PhamePostMoveController.php @@ -28,7 +28,7 @@ final class PhamePostMoveController extends PhamePostController { $xactions = array(); $xactions[] = id(new PhamePostTransaction()) - ->setTransactionType(PhamePostTransaction::TYPE_BLOG) + ->setTransactionType(PhamePostBlogTransaction::TRANSACTIONTYPE) ->setNewValue($v_blog); $editor = id(new PhamePostEditor()) diff --git a/src/applications/phame/controller/post/PhamePostPublishController.php b/src/applications/phame/controller/post/PhamePostPublishController.php index 567099f0d4..70989082bd 100644 --- a/src/applications/phame/controller/post/PhamePostPublishController.php +++ b/src/applications/phame/controller/post/PhamePostPublishController.php @@ -34,7 +34,7 @@ final class PhamePostPublishController extends PhamePostController { } $xactions[] = id(new PhamePostTransaction()) - ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) + ->setTransactionType(PhamePostVisibilityTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); id(new PhamePostEditor()) diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index a73876a197..63adedb7ae 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -46,8 +46,10 @@ final class PhamePostViewController ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setTitle(pht('Draft Post')) ->appendChild( - pht('Only you can see this draft until you publish it. '. - 'Use "Publish" to publish this post.'))); + pht( + 'This is a draft, and is only visible to you and other users '. + 'who can edit %s. Use "Publish" to publish this post.', + $viewer->renderHandle($post->getBlogPHID())))); } if ($post->isArchived()) { @@ -56,8 +58,10 @@ final class PhamePostViewController ->setSeverity(PHUIInfoView::SEVERITY_ERROR) ->setTitle(pht('Archived Post')) ->appendChild( - pht('Only you can see this archived post until you publish it. '. - 'Use "Publish" to publish this post.'))); + pht( + 'This post has been archived, and is only visible to you and '. + 'other users who can edit %s.', + $viewer->renderHandle($post->getBlogPHID())))); } if (!$post->getBlog()) { diff --git a/src/applications/phame/editor/PhameBlogEditEngine.php b/src/applications/phame/editor/PhameBlogEditEngine.php index 7d6511d55c..9b6be308c4 100644 --- a/src/applications/phame/editor/PhameBlogEditEngine.php +++ b/src/applications/phame/editor/PhameBlogEditEngine.php @@ -75,7 +75,8 @@ final class PhameBlogEditEngine ->setDescription(pht('Blog name.')) ->setConduitDescription(pht('Retitle the blog.')) ->setConduitTypeDescription(pht('New blog title.')) - ->setTransactionType(PhameBlogTransaction::TYPE_NAME) + ->setTransactionType(PhameBlogNameTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) ->setValue($object->getName()), id(new PhabricatorTextEditField()) ->setKey('subtitle') @@ -83,7 +84,7 @@ final class PhameBlogEditEngine ->setDescription(pht('Blog subtitle.')) ->setConduitDescription(pht('Change the blog subtitle.')) ->setConduitTypeDescription(pht('New blog subtitle.')) - ->setTransactionType(PhameBlogTransaction::TYPE_SUBTITLE) + ->setTransactionType(PhameBlogSubtitleTransaction::TRANSACTIONTYPE) ->setValue($object->getSubtitle()), id(new PhabricatorRemarkupEditField()) ->setKey('description') @@ -91,7 +92,7 @@ final class PhameBlogEditEngine ->setDescription(pht('Blog description.')) ->setConduitDescription(pht('Change the blog description.')) ->setConduitTypeDescription(pht('New blog description.')) - ->setTransactionType(PhameBlogTransaction::TYPE_DESCRIPTION) + ->setTransactionType(PhameBlogDescriptionTransaction::TRANSACTIONTYPE) ->setValue($object->getDescription()), id(new PhabricatorTextEditField()) ->setKey('domainFullURI') @@ -104,7 +105,7 @@ final class PhameBlogEditEngine ->setConduitDescription(pht('Change the blog full domain URI.')) ->setConduitTypeDescription(pht('New blog full domain URI.')) ->setValue($object->getDomainFullURI()) - ->setTransactionType(PhameBlogTransaction::TYPE_FULLDOMAIN), + ->setTransactionType(PhameBlogFullDomainTransaction::TRANSACTIONTYPE), id(new PhabricatorTextEditField()) ->setKey('parentSite') ->setLabel(pht('Parent Site Name')) @@ -112,7 +113,7 @@ final class PhameBlogEditEngine ->setConduitDescription(pht('Change the blog parent site name.')) ->setConduitTypeDescription(pht('New blog parent site name.')) ->setValue($object->getParentSite()) - ->setTransactionType(PhameBlogTransaction::TYPE_PARENTSITE), + ->setTransactionType(PhameBlogParentSiteTransaction::TRANSACTIONTYPE), id(new PhabricatorTextEditField()) ->setKey('parentDomain') ->setLabel(pht('Parent Site URI')) @@ -120,11 +121,11 @@ final class PhameBlogEditEngine ->setConduitDescription(pht('Change the blog parent domain.')) ->setConduitTypeDescription(pht('New blog parent domain.')) ->setValue($object->getParentDomain()) - ->setTransactionType(PhameBlogTransaction::TYPE_PARENTDOMAIN), + ->setTransactionType(PhameBlogParentDomainTransaction::TRANSACTIONTYPE), id(new PhabricatorSelectEditField()) ->setKey('status') ->setLabel(pht('Status')) - ->setTransactionType(PhameBlogTransaction::TYPE_STATUS) + ->setTransactionType(PhameBlogStatusTransaction::TRANSACTIONTYPE) ->setIsConduitOnly(true) ->setOptions(PhameBlog::getStatusNameMap()) ->setDescription(pht('Active or archived status.')) diff --git a/src/applications/phame/editor/PhameBlogEditor.php b/src/applications/phame/editor/PhameBlogEditor.php index 14f63f51ac..f30a74065e 100644 --- a/src/applications/phame/editor/PhameBlogEditor.php +++ b/src/applications/phame/editor/PhameBlogEditor.php @@ -11,251 +11,22 @@ final class PhameBlogEditor return pht('Phame Blogs'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this blog.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); - - $types[] = PhameBlogTransaction::TYPE_NAME; - $types[] = PhameBlogTransaction::TYPE_SUBTITLE; - $types[] = PhameBlogTransaction::TYPE_DESCRIPTION; - $types[] = PhameBlogTransaction::TYPE_FULLDOMAIN; - $types[] = PhameBlogTransaction::TYPE_PARENTSITE; - $types[] = PhameBlogTransaction::TYPE_PARENTDOMAIN; - $types[] = PhameBlogTransaction::TYPE_STATUS; - $types[] = PhameBlogTransaction::TYPE_HEADERIMAGE; - $types[] = PhameBlogTransaction::TYPE_PROFILEIMAGE; - $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhameBlogTransaction::TYPE_NAME: - return $object->getName(); - case PhameBlogTransaction::TYPE_SUBTITLE: - return $object->getSubtitle(); - case PhameBlogTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case PhameBlogTransaction::TYPE_FULLDOMAIN: - return $object->getDomainFullURI(); - case PhameBlogTransaction::TYPE_PARENTSITE: - return $object->getParentSite(); - case PhameBlogTransaction::TYPE_PARENTDOMAIN: - return $object->getParentDomain(); - case PhameBlogTransaction::TYPE_PROFILEIMAGE: - return $object->getProfileImagePHID(); - case PhameBlogTransaction::TYPE_HEADERIMAGE: - return $object->getHeaderImagePHID(); - case PhameBlogTransaction::TYPE_STATUS: - return $object->getStatus(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhameBlogTransaction::TYPE_NAME: - case PhameBlogTransaction::TYPE_SUBTITLE: - case PhameBlogTransaction::TYPE_DESCRIPTION: - case PhameBlogTransaction::TYPE_STATUS: - case PhameBlogTransaction::TYPE_PARENTSITE: - case PhameBlogTransaction::TYPE_PARENTDOMAIN: - case PhameBlogTransaction::TYPE_PROFILEIMAGE: - case PhameBlogTransaction::TYPE_HEADERIMAGE: - return $xaction->getNewValue(); - case PhameBlogTransaction::TYPE_FULLDOMAIN: - $domain = $xaction->getNewValue(); - if (!strlen($xaction->getNewValue())) { - return null; - } - return $domain; - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhameBlogTransaction::TYPE_NAME: - return $object->setName($xaction->getNewValue()); - case PhameBlogTransaction::TYPE_SUBTITLE: - return $object->setSubtitle($xaction->getNewValue()); - case PhameBlogTransaction::TYPE_DESCRIPTION: - return $object->setDescription($xaction->getNewValue()); - case PhameBlogTransaction::TYPE_FULLDOMAIN: - $new_value = $xaction->getNewValue(); - if (strlen($new_value)) { - $uri = new PhutilURI($new_value); - $domain = $uri->getDomain(); - $object->setDomain($domain); - } else { - $object->setDomain(null); - } - $object->setDomainFullURI($new_value); - return; - case PhameBlogTransaction::TYPE_PROFILEIMAGE: - return $object->setProfileImagePHID($xaction->getNewValue()); - case PhameBlogTransaction::TYPE_HEADERIMAGE: - return $object->setHeaderImagePHID($xaction->getNewValue()); - case PhameBlogTransaction::TYPE_STATUS: - return $object->setStatus($xaction->getNewValue()); - case PhameBlogTransaction::TYPE_PARENTSITE: - return $object->setParentSite($xaction->getNewValue()); - case PhameBlogTransaction::TYPE_PARENTDOMAIN: - return $object->setParentDomain($xaction->getNewValue()); - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhameBlogTransaction::TYPE_NAME: - case PhameBlogTransaction::TYPE_SUBTITLE: - case PhameBlogTransaction::TYPE_DESCRIPTION: - case PhameBlogTransaction::TYPE_FULLDOMAIN: - case PhameBlogTransaction::TYPE_PARENTSITE: - case PhameBlogTransaction::TYPE_PARENTDOMAIN: - case PhameBlogTransaction::TYPE_HEADERIMAGE: - case PhameBlogTransaction::TYPE_PROFILEIMAGE: - case PhameBlogTransaction::TYPE_STATUS: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - - switch ($type) { - case PhameBlogTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - - foreach ($xactions as $xaction) { - $new = $xaction->getNewValue(); - if (phutil_utf8_strlen($new) > 64) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'The selected blog title is too long. The maximum length '. - 'of a blog title is 64 characters.'), - $xaction); - } - } - break; - case PhameBlogTransaction::TYPE_SUBTITLE: - foreach ($xactions as $xaction) { - $new = $xaction->getNewValue(); - if (phutil_utf8_strlen($new) > 64) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'The selected blog subtitle is too long. The maximum length '. - 'of a blog subtitle is 64 characters.'), - $xaction); - } - } - break; - case PhameBlogTransaction::TYPE_PARENTDOMAIN: - if (!$xactions) { - continue; - } - $parent_domain = last($xactions)->getNewValue(); - if (empty($parent_domain)) { - continue; - } - try { - PhabricatorEnv::requireValidRemoteURIForLink($parent_domain); - } catch (Exception $ex) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid URI'), - pht('Parent Domain must be set to a valid Remote URI.'), - nonempty(last($xactions), null)); - $errors[] = $error; - } - break; - case PhameBlogTransaction::TYPE_FULLDOMAIN: - if (!$xactions) { - continue; - } - $custom_domain = last($xactions)->getNewValue(); - if (empty($custom_domain)) { - continue; - } - list($error_label, $error_text) = - $object->validateCustomDomain($custom_domain); - if ($error_label) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - $error_label, - $error_text, - nonempty(last($xactions), null)); - $errors[] = $error; - } - if ($object->getViewPolicy() != PhabricatorPolicies::POLICY_PUBLIC) { - $error_text = pht( - 'For custom domains to work, the blog must have a view policy of '. - 'public.'); - $error = new PhabricatorApplicationTransactionValidationError( - PhabricatorTransactions::TYPE_VIEW_POLICY, - pht('Invalid Policy'), - $error_text, - nonempty(last($xactions), null)); - $errors[] = $error; - } - $domain = new PhutilURI($custom_domain); - $domain = $domain->getDomain(); - $duplicate_blog = id(new PhameBlogQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withDomain($domain) - ->executeOne(); - if ($duplicate_blog && $duplicate_blog->getID() != $object->getID()) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Not Unique'), - pht('Domain must be unique; another blog already has this domain.'), - nonempty(last($xactions), null)); - $errors[] = $error; - } - - break; - } - return $errors; - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/phame/editor/PhamePostEditEngine.php b/src/applications/phame/editor/PhamePostEditEngine.php index e80c1f6d0d..04738beb80 100644 --- a/src/applications/phame/editor/PhamePostEditEngine.php +++ b/src/applications/phame/editor/PhamePostEditEngine.php @@ -84,7 +84,7 @@ final class PhamePostEditEngine pht('Choose a blog to create a post on (or move a post to).')) ->setConduitTypeDescription(pht('PHID of the blog.')) ->setAliases(array('blogPHID')) - ->setTransactionType(PhamePostTransaction::TYPE_BLOG) + ->setTransactionType(PhamePostBlogTransaction::TRANSACTIONTYPE) ->setHandleParameterType(new AphrontPHIDListHTTPParameterType()) ->setSingleValue($blog_phid) ->setIsReorderable(false) @@ -97,7 +97,8 @@ final class PhamePostEditEngine ->setDescription(pht('Post title.')) ->setConduitDescription(pht('Retitle the post.')) ->setConduitTypeDescription(pht('New post title.')) - ->setTransactionType(PhamePostTransaction::TYPE_TITLE) + ->setTransactionType(PhamePostTitleTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) ->setValue($object->getTitle()), id(new PhabricatorTextEditField()) ->setKey('subtitle') @@ -105,7 +106,7 @@ final class PhamePostEditEngine ->setDescription(pht('Post subtitle.')) ->setConduitDescription(pht('Change the post subtitle.')) ->setConduitTypeDescription(pht('New post subtitle.')) - ->setTransactionType(PhamePostTransaction::TYPE_SUBTITLE) + ->setTransactionType(PhamePostSubtitleTransaction::TRANSACTIONTYPE) ->setValue($object->getSubtitle()), id(new PhabricatorSelectEditField()) ->setKey('visibility') @@ -113,7 +114,7 @@ final class PhamePostEditEngine ->setDescription(pht('Post visibility.')) ->setConduitDescription(pht('Change post visibility.')) ->setConduitTypeDescription(pht('New post visibility constant.')) - ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) + ->setTransactionType(PhamePostVisibilityTransaction::TRANSACTIONTYPE) ->setValue($object->getVisibility()) ->setOptions(PhameConstants::getPhamePostStatusMap()), id(new PhabricatorRemarkupEditField()) @@ -122,7 +123,7 @@ final class PhamePostEditEngine ->setDescription(pht('Post body.')) ->setConduitDescription(pht('Change post body.')) ->setConduitTypeDescription(pht('New post body.')) - ->setTransactionType(PhamePostTransaction::TYPE_BODY) + ->setTransactionType(PhamePostBodyTransaction::TRANSACTIONTYPE) ->setValue($object->getBody()) ->setPreviewPanel( id(new PHUIRemarkupPreviewPanel()) diff --git a/src/applications/phame/editor/PhamePostEditor.php b/src/applications/phame/editor/PhamePostEditor.php index 929613fe80..488d7a4938 100644 --- a/src/applications/phame/editor/PhamePostEditor.php +++ b/src/applications/phame/editor/PhamePostEditor.php @@ -11,177 +11,21 @@ final class PhamePostEditor return pht('Phame Posts'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this post.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); - - $types[] = PhamePostTransaction::TYPE_BLOG; - $types[] = PhamePostTransaction::TYPE_TITLE; - $types[] = PhamePostTransaction::TYPE_SUBTITLE; - $types[] = PhamePostTransaction::TYPE_BODY; - $types[] = PhamePostTransaction::TYPE_VISIBILITY; - $types[] = PhamePostTransaction::TYPE_HEADERIMAGE; $types[] = PhabricatorTransactions::TYPE_COMMENT; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhamePostTransaction::TYPE_BLOG: - return $object->getBlogPHID(); - case PhamePostTransaction::TYPE_TITLE: - return $object->getTitle(); - case PhamePostTransaction::TYPE_SUBTITLE: - return $object->getSubtitle(); - case PhamePostTransaction::TYPE_BODY: - return $object->getBody(); - case PhamePostTransaction::TYPE_VISIBILITY: - return $object->getVisibility(); - case PhamePostTransaction::TYPE_HEADERIMAGE: - return $object->getHeaderImagePHID(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhamePostTransaction::TYPE_TITLE: - case PhamePostTransaction::TYPE_SUBTITLE: - case PhamePostTransaction::TYPE_BODY: - case PhamePostTransaction::TYPE_VISIBILITY: - case PhamePostTransaction::TYPE_HEADERIMAGE: - case PhamePostTransaction::TYPE_BLOG: - return $xaction->getNewValue(); - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhamePostTransaction::TYPE_TITLE: - return $object->setTitle($xaction->getNewValue()); - case PhamePostTransaction::TYPE_SUBTITLE: - return $object->setSubtitle($xaction->getNewValue()); - case PhamePostTransaction::TYPE_BODY: - return $object->setBody($xaction->getNewValue()); - case PhamePostTransaction::TYPE_BLOG: - return $object->setBlogPHID($xaction->getNewValue()); - case PhamePostTransaction::TYPE_HEADERIMAGE: - return $object->setHeaderImagePHID($xaction->getNewValue()); - case PhamePostTransaction::TYPE_VISIBILITY: - if ($xaction->getNewValue() == PhameConstants::VISIBILITY_DRAFT) { - $object->setDatePublished(0); - } else if ($xaction->getNewValue() == - PhameConstants::VISIBILITY_ARCHIVED) { - $object->setDatePublished(0); - } else { - $object->setDatePublished(PhabricatorTime::getNow()); - } - return $object->setVisibility($xaction->getNewValue()); - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhamePostTransaction::TYPE_TITLE: - case PhamePostTransaction::TYPE_SUBTITLE: - case PhamePostTransaction::TYPE_BODY: - case PhamePostTransaction::TYPE_VISIBILITY: - case PhamePostTransaction::TYPE_HEADERIMAGE: - case PhamePostTransaction::TYPE_BLOG: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhamePostTransaction::TYPE_TITLE: - $missing = $this->validateIsEmptyTextField( - $object->getTitle(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Title is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case PhamePostTransaction::TYPE_BLOG: - if ($this->getIsNewObject()) { - if (!$xactions) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht( - 'When creating a post, you must specify which blog it '. - 'should belong to.'), - null); - - $error->setIsMissingFieldError(true); - - $errors[] = $error; - break; - } - } - - foreach ($xactions as $xaction) { - $new_phid = $xaction->getNewValue(); - - $blog = id(new PhameBlogQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($new_phid)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->execute(); - - if ($blog) { - continue; - } - - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'The specified blog PHID ("%s") is not valid. You can only '. - 'create a post on (or move a post into) a blog which you '. - 'have permission to see and edit.', - $new_phid), - $xaction); - } - - break; - } - return $errors; - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { @@ -244,7 +88,7 @@ final class PhamePostEditor } else { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhamePostTransaction::TYPE_VISIBILITY: + case PhamePostVisibilityTransaction::TRANSACTIONTYPE: if (!$object->isDraft() && !$object->isArchived()) { $body->addRemarkupSection(null, $object->getBody()); } diff --git a/src/applications/phame/query/PhameBlogQuery.php b/src/applications/phame/query/PhameBlogQuery.php index bd34255bd0..b4018c78eb 100644 --- a/src/applications/phame/query/PhameBlogQuery.php +++ b/src/applications/phame/query/PhameBlogQuery.php @@ -55,28 +55,28 @@ final class PhameBlogQuery extends PhabricatorCursorPagedPolicyAwareQuery { if ($this->statuses !== null) { $where[] = qsprintf( $conn, - 'status IN (%Ls)', + 'b.status IN (%Ls)', $this->statuses); } if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ls)', + 'b.id IN (%Ls)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'b.phid IN (%Ls)', $this->phids); } if ($this->domain !== null) { $where[] = qsprintf( $conn, - 'domain = %s', + 'b.domain = %s', $this->domain); } @@ -143,4 +143,8 @@ final class PhameBlogQuery extends PhabricatorCursorPagedPolicyAwareQuery { return null; } + protected function getPrimaryTableAlias() { + return 'b'; + } + } diff --git a/src/applications/phame/query/PhameBlogSearchEngine.php b/src/applications/phame/query/PhameBlogSearchEngine.php index d006745780..eaef32af1c 100644 --- a/src/applications/phame/query/PhameBlogSearchEngine.php +++ b/src/applications/phame/query/PhameBlogSearchEngine.php @@ -97,8 +97,9 @@ final class PhameBlogSearchEngine $button = id(new PHUIButtonView()) ->setTag('a') ->setText('New Post') - ->setHref($this->getApplicationURI('/post/edit/?blog='.$id)); - $item->setLaunchButton($button); + ->setHref($this->getApplicationURI('/post/edit/?blog='.$id)) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE); + $item->setSideColumn($button); } $list->addItem($item); diff --git a/src/applications/phame/query/PhamePostQuery.php b/src/applications/phame/query/PhamePostQuery.php index 357418cfb3..85ef470cea 100644 --- a/src/applications/phame/query/PhamePostQuery.php +++ b/src/applications/phame/query/PhamePostQuery.php @@ -106,45 +106,45 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery { protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'p.id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'p.phid IN (%Ls)', $this->phids); } - if ($this->bloggerPHIDs) { + if ($this->bloggerPHIDs !== null) { $where[] = qsprintf( $conn, - 'bloggerPHID IN (%Ls)', + 'p.bloggerPHID IN (%Ls)', $this->bloggerPHIDs); } - if ($this->visibility) { + if ($this->visibility !== null) { $where[] = qsprintf( $conn, - 'visibility IN (%Ld)', + 'p.visibility IN (%Ld)', $this->visibility); } if ($this->publishedAfter !== null) { $where[] = qsprintf( $conn, - 'datePublished > %d', + 'p.datePublished > %d', $this->publishedAfter); } if ($this->blogPHIDs !== null) { $where[] = qsprintf( $conn, - 'blogPHID in (%Ls)', + 'p.blogPHID in (%Ls)', $this->blogPHIDs); } @@ -163,6 +163,7 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery { public function getOrderableColumns() { return parent::getOrderableColumns() + array( 'datePublished' => array( + 'table' => $this->getPrimaryTableAlias(), 'column' => 'datePublished', 'type' => 'int', 'reverse' => false, @@ -186,4 +187,8 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery { return null; } + protected function getPrimaryTableAlias() { + return 'p'; + } + } diff --git a/src/applications/phame/search/PhameBlogFerretEngine.php b/src/applications/phame/search/PhameBlogFerretEngine.php new file mode 100644 index 0000000000..76901f915c --- /dev/null +++ b/src/applications/phame/search/PhameBlogFerretEngine.php @@ -0,0 +1,18 @@ + $href), - pht('here'))), - ); + return pht( + 'For custom domains to work, this Phabricator instance must be '. + 'configured to allow the public access policy. Configure this '. + 'setting %s, or ask an administrator to configure this setting. '. + 'The domain can be specified later once this setting has been '. + 'changed.', + phutil_tag( + 'a', + array('href' => $href), + pht('here'))); } return null; @@ -404,4 +393,12 @@ final class PhameBlog extends PhameDAO return new PhameBlogFulltextEngine(); } + +/* -( PhabricatorFerretInterface )----------------------------------------- */ + + + public function newFerretEngine() { + return new PhameBlogFerretEngine(); + } + } diff --git a/src/applications/phame/storage/PhameBlogTransaction.php b/src/applications/phame/storage/PhameBlogTransaction.php index f08c7be688..c605510d7d 100644 --- a/src/applications/phame/storage/PhameBlogTransaction.php +++ b/src/applications/phame/storage/PhameBlogTransaction.php @@ -1,17 +1,7 @@ getOldValue(); - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - if ($old === null) { - return true; - } - } - return parent::shouldHide(); - } - - public function getRequiredHandlePHIDs() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $req_phids = array(); - switch ($this->getTransactionType()) { - case self::TYPE_PROFILEIMAGE: - case self::TYPE_HEADERIMAGE: - $req_phids[] = $old; - $req_phids[] = $new; - break; - } - - return array_merge($req_phids, parent::getRequiredHandlePHIDs()); - } - - public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return 'fa-plus'; - } else { - return 'fa-pencil'; - } - break; - case self::TYPE_DESCRIPTION: - case self::TYPE_FULLDOMAIN: - return 'fa-pencil'; - case self::TYPE_HEADERIMAGE: - return 'fa-image'; - case self::TYPE_PROFILEIMAGE: - return 'fa-star'; - case self::TYPE_STATUS: - if ($new == PhameBlog::STATUS_ARCHIVED) { - return 'fa-ban'; - } else { - return 'fa-check'; - } - break; - } - return parent::getIcon(); - } - - public function getColor() { - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_STATUS: - if ($new == PhameBlog::STATUS_ARCHIVED) { - return 'violet'; - } else { - return 'green'; - } - } - return parent::getColor(); + public function getBaseTransactionClass() { + return 'PhameBlogTransactionType'; } public function getMailTags() { @@ -104,14 +26,14 @@ final class PhameBlogTransaction case PhabricatorTransactions::TYPE_SUBSCRIBERS: $tags[] = self::MAILTAG_SUBSCRIBERS; break; - case self::TYPE_NAME: - case self::TYPE_SUBTITLE: - case self::TYPE_DESCRIPTION: - case self::TYPE_FULLDOMAIN: - case self::TYPE_PARENTSITE: - case self::TYPE_PARENTDOMAIN: - case self::TYPE_PROFILEIMAGE: - case self::TYPE_HEADERIMAGE: + case PhameBlogNameTransaction::TRANSACTIONTYPE: + case PhameBlogSubtitleTransaction::TRANSACTIONTYPE: + case PhameBlogDescriptionTransaction::TRANSACTIONTYPE: + case PhameBlogFullDomainTransaction::TRANSACTIONTYPE: + case PhameBlogParentSiteTransaction::TRANSACTIONTYPE: + case PhameBlogParentDomainTransaction::TRANSACTIONTYPE: + case PhameBlogProfileImageTransaction::TRANSACTIONTYPE: + case PhameBlogHeaderImageTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_DETAILS; break; default: @@ -121,247 +43,4 @@ final class PhameBlogTransaction return $tags; } - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $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( - '%s created this blog.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s updated the blog\'s name to "%s".', - $this->renderHandleLink($author_phid), - $new); - } - break; - case self::TYPE_SUBTITLE: - if ($old === null) { - return pht( - '%s set this blog\'s subtitle to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s updated the blog\'s subtitle to "%s".', - $this->renderHandleLink($author_phid), - $new); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the blog\'s description.', - $this->renderHandleLink($author_phid)); - break; - case self::TYPE_FULLDOMAIN: - return pht( - '%s updated the blog\'s full domain to "%s".', - $this->renderHandleLink($author_phid), - $new); - break; - case self::TYPE_PARENTSITE: - if ($old === null) { - return pht( - '%s set this blog\'s parent site to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s updated the blog\'s parent site to "%s".', - $this->renderHandleLink($author_phid), - $new); - } - break; - case self::TYPE_PARENTDOMAIN: - if ($old === null) { - return pht( - '%s set this blog\'s parent domain to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s updated the blog\'s parent domain to "%s".', - $this->renderHandleLink($author_phid), - $new); - } - break; - case self::TYPE_HEADERIMAGE: - if (!$old) { - return pht( - "%s set this blog's header image to %s.", - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); - } else if (!$new) { - return pht( - "%s removed this blog's header image.", - $this->renderHandleLink($author_phid)); - } else { - return pht( - "%s updated this blog's header image from %s to %s.", - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - break; - case self::TYPE_PROFILEIMAGE: - if (!$old) { - return pht( - "%s set this blog's profile image to %s.", - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new)); - } else if (!$new) { - return pht( - "%s removed this blog's profile image.", - $this->renderHandleLink($author_phid)); - } else { - return pht( - "%s updated this blog's profile image from %s to %s.", - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - break; - case self::TYPE_STATUS: - switch ($new) { - case PhameBlog::STATUS_ACTIVE: - return pht( - '%s published this blog.', - $this->renderHandleLink($author_phid)); - case PhameBlog::STATUS_ARCHIVED: - return pht( - '%s archived this blog.', - $this->renderHandleLink($author_phid)); - } - - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s updated the name for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_SUBTITLE: - if ($old === null) { - return pht( - '%s set the subtitle for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s updated the subtitle for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_FULLDOMAIN: - return pht( - '%s updated the full domain for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_PARENTSITE: - return pht( - '%s updated the parent site for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_PARENTDOMAIN: - return pht( - '%s updated the parent domain for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_HEADERIMAGE: - return pht( - '%s updated the header image for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_PROFILEIMAGE: - return pht( - '%s updated the profile image for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_STATUS: - switch ($new) { - case PhameBlog::STATUS_ACTIVE: - return pht( - '%s published the blog %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case PhameBlog::STATUS_ARCHIVED: - return pht( - '%s archived the blog %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - - } - - return parent::getTitleForFeed(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($this->getOldValue() !== null); - } - - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - return $this->renderTextCorpusChangeDetails( - $viewer, - $old, - $new); - } - - return parent::renderChangeDetails($viewer); - } - } diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index f87a37e7a4..8380a18f4d 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -11,7 +11,8 @@ final class PhamePost extends PhameDAO PhabricatorDestructibleInterface, PhabricatorTokenReceiverInterface, PhabricatorConduitResultInterface, - PhabricatorFulltextInterface { + PhabricatorFulltextInterface, + PhabricatorFerretInterface { const MARKUP_FIELD_BODY = 'markup:body'; const MARKUP_FIELD_SUMMARY = 'markup:summary'; @@ -200,8 +201,9 @@ final class PhamePost extends PhameDAO } public function getPolicy($capability) { - // Draft posts are visible only to the author. Published posts are visible - // to whoever the blog is visible to. + // Draft and archived posts are visible only to the author and other + // users who can edit the blog. Published posts are visible to whoever + // the blog is visible to. switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: @@ -386,4 +388,12 @@ final class PhamePost extends PhameDAO return new PhamePostFulltextEngine(); } + +/* -( PhabricatorFerretInterface )----------------------------------------- */ + + + public function newFerretEngine() { + return new PhamePostFerretEngine(); + } + } diff --git a/src/applications/phame/storage/PhamePostTransaction.php b/src/applications/phame/storage/PhamePostTransaction.php index 6e54aeda6d..6adc640b67 100644 --- a/src/applications/phame/storage/PhamePostTransaction.php +++ b/src/applications/phame/storage/PhamePostTransaction.php @@ -1,14 +1,7 @@ getTransactionType()) { - case self::TYPE_BODY: - $blocks[] = $this->getNewValue(); - break; - } - - return $blocks; - } - - public function shouldHide() { - return parent::shouldHide(); - } - - public function getRequiredHandlePHIDs() { - $phids = parent::getRequiredHandlePHIDs(); - - switch ($this->getTransactionType()) { - case self::TYPE_BLOG: - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - if ($old) { - $phids[] = $old; - } - - if ($new) { - $phids[] = $new; - } - break; - } - - return $phids; - } - - - public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_CREATE: - return 'fa-plus'; - break; - case self::TYPE_HEADERIMAGE: - return 'fa-camera-retro'; - break; - case self::TYPE_VISIBILITY: - if ($new == PhameConstants::VISIBILITY_PUBLISHED) { - return 'fa-globe'; - } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { - return 'fa-ban'; - } else { - return 'fa-eye-slash'; - } - break; - } - return parent::getIcon(); - } - public function getMailTags() { $tags = parent::getMailTags(); @@ -98,9 +34,9 @@ final class PhamePostTransaction case PhabricatorTransactions::TYPE_SUBSCRIBERS: $tags[] = self::MAILTAG_SUBSCRIBERS; break; - case self::TYPE_TITLE: - case self::TYPE_SUBTITLE: - case self::TYPE_BODY: + case PhamePostTitleTransaction::TRANSACTIONTYPE: + case PhamePostSubtitleTransaction::TRANSACTIONTYPE: + case PhamePostBodyTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_CONTENT; break; default: @@ -110,200 +46,4 @@ final class PhamePostTransaction return $tags; } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case PhabricatorTransactions::TYPE_CREATE: - return pht( - '%s authored this post.', - $this->renderHandleLink($author_phid)); - case self::TYPE_BLOG: - return pht( - '%s moved this post from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s authored this post.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s updated the post\'s name to "%s".', - $this->renderHandleLink($author_phid), - $new); - } - break; - case self::TYPE_SUBTITLE: - if ($old === null) { - return pht( - '%s set the post\'s subtitle to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s updated the post\'s subtitle to "%s".', - $this->renderHandleLink($author_phid), - $new); - } - break; - case self::TYPE_BODY: - return pht( - '%s updated the blog post.', - $this->renderHandleLink($author_phid)); - break; - case self::TYPE_HEADERIMAGE: - return pht( - '%s updated the header image.', - $this->renderHandleLink($author_phid)); - break; - case self::TYPE_VISIBILITY: - if ($new == PhameConstants::VISIBILITY_DRAFT) { - return pht( - '%s marked this post as a draft.', - $this->renderHandleLink($author_phid)); - } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { - return pht( - '%s archived this post.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s published this post.', - $this->renderHandleLink($author_phid)); - } - break; - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case PhabricatorTransactions::TYPE_CREATE: - return pht( - '%s authored %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_BLOG: - return pht( - '%s moved post "%s" from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s authored %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s updated the name for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_SUBTITLE: - return pht( - '%s updated the subtitle for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_BODY: - return pht( - '%s updated the blog post %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_HEADERIMAGE: - return pht( - '%s updated the header image for post %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_VISIBILITY: - if ($new == PhameConstants::VISIBILITY_DRAFT) { - return pht( - '%s marked %s as a draft.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { - return pht( - '%s marked %s as archived.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s published %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - } - - return parent::getTitleForFeed(); - } - - public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) { - $old = $this->getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_BODY: - if ($old === null) { - return $this->getNewValue(); - } - break; - } - - return null; - } - - public function getColor() { - switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_CREATE: - return PhabricatorTransactions::COLOR_GREEN; - } - return parent::getColor(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_BODY: - return ($this->getOldValue() !== null); - } - - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - switch ($this->getTransactionType()) { - case self::TYPE_BODY: - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - return $this->renderTextCorpusChangeDetails( - $viewer, - $old, - $new); - } - - return parent::renderChangeDetails($viewer); - } - } diff --git a/src/applications/phame/xaction/PhameBlogDescriptionTransaction.php b/src/applications/phame/xaction/PhameBlogDescriptionTransaction.php new file mode 100644 index 0000000000..2bc12f3998 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogDescriptionTransaction.php @@ -0,0 +1,60 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + '%s updated the description.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO BLOG DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + public function getIcon() { + return 'fa-file-text-o'; + } + +} diff --git a/src/applications/phame/xaction/PhameBlogFullDomainTransaction.php b/src/applications/phame/xaction/PhameBlogFullDomainTransaction.php new file mode 100644 index 0000000000..5287f9cc06 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogFullDomainTransaction.php @@ -0,0 +1,95 @@ +getDomainFullURI(); + } + + public function applyInternalEffects($object, $value) { + if (strlen($value)) { + $uri = new PhutilURI($value); + $domain = $uri->getDomain(); + $object->setDomain($domain); + } else { + $object->setDomain(null); + } + $object->setDomainFullURI($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s set this blog\'s full domain to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s updated the blog\'s full domain from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s set %s blog\'s full domain to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewValue()); + } else { + return pht( + '%s updated %s blog\'s full domain from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$xactions) { + return $errors; + } + + $custom_domain = last($xactions)->getNewValue(); + if (empty($custom_domain)) { + return $errors; + } + + $error_text = $object->validateCustomDomain($custom_domain); + if ($error_text) { + $errors[] = $this->newInvalidError($error_text); + } + + if ($object->getViewPolicy() != PhabricatorPolicies::POLICY_PUBLIC) { + $errors[] = $this->newInvalidError( + pht('For custom domains to work, the blog must have a view policy of '. + 'public. This blog is currently set to "%s".', + $object->getViewPolicy())); + } + + $domain = new PhutilURI($custom_domain); + $domain = $domain->getDomain(); + $duplicate_blog = id(new PhameBlogQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withDomain($domain) + ->executeOne(); + if ($duplicate_blog && $duplicate_blog->getID() != $object->getID()) { + $errors[] = $this->newInvalidError( + pht('Domain must be unique; another blog already has this domain.')); + } + + return $errors; + } + +} diff --git a/src/applications/phame/xaction/PhameBlogHeaderImageTransaction.php b/src/applications/phame/xaction/PhameBlogHeaderImageTransaction.php new file mode 100644 index 0000000000..b328b9aca5 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogHeaderImageTransaction.php @@ -0,0 +1,34 @@ +getHeaderImagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setHeaderImagePHID($value); + } + + public function getTitle() { + return pht( + '%s changed the header image for this blog.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s changed the header image for blog %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-camera'; + } + +} diff --git a/src/applications/phame/xaction/PhameBlogNameTransaction.php b/src/applications/phame/xaction/PhameBlogNameTransaction.php new file mode 100644 index 0000000000..c219ba494b --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogNameTransaction.php @@ -0,0 +1,59 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this blog from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s renamed %s blog from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Blogs must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The name can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + + public function getIcon() { + return 'fa-rss'; + } + +} diff --git a/src/applications/phame/xaction/PhameBlogParentDomainTransaction.php b/src/applications/phame/xaction/PhameBlogParentDomainTransaction.php new file mode 100644 index 0000000000..227c8ce8c3 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogParentDomainTransaction.php @@ -0,0 +1,82 @@ +getParentDomain(); + } + + public function applyInternalEffects($object, $value) { + $object->setParentDomain($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s set this blog\'s parent domain to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s updated the blog\'s parent domain from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s set %s blog\'s parent domain to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewValue()); + } else { + return pht( + '%s updated %s blog\'s parent domain from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$xactions) { + return $errors; + } + + $parent_domain = last($xactions)->getNewValue(); + if (empty($parent_domain)) { + return $errors; + } + + try { + PhabricatorEnv::requireValidRemoteURIForLink($parent_domain); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError( + pht('Parent Domain must be set to a valid Remote URI.')); + } + + $max_length = $object->getColumnMaximumByteLength('parentDomain'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The parent domain can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } +} diff --git a/src/applications/phame/xaction/PhameBlogParentSiteTransaction.php b/src/applications/phame/xaction/PhameBlogParentSiteTransaction.php new file mode 100644 index 0000000000..1b62b98488 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogParentSiteTransaction.php @@ -0,0 +1,66 @@ +getParentSite(); + } + + public function applyInternalEffects($object, $value) { + $object->setParentSite($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s set this blog\'s parent site to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s updated the blog\'s parent site from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s set %s blog\'s parent site to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewValue()); + } else { + return pht( + '%s updated %s blog\'s parent site from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('parentSite'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The parent site can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } +} diff --git a/src/applications/phame/xaction/PhameBlogProfileImageTransaction.php b/src/applications/phame/xaction/PhameBlogProfileImageTransaction.php new file mode 100644 index 0000000000..77acf680fd --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogProfileImageTransaction.php @@ -0,0 +1,34 @@ +getProfileImagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setProfileImagePHID($value); + } + + public function getTitle() { + return pht( + '%s changed the profile image for this blog.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s changed the profile image for blog %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-file-image-o'; + } + +} diff --git a/src/applications/phame/xaction/PhameBlogStatusTransaction.php b/src/applications/phame/xaction/PhameBlogStatusTransaction.php new file mode 100644 index 0000000000..b3366bece7 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogStatusTransaction.php @@ -0,0 +1,55 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + switch ($new) { + case PhameBlog::STATUS_ACTIVE: + return pht( + '%s published this blog.', + $this->renderAuthor()); + case PhameBlog::STATUS_ARCHIVED: + return pht( + '%s archived this blog.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + switch ($new) { + case PhameBlog::STATUS_ACTIVE: + return pht( + '%s published the blog %s.', + $this->renderAuthor(), + $this->renderObject()); + case PhameBlog::STATUS_ARCHIVED: + return pht( + '%s archived the blog %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + if ($new == PhameBlog::STATUS_ARCHIVED) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + +} diff --git a/src/applications/phame/xaction/PhameBlogSubtitleTransaction.php b/src/applications/phame/xaction/PhameBlogSubtitleTransaction.php new file mode 100644 index 0000000000..87788a31f7 --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogSubtitleTransaction.php @@ -0,0 +1,63 @@ +getSubtitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setSubtitle($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s set this blog\'s subtitle to "%s".', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s updated the blog\'s subtitle to "%s".', + $this->renderAuthor(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s set the subtitle for %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s updated the subtitle for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('subtitle'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The subtitle can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/phame/xaction/PhameBlogTransactionType.php b/src/applications/phame/xaction/PhameBlogTransactionType.php new file mode 100644 index 0000000000..cf83633b9f --- /dev/null +++ b/src/applications/phame/xaction/PhameBlogTransactionType.php @@ -0,0 +1,4 @@ +getBlogPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setBlogPHID($value); + } + + public function getTitle() { + return pht( + '%s changed the blog for this post.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s changed the blog for post %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getBlogPHID(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Posts must be attached to a blog.')); + } + + foreach ($xactions as $xaction) { + $new_phid = $xaction->getNewValue(); + + $blog = id(new PhameBlogQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($new_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + + if ($blog) { + continue; + } + + $errors[] = $this->newInvalidError( + pht('The specified blog PHID ("%s") is not valid. You can only '. + 'create a post on (or move a post into) a blog which you '. + 'have permission to see and edit.', + $new_phid)); + } + + return $errors; + } + +} diff --git a/src/applications/phame/xaction/PhamePostBodyTransaction.php b/src/applications/phame/xaction/PhamePostBodyTransaction.php new file mode 100644 index 0000000000..bf34cf73ff --- /dev/null +++ b/src/applications/phame/xaction/PhamePostBodyTransaction.php @@ -0,0 +1,60 @@ +getBody(); + } + + public function applyInternalEffects($object, $value) { + $object->setBody($value); + } + + public function getTitle() { + return pht( + '%s updated the post content.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the post content for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO POST CONTENT'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + public function getIcon() { + return 'fa-file-text-o'; + } + +} diff --git a/src/applications/phame/xaction/PhamePostHeaderImageTransaction.php b/src/applications/phame/xaction/PhamePostHeaderImageTransaction.php new file mode 100644 index 0000000000..2b9631cdc4 --- /dev/null +++ b/src/applications/phame/xaction/PhamePostHeaderImageTransaction.php @@ -0,0 +1,33 @@ +getHeaderImagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setHeaderImagePHID($value); + } + + public function getTitle() { + return pht( + '%s changed the header image for this post.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s changed the header image for post %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-camera'; + } + +} diff --git a/src/applications/phame/xaction/PhamePostSubtitleTransaction.php b/src/applications/phame/xaction/PhamePostSubtitleTransaction.php new file mode 100644 index 0000000000..05b0f34790 --- /dev/null +++ b/src/applications/phame/xaction/PhamePostSubtitleTransaction.php @@ -0,0 +1,63 @@ +getSubtitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setSubtitle($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s set this post\'s subtitle to "%s".', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s updated the post\'s subtitle to "%s".', + $this->renderAuthor(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s set the subtitle for %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s updated the subtitle for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('subtitle'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The subtitle can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/phame/xaction/PhamePostTitleTransaction.php b/src/applications/phame/xaction/PhamePostTitleTransaction.php new file mode 100644 index 0000000000..99c556cc3a --- /dev/null +++ b/src/applications/phame/xaction/PhamePostTitleTransaction.php @@ -0,0 +1,55 @@ +getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setTitle($value); + } + + public function getTitle() { + return pht( + '%s renamed this blog post from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s renamed %s blog post from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getTitle(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Posts must have a title.')); + } + + $max_length = $object->getColumnMaximumByteLength('title'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The title can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/phame/xaction/PhamePostTransactionType.php b/src/applications/phame/xaction/PhamePostTransactionType.php new file mode 100644 index 0000000000..b8ce182bc7 --- /dev/null +++ b/src/applications/phame/xaction/PhamePostTransactionType.php @@ -0,0 +1,4 @@ +getVisibility(); + } + + public function applyInternalEffects($object, $value) { + if ($value == PhameConstants::VISIBILITY_DRAFT) { + $object->setDatePublished(0); + } else if ($value == PhameConstants::VISIBILITY_ARCHIVED) { + $object->setDatePublished(0); + } else { + $object->setDatePublished(PhabricatorTime::getNow()); + } + $object->setVisibility($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + if ($new == PhameConstants::VISIBILITY_DRAFT) { + return pht( + '%s marked this post as a draft.', + $this->renderAuthor()); + } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { + return pht( + '%s archived this post.', + $this->renderAuthor()); + } else { + return pht( + '%s published this post.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + if ($new == PhameConstants::VISIBILITY_DRAFT) { + return pht( + '%s marked %s as a draft.', + $this->renderAuthor(), + $this->renderObject()); + } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { + return pht( + '%s marked %s as archived.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s published %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + if ($new == PhameConstants::VISIBILITY_PUBLISHED) { + return 'fa-rss'; + } else if ($new == PhameConstants::VISIBILITY_ARCHIVED) { + return 'fa-ban'; + } else { + return 'fa-eye-slash'; + } + } +} diff --git a/src/applications/phid/PhabricatorObjectHandle.php b/src/applications/phid/PhabricatorObjectHandle.php index fa66642e07..6379915225 100644 --- a/src/applications/phid/PhabricatorObjectHandle.php +++ b/src/applications/phid/PhabricatorObjectHandle.php @@ -407,8 +407,8 @@ final class PhabricatorObjectHandle public function renderTag() { return id(new PHUITagView()) - ->setType(PHUITagView::TYPE_OBJECT) - ->setShade($this->getTagColor()) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor($this->getTagColor()) ->setIcon($this->getIcon()) ->setHref($this->getURI()) ->setName($this->getLinkName()); diff --git a/src/applications/phid/view/PHUIHandleTagListView.php b/src/applications/phid/view/PHUIHandleTagListView.php index c4ef3762c3..abae359a16 100644 --- a/src/applications/phid/view/PHUIHandleTagListView.php +++ b/src/applications/phid/view/PHUIHandleTagListView.php @@ -121,8 +121,8 @@ final class PHUIHandleTagListView extends AphrontTagView { private function newPlaceholderTag() { return id(new PHUITagView()) - ->setType(PHUITagView::TYPE_OBJECT) - ->setShade(PHUITagView::COLOR_DISABLED) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_DISABLED) ->setSlimShady($this->slim); } diff --git a/src/applications/phlux/controller/PhluxEditController.php b/src/applications/phlux/controller/PhluxEditController.php index f37795a754..e2bb7719f7 100644 --- a/src/applications/phlux/controller/PhluxEditController.php +++ b/src/applications/phlux/controller/PhluxEditController.php @@ -154,26 +154,19 @@ final class PhluxEditController extends PhluxController { if ($is_new) { $title = pht('Create Variable'); $crumbs->addTextCrumb($title, $request->getRequestURI()); - $header_icon = 'fa-plus-square'; } else { $title = pht('Edit Variable: %s', $key); - $header_icon = 'fa-pencil'; $crumbs->addTextCrumb($title, $request->getRequestURI()); } $crumbs->setBorder(true); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Variable')) + ->setHeaderText($title) ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter(array( $box, )); diff --git a/src/applications/pholio/application/PhabricatorPholioApplication.php b/src/applications/pholio/application/PhabricatorPholioApplication.php index 4d80dd12ae..4afaeba263 100644 --- a/src/applications/pholio/application/PhabricatorPholioApplication.php +++ b/src/applications/pholio/application/PhabricatorPholioApplication.php @@ -6,6 +6,10 @@ final class PhabricatorPholioApplication extends PhabricatorApplication { return pht('Pholio'); } + public function getMenuName() { + return pht('Design Review'); + } + public function getBaseURI() { return '/pholio/'; } diff --git a/src/applications/pholio/controller/PholioMockArchiveController.php b/src/applications/pholio/controller/PholioMockArchiveController.php index be8066ea7b..feca94610b 100644 --- a/src/applications/pholio/controller/PholioMockArchiveController.php +++ b/src/applications/pholio/controller/PholioMockArchiveController.php @@ -32,7 +32,7 @@ final class PholioMockArchiveController $xactions = array(); $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransaction::TYPE_STATUS) + ->setTransactionType(PholioMockStatusTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); id(new PholioMockEditor()) diff --git a/src/applications/pholio/controller/PholioMockCommentController.php b/src/applications/pholio/controller/PholioMockCommentController.php index b127d0b1da..1888c7369e 100644 --- a/src/applications/pholio/controller/PholioMockCommentController.php +++ b/src/applications/pholio/controller/PholioMockCommentController.php @@ -45,7 +45,7 @@ final class PholioMockCommentController extends PholioController { foreach ($inline_comments as $inline_comment) { $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransaction::TYPE_INLINE) + ->setTransactionType(PholioMockInlineTransaction::TRANSACTIONTYPE) ->attachComment($inline_comment); } diff --git a/src/applications/pholio/controller/PholioMockEditController.php b/src/applications/pholio/controller/PholioMockEditController.php index 06d7e9c029..89d1fe2a50 100644 --- a/src/applications/pholio/controller/PholioMockEditController.php +++ b/src/applications/pholio/controller/PholioMockEditController.php @@ -23,7 +23,6 @@ final class PholioMockEditController extends PholioController { } $title = pht('Edit Mock: %s', $mock->getName()); - $header_icon = 'fa-pencil'; $is_new = false; $mock_images = $mock->getImages(); @@ -33,7 +32,6 @@ final class PholioMockEditController extends PholioController { $mock = PholioMock::initializeNewMock($viewer); $title = pht('Create Mock'); - $header_icon = 'fa-plus-square'; $is_new = true; $files = array(); @@ -65,8 +63,8 @@ final class PholioMockEditController extends PholioController { if ($request->isFormPost()) { $xactions = array(); - $type_name = PholioTransaction::TYPE_NAME; - $type_desc = PholioTransaction::TYPE_DESCRIPTION; + $type_name = PholioMockNameTransaction::TRANSACTIONTYPE; + $type_desc = PholioMockDescriptionTransaction::TRANSACTIONTYPE; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; $type_cc = PhabricatorTransactions::TYPE_SUBSCRIBERS; @@ -88,11 +86,6 @@ final class PholioMockEditController extends PholioController { $mock_xactions[$type_cc] = array('=' => $v_cc); $mock_xactions[$type_space] = $v_space; - if (!strlen($request->getStr('name'))) { - $e_name = pht('Required'); - $errors[] = pht('You must give the mock a name.'); - } - $file_phids = $request->getArr('file_phids'); if ($file_phids) { $files = id(new PhabricatorFileQuery()) @@ -156,7 +149,7 @@ final class PholioMockEditController extends PholioController { ->setSequence($sequence); $xactions[] = id(new PholioTransaction()) ->setTransactionType( - PholioTransaction::TYPE_IMAGE_REPLACE) + PholioImageReplaceTransaction::TRANSACTIONTYPE) ->setNewValue($replace_image); $posted_mock_images[] = $replace_image; } else if (!$existing_image) { // this is an add @@ -167,23 +160,23 @@ final class PholioMockEditController extends PholioController { ->setDescription($description) ->setSequence($sequence); $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransaction::TYPE_IMAGE_FILE) + ->setTransactionType(PholioImageFileTransaction::TRANSACTIONTYPE) ->setNewValue( array('+' => array($add_image))); $posted_mock_images[] = $add_image; } else { $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransaction::TYPE_IMAGE_NAME) + ->setTransactionType(PholioImageNameTransaction::TRANSACTIONTYPE) ->setNewValue( array($existing_image->getPHID() => $title)); $xactions[] = id(new PholioTransaction()) ->setTransactionType( - PholioTransaction::TYPE_IMAGE_DESCRIPTION) + PholioImageDescriptionTransaction::TRANSACTIONTYPE) ->setNewValue( array($existing_image->getPHID() => $description)); $xactions[] = id(new PholioTransaction()) ->setTransactionType( - PholioTransaction::TYPE_IMAGE_SEQUENCE) + PholioImageSequenceTransaction::TRANSACTIONTYPE) ->setNewValue( array($existing_image->getPHID() => $sequence)); @@ -194,7 +187,7 @@ final class PholioMockEditController extends PholioController { if (!isset($files[$file_phid]) && !isset($replaces[$file_phid])) { // this is an outright delete $xactions[] = id(new PholioTransaction()) - ->setTransactionType(PholioTransaction::TYPE_IMAGE_FILE) + ->setTransactionType(PholioImageFileTransaction::TRANSACTIONTYPE) ->setNewValue( array('-' => array($mock_image))); } @@ -352,9 +345,9 @@ final class PholioMockEditController extends PholioController { ->appendChild($submit); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Mock')) + ->setHeaderText($title) ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); @@ -364,12 +357,7 @@ final class PholioMockEditController extends PholioController { $crumbs->addTextCrumb($title); $crumbs->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter($form_box); return $this->newPage() diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index 31d1602202..c0fcf31f83 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -17,10 +17,19 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $this->newImages = $new_images; return $this; } - private function getNewImages() { + + public function getNewImages() { return $this->newImages; } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this mock.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); @@ -29,145 +38,17 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = PholioTransaction::TYPE_NAME; - $types[] = PholioTransaction::TYPE_DESCRIPTION; - $types[] = PholioTransaction::TYPE_STATUS; - $types[] = PholioTransaction::TYPE_INLINE; - - $types[] = PholioTransaction::TYPE_IMAGE_FILE; - $types[] = PholioTransaction::TYPE_IMAGE_NAME; - $types[] = PholioTransaction::TYPE_IMAGE_DESCRIPTION; - $types[] = PholioTransaction::TYPE_IMAGE_REPLACE; - $types[] = PholioTransaction::TYPE_IMAGE_SEQUENCE; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_NAME: - return $object->getName(); - case PholioTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case PholioTransaction::TYPE_STATUS: - return $object->getStatus(); - case PholioTransaction::TYPE_IMAGE_FILE: - $images = $object->getImages(); - return mpull($images, 'getPHID'); - case PholioTransaction::TYPE_IMAGE_NAME: - $name = null; - $phid = null; - $image = $this->getImageForXaction($object, $xaction); - if ($image) { - $name = $image->getName(); - $phid = $image->getPHID(); - } - return array($phid => $name); - case PholioTransaction::TYPE_IMAGE_DESCRIPTION: - $description = null; - $phid = null; - $image = $this->getImageForXaction($object, $xaction); - if ($image) { - $description = $image->getDescription(); - $phid = $image->getPHID(); - } - return array($phid => $description); - case PholioTransaction::TYPE_IMAGE_REPLACE: - $raw = $xaction->getNewValue(); - return $raw->getReplacesImagePHID(); - case PholioTransaction::TYPE_IMAGE_SEQUENCE: - $sequence = null; - $phid = null; - $image = $this->getImageForXaction($object, $xaction); - if ($image) { - $sequence = $image->getSequence(); - $phid = $image->getPHID(); - } - return array($phid => $sequence); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_NAME: - case PholioTransaction::TYPE_DESCRIPTION: - case PholioTransaction::TYPE_STATUS: - case PholioTransaction::TYPE_IMAGE_NAME: - case PholioTransaction::TYPE_IMAGE_DESCRIPTION: - case PholioTransaction::TYPE_IMAGE_SEQUENCE: - return $xaction->getNewValue(); - case PholioTransaction::TYPE_IMAGE_REPLACE: - $raw = $xaction->getNewValue(); - return $raw->getPHID(); - case PholioTransaction::TYPE_IMAGE_FILE: - $raw_new_value = $xaction->getNewValue(); - $new_value = array(); - foreach ($raw_new_value as $key => $images) { - $new_value[$key] = mpull($images, 'getPHID'); - } - $xaction->setNewValue($new_value); - return $this->getPHIDTransactionNewValue($xaction); - } - } - - protected function extractFilePHIDsFromCustomTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $images = $this->getNewImages(); - $images = mpull($images, null, 'getPHID'); - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_FILE: - $file_phids = array(); - foreach ($xaction->getNewValue() as $image_phid) { - $image = idx($images, $image_phid); - if (!$image) { - continue; - } - $file_phids[] = $image->getFilePHID(); - } - return $file_phids; - case PholioTransaction::TYPE_IMAGE_REPLACE: - $image_phid = $xaction->getNewValue(); - $image = idx($images, $image_phid); - - if ($image) { - return array($image->getFilePHID()); - } - break; - } - - return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); - } - - - protected function transactionHasEffect( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: - return true; - } - - return parent::transactionHasEffect($object, $xaction); - } - protected function shouldApplyInitialEffects( PhabricatorLiskDAO $object, array $xactions) { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_FILE: - case PholioTransaction::TYPE_IMAGE_REPLACE: + case PholioImageFileTransaction::TRANSACTIONTYPE: + case PholioImageReplaceTransaction::TRANSACTIONTYPE: return true; break; } @@ -182,7 +63,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $new_images = array(); foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_FILE: + case PholioImageFileTransaction::TRANSACTIONTYPE: $new_value = $xaction->getNewValue(); foreach ($new_value as $key => $txn_images) { if ($key != '+') { @@ -194,7 +75,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { } } break; - case PholioTransaction::TYPE_IMAGE_REPLACE: + case PholioImageReplaceTransaction::TRANSACTIONTYPE: $image = $xaction->getNewValue(); $image->save(); $new_images[] = $image; @@ -204,93 +85,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $this->setNewImages($new_images); } - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - if ($object->getOriginalName() === null) { - $object->setOriginalName($xaction->getNewValue()); - } - break; - case PholioTransaction::TYPE_DESCRIPTION: - $object->setDescription($xaction->getNewValue()); - break; - case PholioTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - break; - } - } - - private function getImageForXaction( - PholioMock $mock, - PhabricatorApplicationTransaction $xaction) { - $raw_new_value = $xaction->getNewValue(); - $image_phid = key($raw_new_value); - $images = $mock->getImages(); - foreach ($images as $image) { - if ($image->getPHID() == $image_phid) { - return $image; - } - } - return null; - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_IMAGE_FILE: - $old_map = array_fuse($xaction->getOldValue()); - $new_map = array_fuse($xaction->getNewValue()); - - $obsolete_map = array_diff_key($old_map, $new_map); - $images = $object->getImages(); - foreach ($images as $seq => $image) { - if (isset($obsolete_map[$image->getPHID()])) { - $image->setIsObsolete(1); - $image->save(); - unset($images[$seq]); - } - } - $object->attachImages($images); - break; - case PholioTransaction::TYPE_IMAGE_REPLACE: - $old = $xaction->getOldValue(); - $images = $object->getImages(); - foreach ($images as $seq => $image) { - if ($image->getPHID() == $old) { - $image->setIsObsolete(1); - $image->save(); - unset($images[$seq]); - } - } - $object->attachImages($images); - break; - case PholioTransaction::TYPE_IMAGE_NAME: - $image = $this->getImageForXaction($object, $xaction); - $value = (string)head($xaction->getNewValue()); - $image->setName($value); - $image->save(); - break; - case PholioTransaction::TYPE_IMAGE_DESCRIPTION: - $image = $this->getImageForXaction($object, $xaction); - $value = (string)head($xaction->getNewValue()); - $image->setDescription($value); - $image->save(); - break; - case PholioTransaction::TYPE_IMAGE_SEQUENCE: - $image = $this->getImageForXaction($object, $xaction); - $value = (int)head($xaction->getNewValue()); - $image->setSequence($value); - $image->save(); - break; - } - } - protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { @@ -304,41 +98,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { return $xactions; } - protected function mergeTransactions( - PhabricatorApplicationTransaction $u, - PhabricatorApplicationTransaction $v) { - - $type = $u->getTransactionType(); - switch ($type) { - case PholioTransaction::TYPE_NAME: - case PholioTransaction::TYPE_DESCRIPTION: - case PholioTransaction::TYPE_STATUS: - return $v; - case PholioTransaction::TYPE_IMAGE_REPLACE: - $u_img = $u->getNewValue(); - $v_img = $v->getNewValue(); - if ($u_img->getReplacesImagePHID() == $v_img->getReplacesImagePHID()) { - return $v; - } - break; - case PholioTransaction::TYPE_IMAGE_FILE: - return $this->mergePHIDOrEdgeTransactions($u, $v); - case PholioTransaction::TYPE_IMAGE_NAME: - case PholioTransaction::TYPE_IMAGE_DESCRIPTION: - case PholioTransaction::TYPE_IMAGE_SEQUENCE: - $raw_new_value_u = $u->getNewValue(); - $raw_new_value_v = $v->getNewValue(); - $phid_u = key($raw_new_value_u); - $phid_v = key($raw_new_value_v); - if ($phid_u == $phid_v) { - return $v; - } - break; - } - - return parent::mergeTransactions($u, $v); - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { @@ -382,7 +141,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { } $comment = $xaction->getComment(); switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: if ($comment && strlen($comment->getContent())) { $inline_comments[] = $comment; } @@ -472,7 +231,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { // Move inline comments to the end, so the comments precede them. foreach ($xactions as $xaction) { $type = $xaction->getTransactionType(); - if ($type == PholioTransaction::TYPE_INLINE) { + if ($type == PholioMockInlineTransaction::TRANSACTIONTYPE) { $tail[] = $xaction; } else { $head[] = $xaction; @@ -487,7 +246,7 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: return true; } diff --git a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php index f81eb3e1d7..039b0ddeef 100644 --- a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php +++ b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php @@ -22,9 +22,9 @@ final class PhabricatorPholioMockTestDataGenerator // Accumulate Transactions $changes = array(); - $changes[PholioTransaction::TYPE_NAME] = + $changes[PholioMockNameTransaction::TRANSACTIONTYPE] = $this->generateTitle(); - $changes[PholioTransaction::TYPE_DESCRIPTION] = + $changes[PholioMockDescriptionTransaction::TRANSACTIONTYPE] = $this->generateDescription(); $changes[PhabricatorTransactions::TYPE_VIEW_POLICY] = PhabricatorPolicies::POLICY_PUBLIC; diff --git a/src/applications/pholio/search/PholioMockFerretEngine.php b/src/applications/pholio/search/PholioMockFerretEngine.php new file mode 100644 index 0000000000..f4f9b4610f --- /dev/null +++ b/src/applications/pholio/search/PholioMockFerretEngine.php @@ -0,0 +1,18 @@ +getObjectPHID(); - - $new = $this->getNewValue(); - $old = $this->getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_IMAGE_FILE: - $phids = array_merge($phids, $new, $old); - break; - case self::TYPE_IMAGE_REPLACE: - $phids[] = $new; - $phids[] = $old; - break; - case self::TYPE_IMAGE_DESCRIPTION: - case self::TYPE_IMAGE_NAME: - case self::TYPE_IMAGE_SEQUENCE: - $phids[] = key($new); - break; - } - - return $phids; - } - - public function shouldHide() { - $old = $this->getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($old === null); - case self::TYPE_IMAGE_NAME: - case self::TYPE_IMAGE_DESCRIPTION: - return ($old === array(null => null)); - // this is boring / silly to surface; changing sequence is NBD - case self::TYPE_IMAGE_SEQUENCE: - return true; - } - - return parent::shouldHide(); - } - - public function getIcon() { - - $new = $this->getNewValue(); - $old = $this->getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_INLINE: - return 'fa-comment'; - case self::TYPE_NAME: - case self::TYPE_DESCRIPTION: - case self::TYPE_STATUS: - if ($new == PholioMock::STATUS_CLOSED) { - return 'fa-ban'; - } else { - return 'fa-check'; - } - case self::TYPE_IMAGE_NAME: - case self::TYPE_IMAGE_DESCRIPTION: - case self::TYPE_IMAGE_SEQUENCE: - return 'fa-pencil'; - case self::TYPE_IMAGE_FILE: - case self::TYPE_IMAGE_REPLACE: - return 'fa-picture-o'; - } - - return parent::getIcon(); - } - public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { - case self::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: case PhabricatorTransactions::TYPE_COMMENT: $tags[] = self::MAILTAG_COMMENT; break; - case self::TYPE_STATUS: + case PholioMockStatusTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_STATUS; break; - case self::TYPE_NAME: - case self::TYPE_DESCRIPTION: - case self::TYPE_IMAGE_NAME: - case self::TYPE_IMAGE_DESCRIPTION: - case self::TYPE_IMAGE_SEQUENCE: - case self::TYPE_IMAGE_FILE: - case self::TYPE_IMAGE_REPLACE: + case PholioMockNameTransaction::TRANSACTIONTYPE: + case PholioMockDescriptionTransaction::TRANSACTIONTYPE: + case PholioImageNameTransaction::TRANSACTIONTYPE: + case PholioImageDescriptionTransaction::TRANSACTIONTYPE: + case PholioImageSequenceTransaction::TRANSACTIONTYPE: + case PholioImageFileTransaction::TRANSACTIONTYPE: + case PholioImageReplaceTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_UPDATED; break; default: @@ -134,278 +53,4 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { return $tags; } - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created "%s".', - $this->renderHandleLink($author_phid), - $new); - } else { - return pht( - '%s renamed this mock from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - "%s updated the mock's description.", - $this->renderHandleLink($author_phid)); - break; - case self::TYPE_STATUS: - if ($new == PholioMock::STATUS_CLOSED) { - return pht( - '%s closed this mock.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s opened this mock.', - $this->renderHandleLink($author_phid)); - } - break; - case self::TYPE_INLINE: - $count = 1; - foreach ($this->getTransactionGroup() as $xaction) { - if ($xaction->getTransactionType() == $type) { - $count++; - } - } - - return pht( - '%s added %d inline comment(s).', - $this->renderHandleLink($author_phid), - $count); - break; - case self::TYPE_IMAGE_REPLACE: - return pht( - '%s replaced %s with %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - break; - case self::TYPE_IMAGE_FILE: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - if ($add && $rem) { - return pht( - '%s edited image(s), added %d: %s; removed %d: %s.', - $this->renderHandleLink($author_phid), - count($add), - $this->renderHandleList($add), - count($rem), - $this->renderHandleList($rem)); - } else if ($add) { - return pht( - '%s added %d image(s): %s.', - $this->renderHandleLink($author_phid), - count($add), - $this->renderHandleList($add)); - } else { - return pht( - '%s removed %d image(s): %s.', - $this->renderHandleLink($author_phid), - count($rem), - $this->renderHandleList($rem)); - } - break; - - case self::TYPE_IMAGE_NAME: - return pht( - '%s renamed an image (%s) from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink(key($new)), - reset($old), - reset($new)); - break; - case self::TYPE_IMAGE_DESCRIPTION: - return pht( - '%s updated an image\'s (%s) description.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink(key($new))); - break; - case self::TYPE_IMAGE_SEQUENCE: - return pht( - '%s updated an image\'s (%s) sequence.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink(key($new))); - break; - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s renamed %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_STATUS: - if ($new == PholioMock::STATUS_CLOSED) { - return pht( - '%s closed a mock %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s opened a mock %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_INLINE: - return pht( - '%s added an inline comment to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_IMAGE_REPLACE: - case self::TYPE_IMAGE_FILE: - return pht( - '%s updated images of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_IMAGE_NAME: - return pht( - '%s updated the image names of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_IMAGE_DESCRIPTION: - return pht( - '%s updated image descriptions of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - case self::TYPE_IMAGE_SEQUENCE: - return pht( - '%s updated image sequence of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - } - - return parent::getTitleForFeed(); - } - - public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) { - $text = null; - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($this->getOldValue() === null) { - $mock = $story->getPrimaryObject(); - $text = $mock->getDescription(); - } - break; - case self::TYPE_INLINE: - $text = $this->getComment()->getContent(); - break; - } - - return $text; - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - case self::TYPE_IMAGE_DESCRIPTION: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - if ($this->getTransactionType() == - self::TYPE_IMAGE_DESCRIPTION) { - $old = reset($old); - $new = reset($new); - } - - return $this->renderTextCorpusChangeDetails( - $viewer, - $old, - $new); - } - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_STATUS: - if ($new == PholioMock::STATUS_CLOSED) { - return PhabricatorTransactions::COLOR_INDIGO; - } else { - return PhabricatorTransactions::COLOR_GREEN; - } - case self::TYPE_NAME: - if ($old === null) { - return PhabricatorTransactions::COLOR_GREEN; - } - case self::TYPE_IMAGE_REPLACE: - return PhabricatorTransactions::COLOR_YELLOW; - case self::TYPE_IMAGE_FILE: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - if ($add && $rem) { - return PhabricatorTransactions::COLOR_YELLOW; - } else if ($add) { - return PhabricatorTransactions::COLOR_GREEN; - } else { - return PhabricatorTransactions::COLOR_RED; - } - } - - return parent::getColor(); - } - - public function getNoEffectDescription() { - switch ($this->getTransactionType()) { - case self::TYPE_IMAGE_NAME: - return pht('The image title was not updated.'); - case self::TYPE_IMAGE_DESCRIPTION: - return pht('The image description was not updated.'); - case self::TYPE_IMAGE_SEQUENCE: - return pht('The image sequence was not updated.'); - } - - return parent::getNoEffectDescription(); - } } diff --git a/src/applications/pholio/view/PholioTransactionView.php b/src/applications/pholio/view/PholioTransactionView.php index 92624feec6..69613c5428 100644 --- a/src/applications/pholio/view/PholioTransactionView.php +++ b/src/applications/pholio/view/PholioTransactionView.php @@ -30,14 +30,14 @@ final class PholioTransactionView switch ($u->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: break; default: return false; } switch ($v->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: return true; } @@ -50,7 +50,8 @@ final class PholioTransactionView $out = array(); $group = $xaction->getTransactionGroup(); - if ($xaction->getTransactionType() == PholioTransaction::TYPE_INLINE) { + $type = $xaction->getTransactionType(); + if ($type == PholioMockInlineTransaction::TRANSACTIONTYPE) { array_unshift($group, $xaction); } else { $out[] = parent::renderTransactionContent($xaction); @@ -63,7 +64,7 @@ final class PholioTransactionView $inlines = array(); foreach ($group as $xaction) { switch ($xaction->getTransactionType()) { - case PholioTransaction::TYPE_INLINE: + case PholioMockInlineTransaction::TRANSACTIONTYPE: $inlines[] = $xaction; break; default: diff --git a/src/applications/pholio/view/PholioUploadedImageView.php b/src/applications/pholio/view/PholioUploadedImageView.php index 460b54c517..e583fa687b 100644 --- a/src/applications/pholio/view/PholioUploadedImageView.php +++ b/src/applications/pholio/view/PholioUploadedImageView.php @@ -120,7 +120,7 @@ final class PholioUploadedImageView extends AphrontView { return javelin_tag( 'a', array( - 'class' => 'button grey', + 'class' => 'button button-grey', 'sigil' => 'pholio-drop-remove', ), 'X'); diff --git a/src/applications/pholio/xaction/PholioImageDescriptionTransaction.php b/src/applications/pholio/xaction/PholioImageDescriptionTransaction.php new file mode 100644 index 0000000000..32fd8da285 --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageDescriptionTransaction.php @@ -0,0 +1,76 @@ +getImageForXaction($object); + if ($image) { + $description = $image->getDescription(); + $phid = $image->getPHID(); + } + return array($phid => $description); + } + + public function applyInternalEffects($object, $value) { + $image = $this->getImageForXaction($object); + $value = (string)head($this->getNewValue()); + $image->setDescription($value); + $image->save(); + } + + public function getTitle() { + $new = $this->getNewValue(); + + return pht( + '%s updated an image\'s (%s) description.', + $this->renderAuthor(), + $this->renderHandle(head_key($new))); + } + + public function getTitleForFeed() { + return pht( + '%s updated image descriptions of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function mergeTransactions( + $object, + PhabricatorApplicationTransaction $u, + PhabricatorApplicationTransaction $v) { + + $raw_new_value_u = $u->getNewValue(); + $raw_new_value_v = $v->getNewValue(); + $phid_u = head_key($raw_new_value_u); + $phid_v = head_key($raw_new_value_v); + if ($phid_u == $phid_v) { + return $v; + } + + return null; + } + + public function shouldHide() { + $old = $this->getOldValue(); + return ($old === array(null => null)); + } + + public function hasChangeDetailView() { + return true; + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText(head($this->getOldValue())) + ->setNewText(head($this->getNewValue())); + } + +} diff --git a/src/applications/pholio/xaction/PholioImageFileTransaction.php b/src/applications/pholio/xaction/PholioImageFileTransaction.php new file mode 100644 index 0000000000..5f68dad9f1 --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageFileTransaction.php @@ -0,0 +1,120 @@ +getImages(); + return array_values(mpull($images, 'getPHID')); + } + + public function generateNewValue($object, $value) { + $new_value = array(); + foreach ($value as $key => $images) { + $new_value[$key] = mpull($images, 'getPHID'); + } + $old = array_fuse($this->getOldValue()); + return $this->getEditor()->getPHIDList($old, $new_value); + } + + public function applyInternalEffects($object, $value) { + $old_map = array_fuse($this->getOldValue()); + $new_map = array_fuse($this->getNewValue()); + + $obsolete_map = array_diff_key($old_map, $new_map); + $images = $object->getImages(); + foreach ($images as $seq => $image) { + if (isset($obsolete_map[$image->getPHID()])) { + $image->setIsObsolete(1); + $image->save(); + unset($images[$seq]); + } + } + $object->attachImages($images); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + if ($add && $rem) { + return pht( + '%s edited image(s), added %d: %s; removed %d: %s.', + $this->renderAuthor(), + count($add), + $this->renderHandleList($add), + count($rem), + $this->renderHandleList($rem)); + } else if ($add) { + return pht( + '%s added %d image(s): %s.', + $this->renderAuthor(), + count($add), + $this->renderHandleList($add)); + } else { + return pht( + '%s removed %d image(s): %s.', + $this->renderAuthor(), + count($rem), + $this->renderHandleList($rem)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s updated images of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-picture-o'; + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + if ($add && $rem) { + return PhabricatorTransactions::COLOR_YELLOW; + } else if ($add) { + return PhabricatorTransactions::COLOR_GREEN; + } else { + return PhabricatorTransactions::COLOR_RED; + } + } + + public function extractFilePHIDs($object, $value) { + $images = $this->getEditor()->getNewImages(); + $images = mpull($images, null, 'getPHID'); + + + $file_phids = array(); + foreach ($value as $image_phid) { + $image = idx($images, $image_phid); + if (!$image) { + continue; + } + $file_phids[] = $image->getFilePHID(); + } + return $file_phids; + } + + public function mergeTransactions( + $object, + PhabricatorApplicationTransaction $u, + PhabricatorApplicationTransaction $v) { + return $this->getEditor()->mergePHIDOrEdgeTransactions($u, $v); + } + +} diff --git a/src/applications/pholio/xaction/PholioImageNameTransaction.php b/src/applications/pholio/xaction/PholioImageNameTransaction.php new file mode 100644 index 0000000000..33b01903ee --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageNameTransaction.php @@ -0,0 +1,85 @@ +getImageForXaction($object); + if ($image) { + $name = $image->getName(); + $phid = $image->getPHID(); + } + return array($phid => $name); + } + + public function applyInternalEffects($object, $value) { + $image = $this->getImageForXaction($object); + $value = (string)head($this->getNewValue()); + $image->setName($value); + $image->save(); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s renamed an image (%s) from %s to %s.', + $this->renderAuthor(), + $this->renderHandle(key($new)), + $this->renderValue($old), + $this->renderValue($new)); + } + + public function getTitleForFeed() { + return pht( + '%s updated the image names of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function mergeTransactions( + $object, + PhabricatorApplicationTransaction $u, + PhabricatorApplicationTransaction $v) { + + $raw_new_value_u = $u->getNewValue(); + $raw_new_value_v = $v->getNewValue(); + $phid_u = head_key($raw_new_value_u); + $phid_v = head_key($raw_new_value_v); + if ($phid_u == $phid_v) { + return $v; + } + + return null; + } + + public function shouldHide() { + $old = $this->getOldValue(); + return ($old === array(null => null)); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = head(array_values($xaction->getNewValue())); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Mock image names must not be longer than %s character(s).', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + + +} diff --git a/src/applications/pholio/xaction/PholioImageReplaceTransaction.php b/src/applications/pholio/xaction/PholioImageReplaceTransaction.php new file mode 100644 index 0000000000..4978fa9768 --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageReplaceTransaction.php @@ -0,0 +1,80 @@ +getNewValue(); + return $new_image->getReplacesImagePHID(); + } + + public function generateNewValue($object, $value) { + return $value->getPHID(); + } + + public function applyInternalEffects($object, $value) { + $old = $this->getOldValue(); + $images = $object->getImages(); + foreach ($images as $seq => $image) { + if ($image->getPHID() == $old) { + $image->setIsObsolete(1); + $image->save(); + unset($images[$seq]); + } + } + $object->attachImages($images); + } + + public function getTitle() { + return pht( + '%s replaced %s with %s.', + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + + public function getTitleForFeed() { + return pht( + '%s updated images of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-picture-o'; + } + + public function getColor() { + return PhabricatorTransactions::COLOR_YELLOW; + } + + public function mergeTransactions( + $object, + PhabricatorApplicationTransaction $u, + PhabricatorApplicationTransaction $v) { + $u_img = $u->getNewValue(); + $v_img = $v->getNewValue(); + if ($u_img->getReplacesImagePHID() == $v_img->getReplacesImagePHID()) { + return $v; + } + } + + public function extractFilePHIDs($object, $value) { + $file_phids = array(); + + $editor = $this->getEditor(); + $images = $editor->getNewImages(); + foreach ($images as $image) { + if ($image->getPHID() !== $value) { + continue; + } + + $file_phids[] = $image->getFilePHID(); + } + + return $file_phids; + } + +} diff --git a/src/applications/pholio/xaction/PholioImageSequenceTransaction.php b/src/applications/pholio/xaction/PholioImageSequenceTransaction.php new file mode 100644 index 0000000000..c98c199adf --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageSequenceTransaction.php @@ -0,0 +1,60 @@ +getImageForXaction($object); + if ($image) { + $sequence = $image->getSequence(); + $phid = $image->getPHID(); + } + return array($phid => $sequence); + } + + public function applyInternalEffects($object, $value) { + $image = $this->getImageForXaction($object); + $value = (int)head($this->getNewValue()); + $image->setSequence($value); + $image->save(); + } + + public function getTitle() { + $new = $this->getNewValue(); + + return pht( + '%s updated an image\'s (%s) sequence.', + $this->renderAuthor(), + $this->renderHandleLink(key($new))); + } + + public function getTitleForFeed() { + return pht( + '%s updated image sequence of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function shouldHide() { + // this is boring / silly to surface; changing sequence is NBD + return true; + } + + public function mergeTransactions( + $object, + PhabricatorApplicationTransaction $u, + PhabricatorApplicationTransaction $v) { + $raw_new_value_u = $u->getNewValue(); + $raw_new_value_v = $v->getNewValue(); + $phid_u = key($raw_new_value_u); + $phid_v = key($raw_new_value_v); + if ($phid_u == $phid_v) { + return $v; + } + } + +} diff --git a/src/applications/pholio/xaction/PholioImageTransactionType.php b/src/applications/pholio/xaction/PholioImageTransactionType.php new file mode 100644 index 0000000000..3e576776aa --- /dev/null +++ b/src/applications/pholio/xaction/PholioImageTransactionType.php @@ -0,0 +1,18 @@ +getNewValue(); + $image_phid = head_key($raw_new_value); + $images = $mock->getImages(); + foreach ($images as $image) { + if ($image->getPHID() == $image_phid) { + return $image; + } + } + return null; + } + +} diff --git a/src/applications/pholio/xaction/PholioMockDescriptionTransaction.php b/src/applications/pholio/xaction/PholioMockDescriptionTransaction.php new file mode 100644 index 0000000000..75293168e1 --- /dev/null +++ b/src/applications/pholio/xaction/PholioMockDescriptionTransaction.php @@ -0,0 +1,57 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + "%s updated the mock's description.", + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function shouldHide() { + $old = $this->getOldValue(); + return ($old === null); + } + + public function hasChangeDetailView() { + return true; + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/pholio/xaction/PholioMockInlineTransaction.php b/src/applications/pholio/xaction/PholioMockInlineTransaction.php new file mode 100644 index 0000000000..f89fed0c3a --- /dev/null +++ b/src/applications/pholio/xaction/PholioMockInlineTransaction.php @@ -0,0 +1,33 @@ +renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s added an inline comment to %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-comment'; + } + + public function getTransactionHasEffect($object, $old, $new) { + return true; + } + +} diff --git a/src/applications/pholio/xaction/PholioMockNameTransaction.php b/src/applications/pholio/xaction/PholioMockNameTransaction.php new file mode 100644 index 0000000000..d1231636af --- /dev/null +++ b/src/applications/pholio/xaction/PholioMockNameTransaction.php @@ -0,0 +1,92 @@ +getName(); + } + + public function getActionStrength() { + return 1.4; + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + if ($object->getOriginalName() === null) { + $object->setOriginalName($this->getNewValue()); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderValue($new)); + } else { + return pht( + '%s renamed this mock from %s to %s.', + $this->renderAuthor(), + $this->renderValue($old), + $this->renderValue($new)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($old), + $this->renderValue($new)); + } + } + + public function getColor() { + $old = $this->getOldValue(); + + if ($old === null) { + return PhabricatorTransactions::COLOR_GREEN; + } + + return parent::getColor(); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError(pht('Mocks must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Mock names must not be longer than %s character(s).', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/pholio/xaction/PholioMockStatusTransaction.php b/src/applications/pholio/xaction/PholioMockStatusTransaction.php new file mode 100644 index 0000000000..ccacca2b5e --- /dev/null +++ b/src/applications/pholio/xaction/PholioMockStatusTransaction.php @@ -0,0 +1,66 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new == PholioMock::STATUS_CLOSED) { + return pht( + '%s closed this mock.', + $this->renderAuthor()); + } else { + return pht( + '%s opened this mock.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + if ($new == PholioMock::STATUS_CLOSED) { + return pht( + '%s closed mock %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s opened mock %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + + if ($new == PholioMock::STATUS_CLOSED) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + + public function getColor() { + $new = $this->getNewValue(); + + if ($new == PholioMock::STATUS_CLOSED) { + return PhabricatorTransactions::COLOR_INDIGO; + } else { + return PhabricatorTransactions::COLOR_GREEN; + } + } + +} diff --git a/src/applications/pholio/xaction/PholioMockTransactionType.php b/src/applications/pholio/xaction/PholioMockTransactionType.php new file mode 100644 index 0000000000..7993412304 --- /dev/null +++ b/src/applications/pholio/xaction/PholioMockTransactionType.php @@ -0,0 +1,4 @@ + 'button grey', + 'class' => 'button button-grey', 'href' => $payment_method_uri, ), pht('Add New Payment Method')); diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php index 469887172f..cbd122f682 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php @@ -133,7 +133,7 @@ final class PhortuneMerchantPictureController $button = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $spec['tip'], diff --git a/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php b/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php index 794f97aa8e..87bddd4d33 100644 --- a/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php +++ b/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php @@ -123,7 +123,8 @@ final class PhortunePaymentMethodCreateController $next_uri = $this->getApplicationURI( "cart/{$cart_id}/checkout/?paymentMethodID=".$method->getID()); } else if ($subscription_id) { - $next_uri = $cancel_uri; + $next_uri = new PhutilURI($cancel_uri); + $next_uri->setQueryParam('added', true); } else { $account_uri = $this->getApplicationURI($account->getID().'/'); $next_uri = new PhutilURI($account_uri); diff --git a/src/applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php b/src/applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php index d00db2725f..f5feec8a29 100644 --- a/src/applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php +++ b/src/applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php @@ -25,11 +25,12 @@ final class PhortunePaymentMethodDisableController } $account = $method->getAccount(); - $account_uri = $this->getApplicationURI($account->getID().'/'); + $account_id = $account->getID(); + $account_uri = $this->getApplicationURI("/account/billing/{$account_id}/"); if ($request->isFormPost()) { - // TODO: ApplicationTransactions! + // TODO: ApplicationTransactions!!!! $method ->setStatus(PhortunePaymentMethod::STATUS_DISABLED) ->save(); diff --git a/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php b/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php index 7af8aa3456..e7287f3d29 100644 --- a/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php +++ b/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php @@ -4,6 +4,7 @@ final class PhortuneSubscriptionEditController extends PhortuneController { public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); + $added = $request->getBool('added'); $subscription = id(new PhortuneSubscriptionQuery()) ->setViewer($viewer) @@ -112,6 +113,7 @@ final class PhortuneSubscriptionEditController extends PhortuneController { pht('Subscription %d', $subscription->getID()), $view_uri); $crumbs->addTextCrumb(pht('Edit')); + $crumbs->setBorder(true); $uri = $this->getApplicationURI($account->getID().'/card/new/'); @@ -123,19 +125,23 @@ final class PhortuneSubscriptionEditController extends PhortuneController { 'a', array( 'href' => $uri, - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht('Add Payment Method...')); + $radio = id(new AphrontFormRadioButtonControl()) + ->setName('defaultPaymentMethodPHID') + ->setLabel(pht('Autopay With')) + ->setValue($current_phid) + ->setError($e_method); + + foreach ($options as $key => $value) { + $radio->addButton($key, $value, null); + } + $form = id(new AphrontFormView()) ->setUser($viewer) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('defaultPaymentMethodPHID') - ->setLabel(pht('Autopay With')) - ->setValue($current_phid) - ->setError($e_method) - ->setOptions($options)) + ->appendChild($radio) ->appendChild( id(new AphrontFormMarkupControl()) ->setValue($add_method_button)) @@ -151,6 +157,13 @@ final class PhortuneSubscriptionEditController extends PhortuneController { ->setFormErrors($errors) ->appendChild($form); + if ($added) { + $info_view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_SUCCESS) + ->appendChild(pht('Payment method has been successfully added.')); + $box->setInfoView($info_view); + } + $header = id(new PHUIHeaderView()) ->setHeader(pht('Edit %s', $subscription->getSubscriptionName())) ->setHeaderIcon('fa-pencil'); diff --git a/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php b/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php index daf3daa11d..2e78d37d5c 100644 --- a/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php +++ b/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php @@ -43,8 +43,8 @@ final class PhortuneSubscriptionViewController extends PhortuneController { $curtain->addAction( id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Subscription')) + ->setIcon('fa-credit-card') + ->setName(pht('Manage Autopay')) ->setHref($edit_uri) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); diff --git a/src/applications/phortune/editor/PhortuneMerchantEditEngine.php b/src/applications/phortune/editor/PhortuneMerchantEditEngine.php index 596f842574..04e07401c7 100644 --- a/src/applications/phortune/editor/PhortuneMerchantEditEngine.php +++ b/src/applications/phortune/editor/PhortuneMerchantEditEngine.php @@ -65,6 +65,11 @@ final class PhortuneMerchantEditEngine return false; } + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + PhortuneMerchantCapability::CAPABILITY); + } + protected function buildCustomEditFields($object) { $viewer = $this->getViewer(); diff --git a/src/applications/phortune/product/PhortuneSubscriptionProduct.php b/src/applications/phortune/product/PhortuneSubscriptionProduct.php index b63cc7c21c..85eb2999c7 100644 --- a/src/applications/phortune/product/PhortuneSubscriptionProduct.php +++ b/src/applications/phortune/product/PhortuneSubscriptionProduct.php @@ -50,8 +50,9 @@ final class PhortuneSubscriptionProduct public function didPurchaseProduct( PhortuneProduct $product, PhortunePurchase $purchase) { - // TODO: Callback the subscription. - return; + return $this->getSubscription()->didPurchaseProduct( + $product, + $purchase); } public function didRefundProduct( diff --git a/src/applications/phortune/storage/PhortuneSubscription.php b/src/applications/phortune/storage/PhortuneSubscription.php index cc839adbea..a996dbf5d2 100644 --- a/src/applications/phortune/storage/PhortuneSubscription.php +++ b/src/applications/phortune/storage/PhortuneSubscription.php @@ -232,6 +232,15 @@ final class PhortuneSubscription extends PhortuneDAO $purchase); } + public function didPurchaseProduct( + PhortuneProduct $product, + PhortunePurchase $purchase) { + return $this->getImplementation()->didPurchaseProduct( + $this, + $product, + $purchase); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php b/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php index f8cbc21155..a337551c8d 100644 --- a/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php +++ b/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php @@ -48,4 +48,12 @@ abstract class PhortuneSubscriptionImplementation extends Phobject { PhortunePurchase $purchase) { return null; } + + public function didPurchaseProduct( + PhortuneSubscription $subscription, + PhortuneProduct $product, + PhortunePurchase $purchase) { + return null; + } + } diff --git a/src/applications/phortune/view/PhortuneOrderTableView.php b/src/applications/phortune/view/PhortuneOrderTableView.php index a2e492c5be..435f5151d0 100644 --- a/src/applications/phortune/view/PhortuneOrderTableView.php +++ b/src/applications/phortune/view/PhortuneOrderTableView.php @@ -101,7 +101,7 @@ final class PhortuneOrderTableView extends AphrontView { 'a', array( 'href' => $cart->getCheckoutURI(), - 'class' => 'small green button', + 'class' => 'small button button-green', ), pht('Pay Now')), ); diff --git a/src/applications/phriction/application/PhabricatorPhrictionApplication.php b/src/applications/phriction/application/PhabricatorPhrictionApplication.php index d0eb46a069..1b06fd197c 100644 --- a/src/applications/phriction/application/PhabricatorPhrictionApplication.php +++ b/src/applications/phriction/application/PhabricatorPhrictionApplication.php @@ -6,10 +6,14 @@ final class PhabricatorPhrictionApplication extends PhabricatorApplication { return pht('Phriction'); } - public function getShortDescription() { + public function getMenuName() { return pht('Wiki'); } + public function getShortDescription() { + return pht('Wiki Documents'); + } + public function getBaseURI() { return '/w/'; } diff --git a/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php index 36c74f4a68..fc28b53b66 100644 --- a/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php @@ -47,10 +47,10 @@ final class PhrictionCreateConduitAPIMethod extends PhrictionConduitAPIMethod { $xactions = array(); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_TITLE) + ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('title')); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) + ->setTransactionType(PhrictionDocumentContentTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('content')); $editor = id(new PhrictionTransactionEditor()) diff --git a/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php index e99a866529..70c02d376a 100644 --- a/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php @@ -42,10 +42,10 @@ final class PhrictionEditConduitAPIMethod extends PhrictionConduitAPIMethod { $xactions = array(); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_TITLE) + ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('title')); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) + ->setTransactionType(PhrictionDocumentContentTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('content')); $editor = id(new PhrictionTransactionEditor()) diff --git a/src/applications/phriction/controller/PhrictionDeleteController.php b/src/applications/phriction/controller/PhrictionDeleteController.php index 58dc06a65b..b061e89756 100644 --- a/src/applications/phriction/controller/PhrictionDeleteController.php +++ b/src/applications/phriction/controller/PhrictionDeleteController.php @@ -26,7 +26,8 @@ final class PhrictionDeleteController extends PhrictionController { if ($request->isFormPost()) { $xactions = array(); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_DELETE) + ->setTransactionType( + PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE) ->setNewValue(true); $editor = id(new PhrictionTransactionEditor()) diff --git a/src/applications/phriction/controller/PhrictionDiffController.php b/src/applications/phriction/controller/PhrictionDiffController.php index 8ae9618bbd..b66a3b3094 100644 --- a/src/applications/phriction/controller/PhrictionDiffController.php +++ b/src/applications/phriction/controller/PhrictionDiffController.php @@ -103,7 +103,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => $uri->alter('l', $l - 1)->alter('r', $r - 1), - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht("\xC2\xAB Previous Change")); } else { @@ -111,7 +111,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => '#', - 'class' => 'button grey disabled', + 'class' => 'button button-grey disabled', ), pht('Original Change')); } @@ -122,7 +122,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => $uri->alter('l', $l + 1)->alter('r', $r + 1), - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht("Next Change \xC2\xBB")); } else { @@ -130,7 +130,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => '#', - 'class' => 'button grey disabled', + 'class' => 'button button-grey disabled', ), pht('Most Recent Change')); } @@ -200,7 +200,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => '/phriction/edit/'.$document_id.'/', - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht('Edit Current Version')); } @@ -210,7 +210,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => '/phriction/edit/'.$document_id.'/?revert='.$version, - 'class' => 'button grey', + 'class' => 'button button-grey', ), pht('Revert to Version %s...', $version)); } diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php index 038861d136..0ac3fc35bb 100644 --- a/src/applications/phriction/controller/PhrictionEditController.php +++ b/src/applications/phriction/controller/PhrictionEditController.php @@ -133,10 +133,11 @@ final class PhrictionEditController $xactions = array(); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_TITLE) + ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) + ->setTransactionType( + PhrictionDocumentContentTransaction::TRANSACTIONTYPE) ->setNewValue($content_text); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) @@ -174,10 +175,12 @@ final class PhrictionEditController } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_title = nonempty( - $ex->getShortMessage(PhrictionTransaction::TYPE_TITLE), + $ex->getShortMessage( + PhrictionDocumentTitleTransaction::TRANSACTIONTYPE), true); $e_content = nonempty( - $ex->getShortMessage(PhrictionTransaction::TYPE_CONTENT), + $ex->getShortMessage( + PhrictionDocumentContentTransaction::TRANSACTIONTYPE), true); // if we're not supposed to process the content version error, then @@ -192,19 +195,15 @@ final class PhrictionEditController } if ($document->getID()) { - $panel_header = pht('Edit Document: %s', $content->getTitle()); - $page_title = pht('Edit Document'); - $header_icon = 'fa-pencil'; + $page_title = pht('Edit Document: %s', $content->getTitle()); if ($overwrite) { $submit_button = pht('Overwrite Changes'); } else { $submit_button = pht('Save Changes'); } } else { - $panel_header = pht('Create New Phriction Document'); $submit_button = pht('Create Document'); $page_title = pht('Create Document'); - $header_icon = 'fa-plus-square'; } $uri = $document->getSlug(); @@ -286,9 +285,9 @@ final class PhrictionEditController ->setValue($submit_button)); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Document')) + ->setHeaderText($page_title) ->setValidationException($validation_exception) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); $preview = id(new PHUIRemarkupPreviewPanel()) @@ -308,12 +307,7 @@ final class PhrictionEditController } $crumbs->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($panel_header) - ->setHeaderIcon($header_icon); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter(array( $draft_note, $form_box, diff --git a/src/applications/phriction/controller/PhrictionMoveController.php b/src/applications/phriction/controller/PhrictionMoveController.php index 53e5ae16ec..b82681f0a7 100644 --- a/src/applications/phriction/controller/PhrictionMoveController.php +++ b/src/applications/phriction/controller/PhrictionMoveController.php @@ -60,11 +60,13 @@ final class PhrictionMoveController extends PhrictionController { ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) ->setDescription($v_note); $xactions = array(); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_MOVE_TO) + ->setTransactionType( + PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE) ->setNewValue($document); $target_document = id(new PhrictionDocumentQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) @@ -88,7 +90,8 @@ final class PhrictionMoveController extends PhrictionController { return id(new AphrontRedirectResponse())->setURI($redir_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; - $e_slug = $ex->getShortMessage(PhrictionTransaction::TYPE_MOVE_TO); + $e_slug = $ex->getShortMessage( + PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE); } } diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index af238dfcf9..1920b4c718 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -29,7 +29,7 @@ final class PhrictionTransactionEditor return $this; } - private function getOldContent() { + public function getOldContent() { return $this->oldContent; } @@ -38,7 +38,7 @@ final class PhrictionTransactionEditor return $this; } - private function getNewContent() { + public function getNewContent() { return $this->newContent; } @@ -69,6 +69,11 @@ final class PhrictionTransactionEditor return $this->processContentVersionError; } + public function setMoveAwayDocument(PhrictionDocument $document) { + $this->moveAwayDocument = $document; + return $this; + } + public function getEditorApplicationClass() { return 'PhabricatorPhrictionApplication'; } @@ -80,12 +85,6 @@ final class PhrictionTransactionEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhrictionTransaction::TYPE_TITLE; - $types[] = PhrictionTransaction::TYPE_CONTENT; - $types[] = PhrictionTransaction::TYPE_DELETE; - $types[] = PhrictionTransaction::TYPE_MOVE_TO; - $types[] = PhrictionTransaction::TYPE_MOVE_AWAY; - $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; @@ -94,72 +93,18 @@ final class PhrictionTransactionEditor return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: - if ($this->getIsNewObject()) { - return null; - } - return $this->getOldContent()->getTitle(); - case PhrictionTransaction::TYPE_CONTENT: - if ($this->getIsNewObject()) { - return null; - } - return $this->getOldContent()->getContent(); - case PhrictionTransaction::TYPE_DELETE: - case PhrictionTransaction::TYPE_MOVE_TO: - case PhrictionTransaction::TYPE_MOVE_AWAY: - return null; - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: - case PhrictionTransaction::TYPE_CONTENT: - case PhrictionTransaction::TYPE_DELETE: - return $xaction->getNewValue(); - case PhrictionTransaction::TYPE_MOVE_TO: - $document = $xaction->getNewValue(); - // grab the real object now for the sub-editor to come - $this->moveAwayDocument = $document; - $dict = array( - 'id' => $document->getID(), - 'phid' => $document->getPHID(), - 'content' => $document->getContent()->getContent(), - 'title' => $document->getContent()->getTitle(), - ); - return $dict; - case PhrictionTransaction::TYPE_MOVE_AWAY: - $document = $xaction->getNewValue(); - $dict = array( - 'id' => $document->getID(), - 'phid' => $document->getPHID(), - 'content' => $document->getContent()->getContent(), - 'title' => $document->getContent()->getTitle(), - ); - return $dict; - } - } - protected function shouldApplyInitialEffects( PhabricatorLiskDAO $object, array $xactions) { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: - case PhrictionTransaction::TYPE_CONTENT: - case PhrictionTransaction::TYPE_DELETE: - case PhrictionTransaction::TYPE_MOVE_TO: - case PhrictionTransaction::TYPE_MOVE_AWAY: - return true; + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: + case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: + case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: + case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: + return true; } } return parent::shouldApplyInitialEffects($object, $xactions); @@ -173,44 +118,26 @@ final class PhrictionTransactionEditor $this->setNewContent($this->buildNewContentTemplate($object)); } - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: - case PhrictionTransaction::TYPE_CONTENT: - case PhrictionTransaction::TYPE_MOVE_TO: - $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); - return; - case PhrictionTransaction::TYPE_MOVE_AWAY: - $object->setStatus(PhrictionDocumentStatus::STATUS_MOVED); - return; - case PhrictionTransaction::TYPE_DELETE: - $object->setStatus(PhrictionDocumentStatus::STATUS_DELETED); - return; - } - } - protected function expandTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $xactions = parent::expandTransaction($object, $xaction); switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_CONTENT: + case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: if ($this->getIsNewObject()) { break; } $content = $xaction->getNewValue(); if ($content === '') { $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_DELETE) + ->setTransactionType( + PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE) ->setNewValue(true) ->setMetadataValue('contentDelete', true); } break; - case PhrictionTransaction::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $document = $xaction->getNewValue(); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) @@ -227,42 +154,6 @@ final class PhrictionTransactionEditor return $xactions; } - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: - $this->getNewContent()->setTitle($xaction->getNewValue()); - break; - case PhrictionTransaction::TYPE_CONTENT: - $this->getNewContent()->setContent($xaction->getNewValue()); - break; - case PhrictionTransaction::TYPE_DELETE: - $this->getNewContent()->setContent(''); - $this->getNewContent()->setChangeType( - PhrictionChangeType::CHANGE_DELETE); - break; - case PhrictionTransaction::TYPE_MOVE_TO: - $dict = $xaction->getNewValue(); - $this->getNewContent()->setContent($dict['content']); - $this->getNewContent()->setTitle($dict['title']); - $this->getNewContent()->setChangeType( - PhrictionChangeType::CHANGE_MOVE_HERE); - $this->getNewContent()->setChangeRef($dict['id']); - break; - case PhrictionTransaction::TYPE_MOVE_AWAY: - $dict = $xaction->getNewValue(); - $this->getNewContent()->setContent(''); - $this->getNewContent()->setChangeType( - PhrictionChangeType::CHANGE_MOVE_AWAY); - $this->getNewContent()->setChangeRef($dict['id']); - break; - default: - break; - } - } - protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { @@ -270,11 +161,11 @@ final class PhrictionTransactionEditor $save_content = false; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_TITLE: - case PhrictionTransaction::TYPE_CONTENT: - case PhrictionTransaction::TYPE_DELETE: - case PhrictionTransaction::TYPE_MOVE_AWAY: - case PhrictionTransaction::TYPE_MOVE_TO: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: + case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: + case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: + case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: $save_content = true; break; default: @@ -312,11 +203,13 @@ final class PhrictionTransactionEditor $slug); $stub_xactions = array(); $stub_xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_TITLE) + ->setTransactionType( + PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue(PhabricatorSlug::getDefaultTitle($slug)) ->setMetadataValue('stub:create:phid', $object->getPHID()); $stub_xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_CONTENT) + ->setTransactionType( + PhrictionDocumentContentTransaction::TRANSACTIONTYPE) ->setNewValue('') ->setMetadataValue('stub:create:phid', $object->getPHID()); $stub_xactions[] = id(new PhrictionTransaction()) @@ -340,7 +233,8 @@ final class PhrictionTransactionEditor if ($this->moveAwayDocument !== null) { $move_away_xactions = array(); $move_away_xactions[] = id(new PhrictionTransaction()) - ->setTransactionType(PhrictionTransaction::TYPE_MOVE_AWAY) + ->setTransactionType( + PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE) ->setNewValue($object); $sub_editor = id(new PhrictionTransactionEditor()) ->setActor($this->getActor()) @@ -353,7 +247,7 @@ final class PhrictionTransactionEditor // Compute the content diff URI for the publishing phase. foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_CONTENT: + case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: $uri = id(new PhutilURI('/phriction/diff/'.$object->getID().'/')) ->alter('l', $this->getOldContent()->getVersion()) ->alter('r', $this->getNewContent()->getVersion()); @@ -458,7 +352,7 @@ final class PhrictionTransactionEditor foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhrictionTransaction::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $dict = $xaction->getNewValue(); $phids[] = $dict['phid']; break; @@ -477,53 +371,12 @@ final class PhrictionTransactionEditor foreach ($xactions as $xaction) { switch ($type) { - case PhrictionTransaction::TYPE_TITLE: - $title = $object->getContent()->getTitle(); - $missing = $this->validateIsEmptyTextField( - $title, - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Document title is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } else if ($this->getProcessContentVersionError()) { - $error = $this->validateContentVersion($object, $type, $xaction); - if ($error) { - $this->setProcessContentVersionError(false); - $errors[] = $error; - } - } - break; - - case PhrictionTransaction::TYPE_CONTENT: + case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: if ($xaction->getMetadataValue('stub:create:phid')) { continue; } - $missing = false; - if ($this->getIsNewObject()) { - $content = $object->getContent()->getContent(); - $missing = $this->validateIsEmptyTextField( - $content, - $xactions); - } - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Document content is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } else if ($this->getProcessContentVersionError()) { + if ($this->getProcessContentVersionError()) { $error = $this->validateContentVersion($object, $type, $xaction); if ($error) { $this->setProcessContentVersionError(false); @@ -541,34 +394,10 @@ final class PhrictionTransactionEditor $errors = array_merge($errors, $ancestry_errors); } } - break; - case PhrictionTransaction::TYPE_MOVE_TO: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: $source_document = $xaction->getNewValue(); - switch ($source_document->getStatus()) { - case PhrictionDocumentStatus::STATUS_DELETED: - $e_text = pht('A deleted document can not be moved.'); - break; - case PhrictionDocumentStatus::STATUS_MOVED: - $e_text = pht('A moved document can not be moved again.'); - break; - case PhrictionDocumentStatus::STATUS_STUB: - $e_text = pht('A stub document can not be moved.'); - break; - default: - $e_text = null; - break; - } - - if ($e_text) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Can not move document.'), - $e_text, - $xaction); - $errors[] = $error; - } $ancestry_errors = $this->validateAncestry( $object, @@ -610,42 +439,13 @@ final class PhrictionTransactionEditor } break; - case PhrictionTransaction::TYPE_DELETE: - switch ($object->getStatus()) { - case PhrictionDocumentStatus::STATUS_DELETED: - if ($xaction->getMetadataValue('contentDelete')) { - $e_text = pht( - 'This document is already deleted. You must specify '. - 'content to re-create the document and make further edits.'); - } else { - $e_text = pht( - 'An already deleted document can not be deleted.'); - } - break; - case PhrictionDocumentStatus::STATUS_MOVED: - $e_text = pht('A moved document can not be deleted.'); - break; - case PhrictionDocumentStatus::STATUS_STUB: - $e_text = pht('A stub document can not be deleted.'); - break; - default: - break 2; - } - - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Can not delete document.'), - $e_text, - $xaction); - $errors[] = $error; - break; } } return $errors; } - private function validateAncestry( + public function validateAncestry( PhabricatorLiskDAO $object, $type, PhabricatorApplicationTransaction $xaction, diff --git a/src/applications/phriction/query/PhrictionDocumentQuery.php b/src/applications/phriction/query/PhrictionDocumentQuery.php index 24e7fbbef5..2736d825b5 100644 --- a/src/applications/phriction/query/PhrictionDocumentQuery.php +++ b/src/applications/phriction/query/PhrictionDocumentQuery.php @@ -163,7 +163,7 @@ final class PhrictionDocumentQuery } protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { - $joins = array(); + $joins = parent::buildJoinClauseParts($conn); if ($this->getOrderVector()->containsKey('updated')) { $content_dao = new PhrictionContent(); diff --git a/src/applications/phriction/search/PhrictionDocumentFerretEngine.php b/src/applications/phriction/search/PhrictionDocumentFerretEngine.php new file mode 100644 index 0000000000..76802391e7 --- /dev/null +++ b/src/applications/phriction/search/PhrictionDocumentFerretEngine.php @@ -0,0 +1,18 @@ +getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_MOVE_TO: - case self::TYPE_MOVE_AWAY: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: + case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: $phids[] = $new['phid']; break; - case self::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: if ($this->getMetadataValue('stub:create:phid')) { $phids[] = $this->getMetadataValue('stub:create:phid'); } break; } - return $phids; } - public function getRemarkupBlocks() { - $blocks = parent::getRemarkupBlocks(); - - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - $blocks[] = $this->getNewValue(); - break; - } - - return $blocks; - } - - public function shouldHide() { - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - if ($this->getOldValue() === null) { - return true; - } else { - return false; - } - break; - } - - return parent::shouldHide(); - } - public function shouldHideForMail(array $xactions) { switch ($this->getTransactionType()) { - case self::TYPE_MOVE_TO: - case self::TYPE_MOVE_AWAY: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: + case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: return true; - case self::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: return $this->getMetadataValue('stub:create:phid', false); } return parent::shouldHideForMail($xactions); @@ -85,201 +56,26 @@ final class PhrictionTransaction public function shouldHideForFeed() { switch ($this->getTransactionType()) { - case self::TYPE_MOVE_TO: - case self::TYPE_MOVE_AWAY: + case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: + case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: return true; - case self::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: return $this->getMetadataValue('stub:create:phid', false); } return parent::shouldHideForFeed(); } - public function getActionStrength() { - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - return 1.4; - case self::TYPE_CONTENT: - return 1.3; - case self::TYPE_DELETE: - return 1.5; - case self::TYPE_MOVE_TO: - case self::TYPE_MOVE_AWAY: - return 1.0; - } - - return parent::getActionStrength(); - } - - public function getActionName() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - if ($this->getMetadataValue('stub:create:phid')) { - return pht('Stubbed'); - } else { - return pht('Created'); - } - } - - return pht('Retitled'); - - case self::TYPE_CONTENT: - return pht('Edited'); - - case self::TYPE_DELETE: - return pht('Deleted'); - - case self::TYPE_MOVE_TO: - return pht('Moved'); - - case self::TYPE_MOVE_AWAY: - return pht('Moved Away'); - - } - - return parent::getActionName(); - } - - public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - case self::TYPE_CONTENT: - return 'fa-pencil'; - case self::TYPE_DELETE: - return 'fa-times'; - case self::TYPE_MOVE_TO: - case self::TYPE_MOVE_AWAY: - return 'fa-arrows'; - } - - return parent::getIcon(); - } - - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - if ($this->getMetadataValue('stub:create:phid')) { - return pht( - '%s stubbed out this document when creating %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink( - $this->getMetadataValue('stub:create:phid'))); - } else { - return pht( - '%s created this document.', - $this->renderHandleLink($author_phid)); - } - } - return pht( - '%s changed the title from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - - case self::TYPE_CONTENT: - return pht( - '%s edited the document content.', - $this->renderHandleLink($author_phid)); - - case self::TYPE_DELETE: - return pht( - '%s deleted this document.', - $this->renderHandleLink($author_phid)); - - case self::TYPE_MOVE_TO: - return pht( - '%s moved this document from %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new['phid'])); - - case self::TYPE_MOVE_AWAY: - return pht( - '%s moved this document to %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($new['phid'])); - - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - - return pht( - '%s renamed %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); - - case self::TYPE_CONTENT: - return pht( - '%s edited the content of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - case self::TYPE_DELETE: - return pht( - '%s deleted %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } - return parent::getTitleForFeed(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); - } public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: + case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_TITLE; break; - case self::TYPE_CONTENT: + case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_CONTENT; break; - case self::TYPE_DELETE: + case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_DELETE; break; case PhabricatorTransactions::TYPE_SUBSCRIBERS: diff --git a/src/applications/phriction/xaction/PhrictionDocumentContentTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentContentTransaction.php new file mode 100644 index 0000000000..a051546824 --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentContentTransaction.php @@ -0,0 +1,95 @@ +getEditor()->getIsNewObject()) { + return null; + } + return $object->getContent()->getContent(); + } + + public function generateNewValue($object, $value) { + return $value; + } + + public function applyInternalEffects($object, $value) { + $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); + } + + public function applyExternalEffects($object, $value) { + $this->getEditor()->getNewContent()->setContent($value); + } + + public function shouldHide() { + if ($this->getOldValue() === null) { + return true; + } else { + return false; + } + } + + public function getActionStrength() { + return 1.3; + } + + public function getActionName() { + return pht('Edited'); + } + + public function getTitle() { + return pht( + '%s edited the content of this document.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s edited the content of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO DOCUMENT CONTENT'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $content = $object->getContent()->getContent(); + if ($this->isEmptyTextTransaction($content, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Documents must have content.')); + } + + return $errors; + } + +} diff --git a/src/applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php new file mode 100644 index 0000000000..25cc7c2b28 --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php @@ -0,0 +1,86 @@ +setStatus(PhrictionDocumentStatus::STATUS_DELETED); + } + + public function applyExternalEffects($object, $value) { + $this->getEditor()->getNewContent()->setContent(''); + $this->getEditor()->getNewContent()->setChangeType( + PhrictionChangeType::CHANGE_DELETE); + } + + public function getActionStrength() { + return 1.5; + } + + public function getActionName() { + return pht('Deleted'); + } + + public function getTitle() { + return pht( + '%s deleted this document.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s deleted %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $e_text = null; + foreach ($xactions as $xaction) { + switch ($object->getStatus()) { + case PhrictionDocumentStatus::STATUS_DELETED: + if ($xaction->getMetadataValue('contentDelete')) { + $e_text = pht( + 'This document is already deleted. You must specify '. + 'content to re-create the document and make further edits.'); + } else { + $e_text = pht( + 'An already deleted document can not be deleted.'); + } + break; + case PhrictionDocumentStatus::STATUS_MOVED: + $e_text = pht('A moved document can not be deleted.'); + break; + case PhrictionDocumentStatus::STATUS_STUB: + $e_text = pht('A stub document can not be deleted.'); + break; + default: + break; + } + + if ($e_text !== null) { + $errors[] = $this->newInvalidError($e_text); + } + + } + + return $errors; + } + + public function getIcon() { + return 'fa-trash-o'; + } + + public function getColor() { + return 'red'; + } + +} diff --git a/src/applications/phriction/xaction/PhrictionDocumentMoveAwayTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentMoveAwayTransaction.php new file mode 100644 index 0000000000..c827f7337d --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentMoveAwayTransaction.php @@ -0,0 +1,62 @@ + $document->getID(), + 'phid' => $document->getPHID(), + 'content' => $document->getContent()->getContent(), + 'title' => $document->getContent()->getTitle(), + ); + return $dict; + } + + public function applyInternalEffects($object, $value) { + $object->setStatus(PhrictionDocumentStatus::STATUS_MOVED); + } + + public function applyExternalEffects($object, $value) { + $dict = $value; + $this->getEditor()->getNewContent()->setContent(''); + $this->getEditor()->getNewContent()->setChangeType( + PhrictionChangeType::CHANGE_MOVE_AWAY); + $this->getEditor()->getNewContent()->setChangeRef($dict['id']); + } + + public function getActionName() { + return pht('Moved Away'); + } + + public function getTitle() { + $new = $this->getNewValue(); + + return pht( + '%s moved this document to %s', + $this->renderAuthor(), + $this->renderHandleLink($new['phid'])); + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + return pht( + '%s moved %s to %s', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandleLink($new['phid'])); + } + + public function getIcon() { + return 'fa-arrows'; + } + +} diff --git a/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php new file mode 100644 index 0000000000..a95e70ebdb --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php @@ -0,0 +1,105 @@ + $document->getID(), + 'phid' => $document->getPHID(), + 'content' => $document->getContent()->getContent(), + 'title' => $document->getContent()->getTitle(), + ); + + $editor = $this->getEditor(); + $editor->setMoveAwayDocument($document); + + return $dict; + } + + public function applyInternalEffects($object, $value) { + $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); + } + + public function applyExternalEffects($object, $value) { + $dict = $value; + $this->getEditor()->getNewContent()->setContent($dict['content']); + $this->getEditor()->getNewContent()->setTitle($dict['title']); + $this->getEditor()->getNewContent()->setChangeType( + PhrictionChangeType::CHANGE_MOVE_HERE); + $this->getEditor()->getNewContent()->setChangeRef($dict['id']); + } + + public function getActionStrength() { + return 1.0; + } + + public function getActionName() { + return pht('Moved'); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s moved this document from %s', + $this->renderAuthor(), + $this->renderHandle($new['phid'])); + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s moved %s from %s', + $this->renderAuthor(), + $this->renderObject(), + $this->renderHandle($new['phid'])); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $e_text = null; + foreach ($xactions as $xaction) { + $source_document = $xaction->getNewValue(); + switch ($source_document->getStatus()) { + case PhrictionDocumentStatus::STATUS_DELETED: + $e_text = pht('A deleted document can not be moved.'); + break; + case PhrictionDocumentStatus::STATUS_MOVED: + $e_text = pht('A moved document can not be moved again.'); + break; + case PhrictionDocumentStatus::STATUS_STUB: + $e_text = pht('A stub document can not be moved.'); + break; + default: + $e_text = null; + break; + } + + if ($e_text !== null) { + $errors[] = $this->newInvalidError($e_text); + } + + } + + // TODO: Move Ancestry validation here once all types are converted. + + return $errors; + } + + public function getIcon() { + return 'fa-arrows'; + } + +} diff --git a/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php new file mode 100644 index 0000000000..4f1ba850a7 --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php @@ -0,0 +1,97 @@ +isNewObject()) { + return null; + } + return $this->getEditor()->getOldContent()->getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); + } + + public function applyExternalEffects($object, $value) { + $this->getEditor()->getNewContent()->setTitle($value); + } + + public function getActionStrength() { + return 1.4; + } + + public function getActionName() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + if ($this->getMetadataValue('stub:create:phid')) { + return pht('Stubbed'); + } else { + return pht('Created'); + } + } + return pht('Retitled'); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + if ($this->getMetadataValue('stub:create:phid')) { + return pht( + '%s stubbed out this document when creating %s.', + $this->renderAuthor(), + $this->renderHandleLink( + $this->getMetadataValue('stub:create:phid'))); + } else { + return pht( + '%s created this document.', + $this->renderAuthor()); + } + } + + return pht( + '%s changed the title from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $title = $object->getContent()->getTitle(); + if ($this->isEmptyTextTransaction($title, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Documents must have a title.')); + } + + return $errors; + } + +} diff --git a/src/applications/phriction/xaction/PhrictionDocumentTransactionType.php b/src/applications/phriction/xaction/PhrictionDocumentTransactionType.php new file mode 100644 index 0000000000..d99b53a303 --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentTransactionType.php @@ -0,0 +1,4 @@ + '#', - 'class' => 'button green', + 'class' => 'button button-green', 'sigil' => 'create-rule', 'mustcapture' => true, ), diff --git a/src/applications/policy/controller/PhabricatorPolicyExplainController.php b/src/applications/policy/controller/PhabricatorPolicyExplainController.php index da4103765b..fc508cfa3a 100644 --- a/src/applications/policy/controller/PhabricatorPolicyExplainController.php +++ b/src/applications/policy/controller/PhabricatorPolicyExplainController.php @@ -253,7 +253,7 @@ final class PhabricatorPolicyExplainController ->appendParagraph( pht( 'To access this object, users must have first have access '. - 'capabilties on these other objects:')) + 'capabilities on these other objects:')) ->appendList($items); } diff --git a/src/applications/policy/storage/PhabricatorPolicy.php b/src/applications/policy/storage/PhabricatorPolicy.php index 68462cbc19..4141ef9c36 100644 --- a/src/applications/policy/storage/PhabricatorPolicy.php +++ b/src/applications/policy/storage/PhabricatorPolicy.php @@ -264,9 +264,11 @@ final class PhabricatorPolicy public function getFullName() { switch ($this->getType()) { case PhabricatorPolicyType::TYPE_PROJECT: - return pht('Project: %s', $this->getName()); + return pht('Members of Project: %s', $this->getName()); case PhabricatorPolicyType::TYPE_MASKED: return pht('Other: %s', $this->getName()); + case PhabricatorPolicyType::TYPE_USER: + return pht('Only User: %s', $this->getName()); default: return $this->getName(); } @@ -422,6 +424,10 @@ final class PhabricatorPolicy return ($this_strength > $other_strength); } + public function isValidPolicyForEdit() { + return $this->getType() !== PhabricatorPolicyType::TYPE_MASKED; + } + public static function getSpecialRules( PhabricatorPolicyInterface $object, PhabricatorUser $viewer, diff --git a/src/applications/ponder/application/PhabricatorPonderApplication.php b/src/applications/ponder/application/PhabricatorPonderApplication.php index ced8606e99..ed37c7ef6e 100644 --- a/src/applications/ponder/application/PhabricatorPonderApplication.php +++ b/src/applications/ponder/application/PhabricatorPonderApplication.php @@ -60,22 +60,26 @@ final class PhabricatorPonderApplication extends PhabricatorApplication { '/ponder/' => array( '(?:query/(?P[^/]+)/)?' => 'PonderQuestionListController', - 'answer/add/' - => 'PonderAnswerSaveController', - 'answer/edit/(?P\d+)/' - => 'PonderAnswerEditController', - 'answer/comment/(?P\d+)/' - => 'PonderAnswerCommentController', - 'answer/history/(?P\d+)/' - => 'PonderAnswerHistoryController', - 'question/edit/(?:(?P\d+)/)?' - => 'PonderQuestionEditController', - 'question/create/' - => 'PonderQuestionEditController', - 'question/comment/(?P\d+)/' - => 'PonderQuestionCommentController', - 'question/history/(?P\d+)/' - => 'PonderQuestionHistoryController', + 'answer/' => array( + 'add/' + => 'PonderAnswerSaveController', + 'edit/(?P\d+)/' + => 'PonderAnswerEditController', + 'comment/(?P\d+)/' + => 'PonderAnswerCommentController', + 'history/(?P\d+)/' + => 'PonderAnswerHistoryController', + ), + 'question/' => array( + $this->getEditRoutePattern('edit/') + => 'PonderQuestionEditController', + 'create/' + => 'PonderQuestionEditController', + 'comment/(?P\d+)/' + => 'PonderQuestionCommentController', + 'history/(?P\d+)/' + => 'PonderQuestionHistoryController', + ), 'preview/' => 'PhabricatorMarkupPreviewController', 'question/status/(?P[1-9]\d*)/' diff --git a/src/applications/ponder/controller/PonderAnswerEditController.php b/src/applications/ponder/controller/PonderAnswerEditController.php index 3362e15834..9c48c7c669 100644 --- a/src/applications/ponder/controller/PonderAnswerEditController.php +++ b/src/applications/ponder/controller/PonderAnswerEditController.php @@ -42,11 +42,11 @@ final class PonderAnswerEditController extends PonderController { if (!$errors) { $xactions = array(); $xactions[] = id(new PonderAnswerTransaction()) - ->setTransactionType(PonderAnswerTransaction::TYPE_CONTENT) + ->setTransactionType(PonderAnswerContentTransaction::TRANSACTIONTYPE) ->setNewValue($v_content); $xactions[] = id(new PonderAnswerTransaction()) - ->setTransactionType(PonderAnswerTransaction::TYPE_STATUS) + ->setTransactionType(PonderAnswerStatusTransaction::TRANSACTIONTYPE) ->setNewValue($v_status); $editor = id(new PonderAnswerEditor()) diff --git a/src/applications/ponder/controller/PonderAnswerSaveController.php b/src/applications/ponder/controller/PonderAnswerSaveController.php index cac17acbed..53e0f06ea6 100644 --- a/src/applications/ponder/controller/PonderAnswerSaveController.php +++ b/src/applications/ponder/controller/PonderAnswerSaveController.php @@ -38,7 +38,7 @@ final class PonderAnswerSaveController extends PonderController { $xactions = array(); $xactions[] = id(new PonderQuestionTransaction()) - ->setTransactionType(PonderQuestionTransaction::TYPE_ANSWERS) + ->setTransactionType(PonderQuestionAnswerTransaction::TRANSACTIONTYPE) ->setNewValue( array( '+' => array( @@ -58,11 +58,11 @@ final class PonderAnswerSaveController extends PonderController { $xactions = array(); $xactions[] = id(clone $template) - ->setTransactionType(PonderAnswerTransaction::TYPE_QUESTION_ID) + ->setTransactionType(PonderAnswerQuestionIDTransaction::TRANSACTIONTYPE) ->setNewValue($question->getID()); $xactions[] = id(clone $template) - ->setTransactionType(PonderAnswerTransaction::TYPE_CONTENT) + ->setTransactionType(PonderAnswerContentTransaction::TRANSACTIONTYPE) ->setNewValue($content); $editor = id(new PonderAnswerEditor()) diff --git a/src/applications/ponder/controller/PonderController.php b/src/applications/ponder/controller/PonderController.php index a14d70c773..99edcc1af1 100644 --- a/src/applications/ponder/controller/PonderController.php +++ b/src/applications/ponder/controller/PonderController.php @@ -27,13 +27,9 @@ abstract class PonderController extends PhabricatorController { protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $href = $this->getApplicationURI('question/create/'); - $crumbs - ->addAction( - id(new PHUIListItemView()) - ->setName(pht('Ask Question')) - ->setHref($href) - ->setIcon('fa-plus-square')); + id(new PonderQuestionEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/ponder/controller/PonderQuestionEditController.php b/src/applications/ponder/controller/PonderQuestionEditController.php index 49ca5fe846..c881d60f92 100644 --- a/src/applications/ponder/controller/PonderQuestionEditController.php +++ b/src/applications/ponder/controller/PonderQuestionEditController.php @@ -1,219 +1,11 @@ getViewer(); - $id = $request->getURIData('id'); - - if ($id) { - $question = id(new PonderQuestionQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$question) { - return new Aphront404Response(); - } - $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( - $question->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $v_projects = array_reverse($v_projects); - $is_new = false; - } else { - $is_new = true; - $question = PonderQuestion::initializeNewQuestion($viewer); - $v_projects = array(); - } - - $v_title = $question->getTitle(); - $v_content = $question->getContent(); - $v_wiki = $question->getAnswerWiki(); - $v_view = $question->getViewPolicy(); - $v_space = $question->getSpacePHID(); - $v_status = $question->getStatus(); - - - $errors = array(); - $e_title = true; - if ($request->isFormPost()) { - $v_title = $request->getStr('title'); - $v_content = $request->getStr('content'); - $v_wiki = $request->getStr('answerWiki'); - $v_projects = $request->getArr('projects'); - $v_view = $request->getStr('viewPolicy'); - $v_space = $request->getStr('spacePHID'); - $v_status = $request->getStr('status'); - - $len = phutil_utf8_strlen($v_title); - if ($len < 1) { - $errors[] = pht('Title must not be empty.'); - $e_title = pht('Required'); - } else if ($len > 255) { - $errors[] = pht('Title is too long.'); - $e_title = pht('Too Long'); - } - - if (!$errors) { - $template = id(new PonderQuestionTransaction()); - $xactions = array(); - - $xactions[] = id(clone $template) - ->setTransactionType(PonderQuestionTransaction::TYPE_TITLE) - ->setNewValue($v_title); - - $xactions[] = id(clone $template) - ->setTransactionType(PonderQuestionTransaction::TYPE_CONTENT) - ->setNewValue($v_content); - - $xactions[] = id(clone $template) - ->setTransactionType(PonderQuestionTransaction::TYPE_ANSWERWIKI) - ->setNewValue($v_wiki); - - if (!$is_new) { - $xactions[] = id(clone $template) - ->setTransactionType(PonderQuestionTransaction::TYPE_STATUS) - ->setNewValue($v_status); - } - - $xactions[] = id(clone $template) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($v_view); - - $xactions[] = id(clone $template) - ->setTransactionType(PhabricatorTransactions::TYPE_SPACE) - ->setNewValue($v_space); - - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new PonderQuestionTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($v_projects))); - - $editor = id(new PonderQuestionEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - $editor->applyTransactions($question, $xactions); - - return id(new AphrontRedirectResponse()) - ->setURI('/Q'.$question->getID()); - } - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($question) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Question')) - ->setName('title') - ->setValue($v_title) - ->setError($e_title)) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setName('content') - ->setID('content') - ->setValue($v_content) - ->setLabel(pht('Question Details')) - ->setUser($viewer)) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setName('answerWiki') - ->setID('answerWiki') - ->setValue($v_wiki) - ->setLabel(pht('Answer Summary')) - ->setUser($viewer)) - ->appendControl( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($question) - ->setSpacePHID($v_space) - ->setPolicies($policies) - ->setValue($v_view) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)); - - - if (!$is_new) { - $form->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Status')) - ->setName('status') - ->setValue($v_status) - ->setOptions(PonderQuestionStatus::getQuestionStatusMap())); - } - - $form->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Tags')) - ->setName('projects') - ->setValue($v_projects) - ->setDatasource(new PhabricatorProjectDatasource())); - - $form->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($this->getApplicationURI()) - ->setValue(pht('Submit'))); - - $preview = id(new PHUIRemarkupPreviewPanel()) - ->setHeader(pht('Question Preview')) - ->setControlID('content') - ->setPreviewURI($this->getApplicationURI('preview/')); - - $answer_preview = id(new PHUIRemarkupPreviewPanel()) - ->setHeader(pht('Answer Summary Preview')) - ->setControlID('answerWiki') - ->setPreviewURI($this->getApplicationURI('preview/')); - - $crumbs = $this->buildApplicationCrumbs(); - - $id = $question->getID(); - if ($id) { - $crumbs->addTextCrumb("Q{$id}", "/Q{$id}"); - $crumbs->addTextCrumb(pht('Edit')); - $title = pht('Edit Question'); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - } else { - $crumbs->addTextCrumb(pht('Ask Question')); - $title = pht('Ask New Question'); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-plus-square'); - } - $crumbs->setBorder(true); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Question')) - ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $box, - $preview, - $answer_preview, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - + return id(new PonderQuestionEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/ponder/controller/PonderQuestionStatusController.php b/src/applications/ponder/controller/PonderQuestionStatusController.php index a4671d5915..ab791b740f 100644 --- a/src/applications/ponder/controller/PonderQuestionStatusController.php +++ b/src/applications/ponder/controller/PonderQuestionStatusController.php @@ -28,7 +28,7 @@ final class PonderQuestionStatusController $xactions = array(); $xactions[] = id(new PonderQuestionTransaction()) - ->setTransactionType(PonderQuestionTransaction::TYPE_STATUS) + ->setTransactionType(PonderQuestionStatusTransaction::TRANSACTIONTYPE) ->setNewValue($v_status); $editor = id(new PonderQuestionEditor()) diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 59da519834..2cd555204c 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -94,7 +94,7 @@ final class PonderQuestionViewController extends PonderController { $wiki = new PHUIRemarkupView($viewer, $question->getAnswerWiki()); $answer_wiki = id(new PHUIObjectBoxView()) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setHeaderText(pht('ANSWER SUMMARY')) + ->setHeaderText(pht('Answer Summary')) ->appendChild($wiki) ->addClass('ponder-answer-wiki'); } diff --git a/src/applications/ponder/editor/PonderAnswerEditor.php b/src/applications/ponder/editor/PonderAnswerEditor.php index 10a90a1945..bab0e1f72d 100644 --- a/src/applications/ponder/editor/PonderAnswerEditor.php +++ b/src/applications/ponder/editor/PonderAnswerEditor.php @@ -6,80 +6,21 @@ final class PonderAnswerEditor extends PonderEditor { return pht('Ponder Answers'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s added this answer.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s added %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorTransactions::TYPE_COMMENT; - $types[] = PhabricatorTransactions::TYPE_EDGE; - - $types[] = PonderAnswerTransaction::TYPE_CONTENT; - $types[] = PonderAnswerTransaction::TYPE_STATUS; - $types[] = PonderAnswerTransaction::TYPE_QUESTION_ID; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PonderAnswerTransaction::TYPE_CONTENT: - case PonderAnswerTransaction::TYPE_STATUS: - return $object->getContent(); - case PonderAnswerTransaction::TYPE_QUESTION_ID: - return $object->getQuestionID(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PonderAnswerTransaction::TYPE_CONTENT: - case PonderAnswerTransaction::TYPE_STATUS: - case PonderAnswerTransaction::TYPE_QUESTION_ID: - return $xaction->getNewValue(); - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PonderAnswerTransaction::TYPE_CONTENT: - $object->setContent($xaction->getNewValue()); - break; - case PonderAnswerTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - break; - case PonderAnswerTransaction::TYPE_QUESTION_ID: - $object->setQuestionID($xaction->getNewValue()); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - return; - } - - protected function mergeTransactions( - PhabricatorApplicationTransaction $u, - PhabricatorApplicationTransaction $v) { - - $type = $u->getTransactionType(); - switch ($type) { - case PonderAnswerTransaction::TYPE_CONTENT: - return $v; - } - - return parent::mergeTransactions($u, $v); - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { @@ -130,7 +71,7 @@ final class PonderAnswerEditor extends PonderEditor { foreach ($xactions as $xaction) { $type = $xaction->getTransactionType(); $new = $xaction->getNewValue(); - if ($type == PonderAnswerTransaction::TYPE_CONTENT) { + if ($type == PonderAnswerContentTransaction::TRANSACTIONTYPE) { $body->addRawSection($new); } } diff --git a/src/applications/ponder/editor/PonderQuestionEditEngine.php b/src/applications/ponder/editor/PonderQuestionEditEngine.php new file mode 100644 index 0000000000..640f657ad1 --- /dev/null +++ b/src/applications/ponder/editor/PonderQuestionEditEngine.php @@ -0,0 +1,109 @@ +getViewer()); + } + + protected function newObjectQuery() { + return new PonderQuestionQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Question'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Question: %s', $object->getTitle()); + } + + protected function getObjectEditShortText($object) { + return $object->getTitle(); + } + + protected function getObjectCreateShortText() { + return pht('New Question'); + } + + protected function getObjectName() { + return pht('Question'); + } + + protected function getObjectCreateCancelURI($object) { + return $this->getApplication()->getApplicationURI('/'); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('question/edit/'); + } + + protected function getObjectViewURI($object) { + return $object->getViewURI(); + } + + protected function buildCustomEditFields($object) { + + return array( + id(new PhabricatorTextEditField()) + ->setKey('title') + ->setLabel(pht('Question')) + ->setDescription(pht('Question title.')) + ->setConduitTypeDescription(pht('New question title.')) + ->setTransactionType( + PonderQuestionTitleTransaction::TRANSACTIONTYPE) + ->setValue($object->getTitle()) + ->setIsRequired(true), + id(new PhabricatorRemarkupEditField()) + ->setKey('content') + ->setLabel(pht('Details')) + ->setDescription(pht('Long details of the question.')) + ->setConduitTypeDescription(pht('New question details.')) + ->setValue($object->getContent()) + ->setTransactionType( + PonderQuestionContentTransaction::TRANSACTIONTYPE), + id(new PhabricatorRemarkupEditField()) + ->setKey('answerWiki') + ->setLabel(pht('Answer Summary')) + ->setDescription(pht('Answer summary of the question.')) + ->setConduitTypeDescription(pht('New question answer summary.')) + ->setValue($object->getAnswerWiki()) + ->setTransactionType( + PonderQuestionAnswerWikiTransaction::TRANSACTIONTYPE), + id(new PhabricatorSelectEditField()) + ->setKey('status') + ->setLabel(pht('Status')) + ->setDescription(pht('Status of the question.')) + ->setConduitTypeDescription(pht('New question status.')) + ->setValue($object->getStatus()) + ->setTransactionType( + PonderQuestionStatusTransaction::TRANSACTIONTYPE) + ->setOptions(PonderQuestionStatus::getQuestionStatusMap()), + + ); + } + +} diff --git a/src/applications/ponder/editor/PonderQuestionEditor.php b/src/applications/ponder/editor/PonderQuestionEditor.php index 73d9ed0ce0..0720f436b9 100644 --- a/src/applications/ponder/editor/PonderQuestionEditor.php +++ b/src/applications/ponder/editor/PonderQuestionEditor.php @@ -9,6 +9,14 @@ final class PonderQuestionEditor return pht('Ponder Questions'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s asked this question.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s asked %s.', $author, $object); + } + /** * This is used internally on @{method:applyInitialEffects} if a transaction * of type PonderQuestionTransaction::TYPE_ANSWERS is in the mix. The value @@ -32,7 +40,7 @@ final class PonderQuestionEditor foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_ANSWERS: + case PonderQuestionAnswerTransaction::TRANSACTIONTYPE: return true; } } @@ -46,7 +54,7 @@ final class PonderQuestionEditor foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_ANSWERS: + case PonderQuestionAnswerTransaction::TRANSACTIONTYPE: $new_value = $xaction->getNewValue(); $new = idx($new_value, '+', array()); foreach ($new as $new_answer) { @@ -64,123 +72,12 @@ final class PonderQuestionEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; - $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = PhabricatorTransactions::TYPE_SPACE; - - $types[] = PonderQuestionTransaction::TYPE_TITLE; - $types[] = PonderQuestionTransaction::TYPE_CONTENT; - $types[] = PonderQuestionTransaction::TYPE_ANSWERS; - $types[] = PonderQuestionTransaction::TYPE_STATUS; - $types[] = PonderQuestionTransaction::TYPE_ANSWERWIKI; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_TITLE: - return $object->getTitle(); - case PonderQuestionTransaction::TYPE_CONTENT: - return $object->getContent(); - case PonderQuestionTransaction::TYPE_ANSWERS: - return mpull($object->getAnswers(), 'getPHID'); - case PonderQuestionTransaction::TYPE_STATUS: - return $object->getStatus(); - case PonderQuestionTransaction::TYPE_ANSWERWIKI: - return $object->getAnswerWiki(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_TITLE: - case PonderQuestionTransaction::TYPE_CONTENT: - case PonderQuestionTransaction::TYPE_STATUS: - case PonderQuestionTransaction::TYPE_ANSWERWIKI: - return $xaction->getNewValue(); - case PonderQuestionTransaction::TYPE_ANSWERS: - $raw_new_value = $xaction->getNewValue(); - $new_value = array(); - foreach ($raw_new_value as $key => $answers) { - $phids = array(); - foreach ($answers as $answer) { - $obj = idx($answer, 'answer'); - if (!$answer) { - continue; - } - $phids[] = $obj->getPHID(); - } - $new_value[$key] = $phids; - } - $xaction->setNewValue($new_value); - return $this->getPHIDTransactionNewValue($xaction); - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_TITLE: - $object->setTitle($xaction->getNewValue()); - break; - case PonderQuestionTransaction::TYPE_CONTENT: - $object->setContent($xaction->getNewValue()); - break; - case PonderQuestionTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - break; - case PonderQuestionTransaction::TYPE_ANSWERWIKI: - $object->setAnswerWiki($xaction->getNewValue()); - break; - case PonderQuestionTransaction::TYPE_ANSWERS: - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - $add = array_diff_key($new, $old); - $rem = array_diff_key($old, $new); - - $count = $object->getAnswerCount(); - $count += count($add); - $count -= count($rem); - - $object->setAnswerCount($count); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - return; - } - - protected function mergeTransactions( - PhabricatorApplicationTransaction $u, - PhabricatorApplicationTransaction $v) { - - $type = $u->getTransactionType(); - switch ($type) { - case PonderQuestionTransaction::TYPE_TITLE: - case PonderQuestionTransaction::TYPE_CONTENT: - case PonderQuestionTransaction::TYPE_STATUS: - case PonderQuestionTransaction::TYPE_ANSWERWIKI: - return $v; - } - - return parent::mergeTransactions($u, $v); - } - protected function supportsSearch() { return true; } @@ -190,7 +87,7 @@ final class PonderQuestionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_ANSWERS: + case PonderQuestionAnswerTransaction::TRANSACTIONTYPE: return false; } @@ -202,7 +99,7 @@ final class PonderQuestionEditor array $xactions) { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_ANSWERS: + case PonderQuestionAnswerTransaction::TRANSACTIONTYPE: return false; } } @@ -221,7 +118,7 @@ final class PonderQuestionEditor array $xactions) { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PonderQuestionTransaction::TYPE_ANSWERS: + case PonderQuestionAnswerTransaction::TRANSACTIONTYPE: return false; } } @@ -269,7 +166,7 @@ final class PonderQuestionEditor $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); // If the user just asked the question, add the question text. - if ($type == PonderQuestionTransaction::TYPE_CONTENT) { + if ($type == PonderQuestionContentTransaction::TRANSACTIONTYPE) { if ($old === null) { $body->addRawSection($new); } diff --git a/src/applications/ponder/storage/PonderAnswerTransaction.php b/src/applications/ponder/storage/PonderAnswerTransaction.php index 924817b34e..efbe020106 100644 --- a/src/applications/ponder/storage/PonderAnswerTransaction.php +++ b/src/applications/ponder/storage/PonderAnswerTransaction.php @@ -1,11 +1,7 @@ getTransactionType()) { - case self::TYPE_CONTENT: - case self::TYPE_STATUS: - $phids[] = $this->getObjectPHID(); - break; - } - - return $phids; + public function getBaseTransactionClass() { + return 'PonderAnswerTransactionType'; } - public function getRemarkupBlocks() { - $blocks = parent::getRemarkupBlocks(); - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - $blocks[] = $this->getNewValue(); - break; - } - return $blocks; - } + public function getMailTags() { + $tags = parent::getMailTags(); + $tags[] = PonderQuestionTransaction::MAILTAG_OTHER; - public function shouldHide() { - switch ($this->getTransactionType()) { - case self::TYPE_QUESTION_ID: - return true; - } - return parent::shouldHide(); - } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - if ($old === '') { - return pht( - '%s added %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s edited %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_STATUS: - if ($new == PonderAnswerStatus::ANSWER_STATUS_VISIBLE) { - return pht( - '%s marked %s as visible.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else if ($new == PonderAnswerStatus::ANSWER_STATUS_HIDDEN) { - return pht( - '%s marked %s as hidden.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - if ($old === '') { - return pht( - '%s added %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s updated %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_STATUS: - if ($new == PonderAnswerStatus::ANSWER_STATUS_VISIBLE) { - return pht( - '%s marked %s as visible.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else if ($new == PonderAnswerStatus::ANSWER_STATUS_HIDDEN) { - return pht( - '%s marked %s as hidden.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - } - - return parent::getTitleForFeed(); - } - - public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) { - $text = null; - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - $text = $this->getNewValue(); - break; - } - return $text; - } - - - public function hasChangeDetails() { - $old = $this->getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - return $old !== null; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); + return $tags; } } diff --git a/src/applications/ponder/storage/PonderQuestion.php b/src/applications/ponder/storage/PonderQuestion.php index 6594219f5a..c505deeed9 100644 --- a/src/applications/ponder/storage/PonderQuestion.php +++ b/src/applications/ponder/storage/PonderQuestion.php @@ -105,6 +105,14 @@ final class PonderQuestion extends PonderDAO return $this->comments; } + public function getMonogram() { + return 'Q'.$this->getID(); + } + + public function getViewURI() { + return '/'.$this->getMonogram(); + } + public function attachAnswers(array $answers) { assert_instances_of($answers, 'PonderAnswer'); $this->answers = $answers; diff --git a/src/applications/ponder/storage/PonderQuestionTransaction.php b/src/applications/ponder/storage/PonderQuestionTransaction.php index 7abfd247f5..73a0899ca8 100644 --- a/src/applications/ponder/storage/PonderQuestionTransaction.php +++ b/src/applications/ponder/storage/PonderQuestionTransaction.php @@ -1,13 +1,7 @@ getTransactionType()) { - case self::TYPE_ANSWERS: - $phids[] = $this->getNewAnswerPHID(); - $phids[] = $this->getObjectPHID(); - break; - } - - return $phids; - } - - public function getRemarkupBlocks() { - $blocks = parent::getRemarkupBlocks(); - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - $blocks[] = $this->getNewValue(); - break; - } - return $blocks; - } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s asked this question.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s edited the question title from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - case self::TYPE_CONTENT: - return pht( - '%s edited the question description.', - $this->renderHandleLink($author_phid)); - case self::TYPE_ANSWERWIKI: - return pht( - '%s edited the question answer wiki.', - $this->renderHandleLink($author_phid)); - case self::TYPE_ANSWERS: - $answer_handle = $this->getHandle($this->getNewAnswerPHID()); - $question_handle = $this->getHandle($object_phid); - - return pht( - '%s answered %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_STATUS: - switch ($new) { - case PonderQuestionStatus::STATUS_OPEN: - return pht( - '%s reopened this question.', - $this->renderHandleLink($author_phid)); - case PonderQuestionStatus::STATUS_CLOSED_RESOLVED: - return pht( - '%s closed this question as resolved.', - $this->renderHandleLink($author_phid)); - case PonderQuestionStatus::STATUS_CLOSED_OBSOLETE: - return pht( - '%s closed this question as obsolete.', - $this->renderHandleLink($author_phid)); - case PonderQuestionStatus::STATUS_CLOSED_INVALID: - return pht( - '%s closed this question as invalid.', - $this->renderHandleLink($author_phid)); - } - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'PonderQuestionTransactionType'; } public function getMailTags() { @@ -120,13 +35,13 @@ final class PonderQuestionTransaction case PhabricatorTransactions::TYPE_COMMENT: $tags[] = self::MAILTAG_COMMENT; break; - case self::TYPE_TITLE: - case self::TYPE_CONTENT: - case self::TYPE_STATUS: - case self::TYPE_ANSWERWIKI: + case PonderQuestionTitleTransaction::TRANSACTIONTYPE: + case PonderQuestionContentTransaction::TRANSACTIONTYPE: + case PonderQuestionStatusTransaction::TRANSACTIONTYPE: + case PonderQuestionAnswerWikiTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_DETAILS; break; - case self::TYPE_ANSWERS: + case PonderQuestionAnswerTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_ANSWERS; break; default: @@ -136,182 +51,4 @@ final class PonderQuestionTransaction return $tags; } - public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - case self::TYPE_CONTENT: - case self::TYPE_ANSWERWIKI: - return 'fa-pencil'; - case self::TYPE_STATUS: - return PonderQuestionStatus::getQuestionStatusIcon($new); - case self::TYPE_ANSWERS: - return 'fa-plus'; - } - - return parent::getIcon(); - } - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - case self::TYPE_CONTENT: - case self::TYPE_ANSWERWIKI: - return PhabricatorTransactions::COLOR_BLUE; - case self::TYPE_ANSWERS: - return PhabricatorTransactions::COLOR_GREEN; - case self::TYPE_STATUS: - return PonderQuestionStatus::getQuestionStatusTagColor($new); - } - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - case self::TYPE_ANSWERWIKI: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); - } - - public function getActionStrength() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return 3; - } - break; - case self::TYPE_ANSWERS: - return 2; - } - - return parent::getActionStrength(); - } - - public function getActionName() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht('Asked'); - } - break; - case self::TYPE_ANSWERS: - return pht('Answered'); - } - - return parent::getActionName(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s asked a question: %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } else { - return pht( - '%s edited the title of %s (was "%s")', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old); - } - case self::TYPE_CONTENT: - return pht( - '%s edited the description of %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_ANSWERWIKI: - return pht( - '%s edited the answer wiki for %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_ANSWERS: - $answer_handle = $this->getHandle($this->getNewAnswerPHID()); - $question_handle = $this->getHandle($object_phid); - return pht( - '%s answered %s', - $this->renderHandleLink($author_phid), - $answer_handle->renderLink($question_handle->getFullName())); - case self::TYPE_STATUS: - switch ($new) { - case PonderQuestionStatus::STATUS_OPEN: - return pht( - '%s reopened %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case PonderQuestionStatus::STATUS_CLOSED_RESOLVED: - return pht( - '%s closed %s as resolved.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case PonderQuestionStatus::STATUS_CLOSED_INVALID: - return pht( - '%s closed %s as invalid.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case PonderQuestionStatus::STATUS_CLOSED_OBSOLETE: - return pht( - '%s closed %s as obsolete.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - } - - return parent::getTitleForFeed(); - } - - public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) { - $text = null; - switch ($this->getTransactionType()) { - case self::TYPE_CONTENT: - $text = $this->getNewValue(); - break; - } - return $text; - } - - /** - * Currently the application only supports adding answers one at a time. - * This data is stored as a list of phids. Use this function to get the - * new phid. - */ - private function getNewAnswerPHID() { - $new = $this->getNewValue(); - $old = $this->getOldValue(); - $add = array_diff($new, $old); - - if (count($add) != 1) { - throw new Exception( - pht('There should be only one answer added at a time.')); - } - - return reset($add); - } - } diff --git a/src/applications/ponder/view/PonderAddAnswerView.php b/src/applications/ponder/view/PonderAddAnswerView.php index d958e9843e..43bfd0d6ba 100644 --- a/src/applications/ponder/view/PonderAddAnswerView.php +++ b/src/applications/ponder/view/PonderAddAnswerView.php @@ -72,7 +72,7 @@ final class PonderAddAnswerView extends AphrontView { ->appendChild( id(new PHUIButtonView()) ->setTag('a') - ->setText(pht('Login to Answer')) + ->setText(pht('Log In to Answer')) ->setHref((string)$login_href)); } diff --git a/src/applications/ponder/xaction/PonderAnswerContentTransaction.php b/src/applications/ponder/xaction/PonderAnswerContentTransaction.php new file mode 100644 index 0000000000..2e55ef5fea --- /dev/null +++ b/src/applications/ponder/xaction/PonderAnswerContentTransaction.php @@ -0,0 +1,73 @@ +getContent(); + } + + public function applyInternalEffects($object, $value) { + $object->setContent($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + + if (!strlen($old)) { + return pht( + '%s added an answer.', + $this->renderAuthor()); + } + + return pht( + '%s updated the answer details.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + + if (!strlen($old)) { + return pht( + '%s added %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + return pht( + '%s updated the answer details for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO ANSWER DETAILS'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/ponder/xaction/PonderAnswerQuestionIDTransaction.php b/src/applications/ponder/xaction/PonderAnswerQuestionIDTransaction.php new file mode 100644 index 0000000000..a66608479c --- /dev/null +++ b/src/applications/ponder/xaction/PonderAnswerQuestionIDTransaction.php @@ -0,0 +1,16 @@ +getQuestionID(); + } + + public function applyInternalEffects($object, $value) { + $object->setQuestionID($value); + } + +} diff --git a/src/applications/ponder/xaction/PonderAnswerStatusTransaction.php b/src/applications/ponder/xaction/PonderAnswerStatusTransaction.php new file mode 100644 index 0000000000..46a359aa5e --- /dev/null +++ b/src/applications/ponder/xaction/PonderAnswerStatusTransaction.php @@ -0,0 +1,62 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + if ($new == PonderAnswerStatus::ANSWER_STATUS_VISIBLE) { + return pht( + '%s marked this answer as visible.', + $this->renderAuthor()); + } else if ($new == PonderAnswerStatus::ANSWER_STATUS_HIDDEN) { + return pht( + '%s marked this answer as hidden.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + if ($new == PonderAnswerStatus::ANSWER_STATUS_VISIBLE) { + return pht( + '%s marked %s as visible.', + $this->renderAuthor(), + $this->renderObject()); + } else if ($new == PonderAnswerStatus::ANSWER_STATUS_HIDDEN) { + return pht( + '%s marked %s as hidden.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + if ($new == PonderAnswerStatus::ANSWER_STATUS_VISIBLE) { + return 'fa-ban'; + } else if ($new == PonderAnswerStatus::ANSWER_STATUS_HIDDEN) { + return 'fa-check'; + } + } + + public function getColor() { + $new = $this->getNewValue(); + if ($new == PonderAnswerStatus::ANSWER_STATUS_VISIBLE) { + return 'green'; + } else if ($new == PonderAnswerStatus::ANSWER_STATUS_HIDDEN) { + return 'indigo'; + } + } + +} diff --git a/src/applications/ponder/xaction/PonderAnswerTransactionType.php b/src/applications/ponder/xaction/PonderAnswerTransactionType.php new file mode 100644 index 0000000000..84268f33b2 --- /dev/null +++ b/src/applications/ponder/xaction/PonderAnswerTransactionType.php @@ -0,0 +1,4 @@ +getAnswers(); + } + + public function applyInternalEffects($object, $value) { + $count = $object->getAnswerCount(); + $count++; + $object->setAnswerCount($count); + } + + public function getTitle() { + return pht( + '%s added an answer.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s added an answer to %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-plus'; + } + +} diff --git a/src/applications/ponder/xaction/PonderQuestionAnswerWikiTransaction.php b/src/applications/ponder/xaction/PonderQuestionAnswerWikiTransaction.php new file mode 100644 index 0000000000..b6b5b150fc --- /dev/null +++ b/src/applications/ponder/xaction/PonderQuestionAnswerWikiTransaction.php @@ -0,0 +1,56 @@ +getAnswerWiki(); + } + + public function applyInternalEffects($object, $value) { + $object->setAnswerWiki($value); + } + + public function getTitle() { + return pht( + '%s updated the answer wiki.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the answer wiki for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO ANSWER WIKI'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/ponder/xaction/PonderQuestionContentTransaction.php b/src/applications/ponder/xaction/PonderQuestionContentTransaction.php new file mode 100644 index 0000000000..d958170475 --- /dev/null +++ b/src/applications/ponder/xaction/PonderQuestionContentTransaction.php @@ -0,0 +1,56 @@ +getContent(); + } + + public function applyInternalEffects($object, $value) { + $object->setContent($value); + } + + public function getTitle() { + return pht( + '%s updated the question details.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the question details for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO QUESTION DETAILS'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/ponder/xaction/PonderQuestionStatusTransaction.php b/src/applications/ponder/xaction/PonderQuestionStatusTransaction.php new file mode 100644 index 0000000000..85080b12b7 --- /dev/null +++ b/src/applications/ponder/xaction/PonderQuestionStatusTransaction.php @@ -0,0 +1,74 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + switch ($new) { + case PonderQuestionStatus::STATUS_OPEN: + return pht( + '%s reopened this question.', + $this->renderAuthor()); + case PonderQuestionStatus::STATUS_CLOSED_RESOLVED: + return pht( + '%s closed this question as resolved.', + $this->renderAuthor()); + case PonderQuestionStatus::STATUS_CLOSED_OBSOLETE: + return pht( + '%s closed this question as obsolete.', + $this->renderAuthor()); + case PonderQuestionStatus::STATUS_CLOSED_INVALID: + return pht( + '%s closed this question as invalid.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + switch ($new) { + case PonderQuestionStatus::STATUS_OPEN: + return pht( + '%s reopened %s.', + $this->renderAuthor(), + $this->renderObject()); + case PonderQuestionStatus::STATUS_CLOSED_RESOLVED: + return pht( + '%s closed %s as resolved.', + $this->renderAuthor(), + $this->renderObject()); + case PonderQuestionStatus::STATUS_CLOSED_INVALID: + return pht( + '%s closed %s as invalid.', + $this->renderAuthor(), + $this->renderObject()); + case PonderQuestionStatus::STATUS_CLOSED_OBSOLETE: + return pht( + '%s closed %s as obsolete.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + return PonderQuestionStatus::getQuestionStatusIcon($new); + } + + public function getColor() { + $new = $this->getNewValue(); + return PonderQuestionStatus::getQuestionStatusTagColor($new); + } + +} diff --git a/src/applications/ponder/xaction/PonderQuestionTitleTransaction.php b/src/applications/ponder/xaction/PonderQuestionTitleTransaction.php new file mode 100644 index 0000000000..e96af9d78f --- /dev/null +++ b/src/applications/ponder/xaction/PonderQuestionTitleTransaction.php @@ -0,0 +1,55 @@ +getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setTitle($value); + } + + public function getTitle() { + return pht( + '%s updated the question from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s updated %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getTitle(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Questions must have a title.')); + } + + $max_length = $object->getColumnMaximumByteLength('title'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The title can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/ponder/xaction/PonderQuestionTransactionType.php b/src/applications/ponder/xaction/PonderQuestionTransactionType.php new file mode 100644 index 0000000000..6b68122464 --- /dev/null +++ b/src/applications/ponder/xaction/PonderQuestionTransactionType.php @@ -0,0 +1,4 @@ +setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name); $this->applyTransactions($project, $user, $xactions); $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($name)); $this->applyTransactions($project, $user, $xactions); @@ -382,11 +382,11 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name2); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($name2)); $this->applyTransactions($project, $user, $xactions); @@ -416,7 +416,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($input, $input)); $this->applyTransactions($project, $user, $xactions); @@ -448,7 +448,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($input)); $this->applyTransactions($project, $user, $xactions); @@ -474,7 +474,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($input)); $caught = null; @@ -503,7 +503,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name); $xactions[] = id(new PhabricatorProjectTransaction()) @@ -601,11 +601,11 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType(PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue(array($slug)); $this->applyTransactions($project, $user, $xactions); @@ -1290,7 +1290,8 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $new_name = $proj->getName().' '.mt_rand(); $xaction = new PhabricatorProjectTransaction(); - $xaction->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME); + $xaction->setTransactionType( + PhabricatorProjectNameTransaction::TRANSACTIONTYPE); $xaction->setNewValue($new_name); $this->applyTransactions($proj, $user, array($xaction)); @@ -1337,7 +1338,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE) ->setNewValue($name); if ($projects) { @@ -1440,17 +1441,19 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setNewValue($name); if ($parent) { if ($is_milestone) { $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE) + ->setTransactionType( + PhabricatorProjectMilestoneTransaction::TRANSACTIONTYPE) ->setNewValue($parent->getPHID()); } else { $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT) + ->setTransactionType( + PhabricatorProjectParentTransaction::TRANSACTIONTYPE) ->setNewValue($parent->getPHID()); } } diff --git a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php index 66075e7248..cbaa006951 100644 --- a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php @@ -42,7 +42,7 @@ final class ProjectCreateConduitAPIMethod extends ProjectConduitAPIMethod { $user); $project = PhabricatorProject::initializeNewProject($user); - $type_name = PhabricatorProjectTransaction::TYPE_NAME; + $type_name = PhabricatorProjectNameTransaction::TRANSACTIONTYPE; $members = $request->getValue('members'); $xactions = array(); @@ -52,19 +52,22 @@ final class ProjectCreateConduitAPIMethod extends ProjectConduitAPIMethod { if ($request->getValue('icon')) { $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_ICON) + ->setTransactionType( + PhabricatorProjectIconTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('icon')); } if ($request->getValue('color')) { $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_COLOR) + ->setTransactionType( + PhabricatorProjectColorTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('color')); } if ($request->getValue('tags')) { $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType( + PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setNewValue($request->getValue('tags')); } diff --git a/src/applications/project/config/PhabricatorProjectColorsConfigOptionType.php b/src/applications/project/config/PhabricatorProjectColorsConfigOptionType.php deleted file mode 100644 index 4cd8c09bbc..0000000000 --- a/src/applications/project/config/PhabricatorProjectColorsConfigOptionType.php +++ /dev/null @@ -1,10 +0,0 @@ -deformat(pht(<<deformat(pht(<<setTransactionType(PhabricatorProjectTransaction::TYPE_STATUS) + ->setTransactionType( + PhabricatorProjectStatusTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php b/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php index 3ba1f03a56..b229f59ecb 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php @@ -28,6 +28,7 @@ final class PhabricatorProjectBoardBackgroundController $this->setProject($board); $id = $board->getID(); + $view_uri = $this->getApplicationURI("board/{$id}/"); $manage_uri = $this->getApplicationURI("board/{$id}/manage/"); if ($request->isFormPost()) { @@ -36,7 +37,8 @@ final class PhabricatorProjectBoardBackgroundController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_BACKGROUND) + ->setTransactionType( + PhabricatorProjectWorkboardBackgroundTransaction::TRANSACTIONTYPE) ->setNewValue($background_key); id(new PhabricatorProjectTransactionEditor()) @@ -47,7 +49,7 @@ final class PhabricatorProjectBoardBackgroundController ->applyTransactions($board, $xactions); return id(new AphrontRedirectResponse()) - ->setURI($manage_uri); + ->setURI($view_uri); } $nav = $this->getProfileMenu(); @@ -136,7 +138,7 @@ final class PhabricatorProjectBoardBackgroundController $button = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $option['name'], diff --git a/src/applications/project/controller/PhabricatorProjectBoardDisableController.php b/src/applications/project/controller/PhabricatorProjectBoardDisableController.php index 0440ff9eff..fc850640f1 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardDisableController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardDisableController.php @@ -33,7 +33,8 @@ final class PhabricatorProjectBoardDisableController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_HASWORKBOARD) + ->setTransactionType( + PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE) ->setNewValue(0); id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/controller/PhabricatorProjectBoardImportController.php b/src/applications/project/controller/PhabricatorProjectBoardImportController.php index 988084d3ee..c344bc0af0 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardImportController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardImportController.php @@ -61,8 +61,18 @@ final class PhabricatorProjectBoardImportController ->setProperties($import_column->getProperties()) ->save(); } + $xactions = array(); + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType( + PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE) + ->setNewValue(1); - $project->setHasWorkboard(1)->save(); + id(new PhabricatorProjectTransactionEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($project, $xactions); $table->saveTransaction(); diff --git a/src/applications/project/controller/PhabricatorProjectBoardManageController.php b/src/applications/project/controller/PhabricatorProjectBoardManageController.php index 75bff07106..5c71dcfb61 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardManageController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardManageController.php @@ -31,112 +31,69 @@ final class PhabricatorProjectBoardManageController $board_id = $board->getID(); $header = $this->buildHeaderView($board); - $actions = $this->buildActionView($board); - $properties = $this->buildPropertyView($board); - - $properties->setActionList($actions); + $curtain = $this->buildCurtainView($board); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard'), "/project/board/{$board_id}/"); $crumbs->addTextCrumb(pht('Manage')); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $crumbs->setBorder(true); $nav = $this->getProfileMenu(); + $columns_list = $this->buildColumnsList($board, $columns); + + require_celerity_resource('project-view-css'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->addClass('project-view-home') + ->addClass('project-view-people-home') + ->setCurtain($curtain) + ->setMainColumn($columns_list); $title = array( pht('Manage Workboard'), $board->getDisplayName(), ); - $columns_list = $this->buildColumnsList($board, $columns); - return $this->newPage() ->setTitle($title) ->setNavigation($nav) ->setCrumbs($crumbs) - ->appendChild( - array( - $box, - $columns_list, - )); + ->appendChild($view); } private function buildHeaderView(PhabricatorProject $board) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $header = id(new PHUIHeaderView()) - ->setUser($viewer) - ->setHeader(pht('Workboard: %s', $board->getDisplayName())); + ->setHeader(pht('Workboard: %s', $board->getDisplayName())) + ->setUser($viewer); return $header; } - private function buildActionView(PhabricatorProject $board) { - $viewer = $this->getRequest()->getUser(); + private function buildCurtainView(PhabricatorProject $board) { + $viewer = $this->getViewer(); $id = $board->getID(); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); + $curtain = $this->newCurtainView(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $board, PhabricatorPolicyCapability::CAN_EDIT); - $reorder_uri = $this->getApplicationURI("board/{$id}/reorder/"); - - $actions->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-exchange') - ->setName(pht('Reorder Columns')) - ->setHref($reorder_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(true)); - - $background_uri = $this->getApplicationURI("board/{$id}/background/"); - - $actions->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-paint-brush') - ->setName(pht('Change Background Color')) - ->setHref($background_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - $disable_uri = $this->getApplicationURI("board/{$id}/disable/"); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-ban') - ->setName(pht('Disable Board')) + ->setName(pht('Disable Workboard')) ->setHref($disable_uri) ->setDisabled(!$can_edit) ->setWorkflow(true)); - return $actions; - } - - private function buildPropertyView( - PhabricatorProject $board) { - $viewer = $this->getRequest()->getUser(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($board); - - $background = $board->getDisplayWorkboardBackgroundColor(); - if ($background !== null) { - $map = PhabricatorProjectWorkboardBackgroundColor::getOptions(); - $map = ipull($map, 'name'); - - $name = idx($map, $background, $background); - $properties->addProperty(pht('Background Color'), $name); - } - - return $properties; + return $curtain; } private function buildColumnsList( @@ -165,6 +122,11 @@ final class PhabricatorProjectBoardManageController if ($column->isHidden()) { $item->setDisabled(true); + $item->addAttribute(pht('Hidden')); + $item->setImageIcon('fa-columns grey'); + } else { + $item->addAttribute(pht('Visible')); + $item->setImageIcon('fa-columns'); } $view->addItem($item); @@ -172,6 +134,7 @@ final class PhabricatorProjectBoardManageController return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Columns')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($view); } diff --git a/src/applications/project/controller/PhabricatorProjectBoardReorderController.php b/src/applications/project/controller/PhabricatorProjectBoardReorderController.php index 05f1dd2d43..8cf75ab2a4 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardReorderController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardReorderController.php @@ -23,13 +23,13 @@ final class PhabricatorProjectBoardReorderController $this->setProject($project); $project_id = $project->getID(); - $manage_uri = $this->getApplicationURI("board/{$project_id}/manage/"); + $view_uri = $this->getApplicationURI("board/{$project_id}/"); $reorder_uri = $this->getApplicationURI("board/{$project_id}/reorder/"); if ($request->isFormPost()) { // User clicked "Done", make sure the page reloads to show the new // column order. - return id(new AphrontRedirectResponse())->setURI($manage_uri); + return id(new AphrontRedirectResponse())->setURI($view_uri); } $columns = id(new PhabricatorProjectColumnQuery()) @@ -94,7 +94,8 @@ final class PhabricatorProjectBoardReorderController $list = id(new PHUIObjectItemListView()) ->setUser($viewer) ->setID($list_id) - ->setFlush(true); + ->setFlush(true) + ->setDrag(true); foreach ($columns as $column) { // Don't allow milestone columns to be reordered. @@ -134,14 +135,9 @@ final class PhabricatorProjectBoardReorderController 'reorderURI' => $reorder_uri, )); - $note = id(new PHUIInfoView()) - ->appendChild(pht('Drag and drop columns to reorder them.')) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE); - return $this->newDialog() ->setTitle(pht('Reorder Columns')) ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($note) ->appendChild($list) ->addSubmitButton(pht('Done')); } diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 80ed701ff9..8405ec677f 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -331,7 +331,7 @@ final class PhabricatorProjectBoardViewController $count_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade(PHUITagView::COLOR_BLUE) + ->setColor(PHUITagView::COLOR_BLUE) ->addSigil('column-points') ->setName( javelin_tag( @@ -705,10 +705,13 @@ final class PhabricatorProjectBoardViewController ->setDisabled(!$can_edit) ->setWorkflow(true); + $reorder_uri = $this->getApplicationURI("board/{$id}/reorder/"); $manage_items[] = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Manage Board')) - ->setHref($manage_uri); + ->setIcon('fa-exchange') + ->setName(pht('Reorder Columns')) + ->setHref($reorder_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(true); if ($show_hidden) { $hidden_uri = $this->getURIWithState() @@ -727,6 +730,23 @@ final class PhabricatorProjectBoardViewController ->setName($hidden_text) ->setHref($hidden_uri); + $manage_items[] = id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER); + + $background_uri = $this->getApplicationURI("board/{$id}/background/"); + $manage_items[] = id(new PhabricatorActionView()) + ->setIcon('fa-paint-brush') + ->setName(pht('Change Background Color')) + ->setHref($background_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(false); + + $manage_uri = $this->getApplicationURI("board/{$id}/manage/"); + $manage_items[] = id(new PhabricatorActionView()) + ->setIcon('fa-gear') + ->setName(pht('Manage Workboard')) + ->setHref($manage_uri); + $batch_edit_uri = $request->getRequestURI(); $batch_edit_uri->setQueryParam('batch', self::BATCH_EDIT_ALL); $can_batch_edit = PhabricatorPolicyFilter::hasCapability( @@ -734,12 +754,6 @@ final class PhabricatorProjectBoardViewController PhabricatorApplication::getByClass('PhabricatorManiphestApplication'), ManiphestBulkEditCapability::CAPABILITY); - $manage_items[] = id(new PhabricatorActionView()) - ->setIcon('fa-list-ul') - ->setName(pht('Batch Edit Visible Tasks...')) - ->setHref($batch_edit_uri) - ->setDisabled(!$can_batch_edit); - $manage_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($manage_items as $item) { @@ -837,6 +851,16 @@ final class PhabricatorProjectBoardViewController ->setHref($batch_edit_uri) ->setDisabled(!$can_batch_edit); + // Column Related Actions Below + // + $edit_uri = 'board/'.$this->id.'/edit/'.$column->getID().'/'; + $column_items[] = id(new PhabricatorActionView()) + ->setName(pht('Edit Column')) + ->setIcon('fa-pencil') + ->setHref($this->getApplicationURI($edit_uri)) + ->setDisabled(!$can_edit) + ->setWorkflow(true); + $can_hide = ($can_edit && !$column->isDefaultColumn()); $hide_uri = 'board/'.$this->id.'/hide/'.$column->getID().'/'; $hide_uri = $this->getApplicationURI($hide_uri); @@ -942,7 +966,18 @@ final class PhabricatorProjectBoardViewController ->setProjectPHID($project->getPHID()) ->save(); - $project->setHasWorkboard(1)->save(); + $xactions = array(); + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType( + PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE) + ->setNewValue(1); + + id(new PhabricatorProjectTransactionEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($project, $xactions); return id(new AphrontRedirectResponse()) ->setURI($board_uri); @@ -1026,7 +1061,8 @@ final class PhabricatorProjectBoardViewController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_HASWORKBOARD) + ->setTransactionType( + PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE) ->setNewValue(1); id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php index 84bdc2b89c..24efec5ebb 100644 --- a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php @@ -44,36 +44,38 @@ final class PhabricatorProjectColumnDetailController $title = $column->getDisplayName(); $header = $this->buildHeaderView($column); - $actions = $this->buildActionView($column); - $properties = $this->buildPropertyView($column, $actions); + $properties = $this->buildPropertyView($column); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard'), "/project/board/{$project_id}/"); $crumbs->addTextCrumb(pht('Column: %s', $title)); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $crumbs->setBorder(true); $nav = $this->getProfileMenu(); + require_celerity_resource('project-view-css'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->addClass('project-view-home') + ->addClass('project-view-people-home') + ->setMainColumn(array( + $properties, + $timeline, + )); return $this->newPage() ->setTitle($title) ->setNavigation($nav) ->setCrumbs($crumbs) - ->appendChild( - array( - $box, - $timeline, - )); + ->appendChild($view); } private function buildHeaderView(PhabricatorProjectColumn $column) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $header = id(new PHUIHeaderView()) - ->setUser($viewer) - ->setHeader($column->getDisplayName()); + ->setHeader(pht('Column: %s', $column->getDisplayName())) + ->setUser($viewer); if ($column->isHidden()) { $header->setStatus('fa-ban', 'dark', pht('Hidden')); @@ -82,41 +84,13 @@ final class PhabricatorProjectColumnDetailController return $header; } - private function buildActionView(PhabricatorProjectColumn $column) { - $viewer = $this->getRequest()->getUser(); - - $id = $column->getID(); - $project_id = $this->getProject()->getID(); - $base_uri = '/board/'.$project_id.'/'; - - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $column, - PhabricatorPolicyCapability::CAN_EDIT); - - $actions->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Column')) - ->setIcon('fa-pencil') - ->setHref($this->getApplicationURI($base_uri.'edit/'.$id.'/')) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - - return $actions; - } - private function buildPropertyView( - PhabricatorProjectColumn $column, - PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + PhabricatorProjectColumn $column) { + $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) - ->setObject($column) - ->setActionList($actions); + ->setObject($column); $limit = $column->getPointLimit(); if ($limit === null) { @@ -126,7 +100,12 @@ final class PhabricatorProjectColumnDetailController } $properties->addProperty(pht('Point Limit'), $limit_text); - return $properties; + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); + + return $box; } } diff --git a/src/applications/project/controller/PhabricatorProjectColumnEditController.php b/src/applications/project/controller/PhabricatorProjectColumnEditController.php index fb0c7b0910..94277c92e5 100644 --- a/src/applications/project/controller/PhabricatorProjectColumnEditController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnEditController.php @@ -51,12 +51,7 @@ final class PhabricatorProjectColumnEditController $validation_exception = null; $base_uri = '/board/'.$project_id.'/'; - if ($is_new) { - // we want to go back to the board - $view_uri = $this->getApplicationURI($base_uri); - } else { - $view_uri = $this->getApplicationURI($base_uri.'column/'.$id.'/'); - } + $view_uri = $this->getApplicationURI($base_uri); if ($request->isFormPost()) { $v_name = $request->getStr('name'); diff --git a/src/applications/project/controller/PhabricatorProjectColumnHideController.php b/src/applications/project/controller/PhabricatorProjectColumnHideController.php index 27dbb17c47..1dd5e47ecb 100644 --- a/src/applications/project/controller/PhabricatorProjectColumnHideController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnHideController.php @@ -65,7 +65,8 @@ final class PhabricatorProjectColumnHideController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_STATUS) + ->setTransactionType( + PhabricatorProjectStatusTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php index 45e2f193af..7c344b0b8e 100644 --- a/src/applications/project/controller/PhabricatorProjectController.php +++ b/src/applications/project/controller/PhabricatorProjectController.php @@ -166,4 +166,12 @@ abstract class PhabricatorProjectController extends PhabricatorController { ->buildResponse(); } + public function renderHashtags(array $tags) { + $result = array(); + foreach ($tags as $key => $tag) { + $result[] = '#'.$tag; + } + return implode(', ', $result); + } + } diff --git a/src/applications/project/controller/PhabricatorProjectCoverController.php b/src/applications/project/controller/PhabricatorProjectCoverController.php index 22f787e56b..98f6c1c995 100644 --- a/src/applications/project/controller/PhabricatorProjectCoverController.php +++ b/src/applications/project/controller/PhabricatorProjectCoverController.php @@ -36,7 +36,7 @@ final class PhabricatorProjectCoverController $xactions = array(); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_COVER_IMAGE) + ->setTransactionType(ManiphestTaskCoverImageTransaction::TRANSACTIONTYPE) ->setNewValue($file->getPHID()); $editor = id(new ManiphestTransactionEditor()) diff --git a/src/applications/project/controller/PhabricatorProjectDefaultController.php b/src/applications/project/controller/PhabricatorProjectDefaultController.php index 0246f33f43..8f42ff9736 100644 --- a/src/applications/project/controller/PhabricatorProjectDefaultController.php +++ b/src/applications/project/controller/PhabricatorProjectDefaultController.php @@ -32,7 +32,7 @@ final class PhabricatorProjectDefaultController $button = pht('Save Default Filter'); $xaction_value = $request->getStr('filter'); - $xaction_type = PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER; + $xaction_type = PhabricatorProjectFilterTransaction::TRANSACTIONTYPE; break; case 'sort': $title = pht('Set Board Default Order'); @@ -43,7 +43,7 @@ final class PhabricatorProjectDefaultController $button = pht('Save Default Order'); $xaction_value = $request->getStr('order'); - $xaction_type = PhabricatorProjectTransaction::TYPE_DEFAULT_SORT; + $xaction_type = PhabricatorProjectSortTransaction::TRANSACTIONTYPE; break; default: return new Aphront404Response(); diff --git a/src/applications/project/controller/PhabricatorProjectEditPictureController.php b/src/applications/project/controller/PhabricatorProjectEditPictureController.php index 4352ec6ad8..e601d744c0 100644 --- a/src/applications/project/controller/PhabricatorProjectEditPictureController.php +++ b/src/applications/project/controller/PhabricatorProjectEditPictureController.php @@ -23,8 +23,7 @@ final class PhabricatorProjectEditPictureController $this->setProject($project); - $edit_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); - $view_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); + $manage_uri = $this->getApplicationURI('manage/'.$project->getID().'/'); $supported_formats = PhabricatorFile::getTransformableImageFormats(); $e_file = true; @@ -78,7 +77,8 @@ final class PhabricatorProjectEditPictureController $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_IMAGE) + ->setTransactionType( + PhabricatorProjectImageTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new PhabricatorProjectTransactionEditor()) @@ -89,7 +89,7 @@ final class PhabricatorProjectEditPictureController $editor->applyTransactions($project, $xactions); - return id(new AphrontRedirectResponse())->setURI($edit_uri); + return id(new AphrontRedirectResponse())->setURI($manage_uri); } } @@ -98,7 +98,10 @@ final class PhabricatorProjectEditPictureController $form = id(new PHUIFormLayoutView()) ->setUser($viewer); - $default_image = PhabricatorFile::loadBuiltin($viewer, 'project.png'); + $builtin = PhabricatorProjectIconSet::getIconImage( + $project->getIcon()); + $default_image = PhabricatorFile::loadBuiltin($this->getViewer(), + 'projects/'.$builtin); $images = array(); @@ -121,9 +124,25 @@ final class PhabricatorProjectEditPictureController } } + $root = dirname(phutil_get_library_root('phabricator')); + $root = $root.'/resources/builtin/projects/v3/'; + + $builtins = id(new FileFinder($root)) + ->withType('f') + ->withFollowSymlinks(true) + ->find(); + + foreach ($builtins as $builtin) { + $file = PhabricatorFile::loadBuiltin($viewer, 'projects/v3/'.$builtin); + $images[$file->getPHID()] = array( + 'uri' => $file->getBestURI(), + 'tip' => pht('Builtin Image'), + ); + } + $images[PhabricatorPHIDConstants::PHID_VOID] = array( 'uri' => $default_image->getBestURI(), - 'tip' => pht('No Picture'), + 'tip' => pht('Default Picture'), ); require_celerity_resource('people-profile-css'); @@ -134,7 +153,7 @@ final class PhabricatorProjectEditPictureController $button = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $spec['tip'], @@ -200,7 +219,7 @@ final class PhabricatorProjectEditPictureController $compose_button = javelin_tag( 'button', array( - 'class' => 'grey', + 'class' => 'button-grey', 'id' => $launch_id, 'sigil' => 'icon-composer', ), @@ -227,7 +246,7 @@ final class PhabricatorProjectEditPictureController $form->appendChild( id(new AphrontFormMarkupControl()) - ->setLabel(pht('Quick Create')) + ->setLabel(pht('Custom')) ->setValue($compose_form)); $upload_form = id(new AphrontFormView()) @@ -242,7 +261,7 @@ final class PhabricatorProjectEditPictureController pht('Supported formats: %s', implode(', ', $supported_formats)))) ->appendChild( id(new AphrontFormSubmitControl()) - ->addCancelButton($edit_uri) + ->addCancelButton($manage_uri) ->setValue(pht('Upload Picture'))); $form_box = id(new PHUIObjectBoxView()) @@ -285,7 +304,7 @@ final class PhabricatorProjectEditPictureController $default_button = javelin_tag( 'button', array( - 'class' => 'grey profile-image-button', + 'class' => 'button-grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => pht('Use Icon and Color'), diff --git a/src/applications/project/controller/PhabricatorProjectLockController.php b/src/applications/project/controller/PhabricatorProjectLockController.php index b9b56bde10..a746911d45 100644 --- a/src/applications/project/controller/PhabricatorProjectLockController.php +++ b/src/applications/project/controller/PhabricatorProjectLockController.php @@ -49,7 +49,8 @@ final class PhabricatorProjectLockController } $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_LOCKED) + ->setTransactionType( + PhabricatorProjectLockTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new PhabricatorProjectTransactionEditor()) diff --git a/src/applications/project/controller/PhabricatorProjectManageController.php b/src/applications/project/controller/PhabricatorProjectManageController.php index 6ecdf6f62c..2c76c63606 100644 --- a/src/applications/project/controller/PhabricatorProjectManageController.php +++ b/src/applications/project/controller/PhabricatorProjectManageController.php @@ -21,8 +21,7 @@ final class PhabricatorProjectManageController $header = id(new PHUIHeaderView()) ->setHeader(pht('Project History')) ->setUser($viewer) - ->setPolicyObject($project) - ->setImage($picture); + ->setPolicyObject($project); if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); @@ -45,10 +44,14 @@ final class PhabricatorProjectManageController $crumbs->addTextCrumb(pht('Manage')); $crumbs->setBorder(true); + require_celerity_resource('project-view-css'); + $manage = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) ->addPropertySection(pht('Details'), $properties) + ->addClass('project-view-home') + ->addClass('project-view-people-home') ->setMainColumn( array( $timeline, @@ -135,6 +138,12 @@ final class PhabricatorProjectManageController pht('Looks Like'), $viewer->renderHandle($project->getPHID())->setAsTag(true)); + $slugs = $project->getSlugs(); + $tags = mpull($slugs, 'getSlug'); + + $view->addProperty( + pht('Hashtags'), + $this->renderHashtags($tags)); $field_list = PhabricatorCustomField::getObjectFields( $project, @@ -144,5 +153,4 @@ final class PhabricatorProjectManageController return $view; } - } diff --git a/src/applications/project/controller/PhabricatorProjectMembersViewController.php b/src/applications/project/controller/PhabricatorProjectMembersViewController.php index 959927c9f3..ad553eb664 100644 --- a/src/applications/project/controller/PhabricatorProjectMembersViewController.php +++ b/src/applications/project/controller/PhabricatorProjectMembersViewController.php @@ -20,26 +20,21 @@ final class PhabricatorProjectMembersViewController $this->setProject($project); $title = pht('Members and Watchers'); - - $properties = $this->buildProperties($project); $curtain = $this->buildCurtainView($project); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($properties); - $member_list = id(new PhabricatorProjectMemberListView()) ->setUser($viewer) ->setProject($project) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setUserPHIDs($project->getMemberPHIDs()); + ->setUserPHIDs($project->getMemberPHIDs()) + ->setShowNote(true); $watcher_list = id(new PhabricatorProjectWatcherListView()) ->setUser($viewer) ->setProject($project) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setUserPHIDs($project->getWatcherPHIDs()); + ->setUserPHIDs($project->getWatcherPHIDs()) + ->setShowNote(true); $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorProject::ITEM_MEMBERS); @@ -52,16 +47,18 @@ final class PhabricatorProjectMembersViewController ->setHeader($title) ->setHeaderIcon('fa-group'); + require_celerity_resource('project-view-css'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) + ->addClass('project-view-home') + ->addClass('project-view-people-home') ->setMainColumn(array( - $object_box, $member_list, $watcher_list, )); - return $this->newPage() ->setNavigation($nav) ->setCrumbs($crumbs) @@ -69,110 +66,11 @@ final class PhabricatorProjectMembersViewController ->appendChild($view); } - private function buildProperties(PhabricatorProject $project) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($project); - - if ($project->isMilestone()) { - $icon_key = PhabricatorProjectIconSet::getMilestoneIconKey(); - $icon = PhabricatorProjectIconSet::getIconIcon($icon_key); - $target = PhabricatorProjectIconSet::getIconName($icon_key); - $note = pht( - 'Members of the parent project are members of this project.'); - $show_join = false; - } else if ($project->getHasSubprojects()) { - $icon = 'fa-sitemap'; - $target = pht('Parent Project'); - $note = pht( - 'Members of all subprojects are members of this project.'); - $show_join = false; - } else if ($project->getIsMembershipLocked()) { - $icon = 'fa-lock'; - $target = pht('Locked Project'); - $note = pht( - 'Users with access may join this project, but may not leave.'); - $show_join = true; - } else { - $icon = 'fa-briefcase'; - $target = pht('Normal Project'); - $note = pht('Users with access may join and leave this project.'); - $show_join = true; - } - - $item = id(new PHUIStatusItemView()) - ->setIcon($icon) - ->setTarget(phutil_tag('strong', array(), $target)) - ->setNote($note); - - $status = id(new PHUIStatusListView()) - ->addItem($item); - - $view->addProperty(pht('Membership'), $status); - - if ($show_join) { - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( - $viewer, - $project); - - $view->addProperty( - pht('Joinable By'), - $descriptions[PhabricatorPolicyCapability::CAN_JOIN]); - } - - $viewer_phid = $viewer->getPHID(); - - if ($project->isUserWatcher($viewer_phid)) { - $watch_item = id(new PHUIStatusItemView()) - ->setIcon('fa-eye green') - ->setTarget(phutil_tag('strong', array(), pht('Watching'))) - ->setNote( - pht( - 'You will receive mail about changes made to any related '. - 'object.')); - - $watch_status = id(new PHUIStatusListView()) - ->addItem($watch_item); - - $view->addProperty(pht('Watching'), $watch_status); - } - - if ($project->isUserMember($viewer_phid)) { - $is_silenced = $this->isProjectSilenced($project); - if ($is_silenced) { - $mail_icon = 'fa-envelope-o grey'; - $mail_target = pht('Disabled'); - $mail_note = pht( - 'When mail is sent to project members, you will not receive '. - 'a copy.'); - } else { - $mail_icon = 'fa-envelope-o green'; - $mail_target = pht('Enabled'); - $mail_note = pht( - 'You will receive mail that is sent to project members.'); - } - - $mail_item = id(new PHUIStatusItemView()) - ->setIcon($mail_icon) - ->setTarget(phutil_tag('strong', array(), $mail_target)) - ->setNote($mail_note); - - $mail_status = id(new PHUIStatusListView()) - ->addItem($mail_item); - - $view->addProperty(pht('Mail to Members'), $mail_status); - } - - return $view; - } - private function buildCurtainView(PhabricatorProject $project) { $viewer = $this->getViewer(); $id = $project->getID(); - $curtain = $this->newCurtainView($project); + $curtain = $this->newCurtainView(); $is_locked = $project->getIsMembershipLocked(); @@ -272,6 +170,42 @@ final class PhabricatorProjectMembersViewController ->setDisabled(!$can_lock) ->setWorkflow(true)); + if ($project->isMilestone()) { + $icon_key = PhabricatorProjectIconSet::getMilestoneIconKey(); + $header = PhabricatorProjectIconSet::getIconName($icon_key); + $note = pht( + 'Members of the parent project are members of this project.'); + $show_join = false; + } else if ($project->getHasSubprojects()) { + $header = pht('Parent Project'); + $note = pht( + 'Members of all subprojects are members of this project.'); + $show_join = false; + } else if ($project->getIsMembershipLocked()) { + $header = pht('Locked Project'); + $note = pht( + 'Users with access may join this project, but may not leave.'); + $show_join = true; + } else { + $header = pht('Normal Project'); + $note = pht('Users with access may join and leave this project.'); + $show_join = true; + } + + $curtain->newPanel() + ->setHeaderText($header) + ->appendChild($note); + + if ($show_join) { + $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( + $viewer, + $project); + + $curtain->newPanel() + ->setHeaderText(pht('Joinable By')) + ->appendChild($descriptions[PhabricatorPolicyCapability::CAN_JOIN]); + } + return $curtain; } diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php index c92c843204..29b70cfafc 100644 --- a/src/applications/project/controller/PhabricatorProjectMoveController.php +++ b/src/applications/project/controller/PhabricatorProjectMoveController.php @@ -148,15 +148,22 @@ final class PhabricatorProjectMoveController list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority( $task, $is_after); + + // If we find a priority on the first try, don't keep going. + break; } + $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); + $keyword = head(idx($keyword_map, $pri)); + $xactions = array(); if ($pri !== null) { $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) - ->setNewValue($pri); + ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE) + ->setNewValue($keyword); $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY) + ->setTransactionType( + ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE) ->setNewValue($sub); } diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index dae57e55f9..1f59398930 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -29,7 +29,6 @@ final class PhabricatorProjectProfileController ->setHeader(array($project->getDisplayName(), $tag)) ->setUser($viewer) ->setPolicyObject($project) - ->setImage($picture) ->setProfileHeader(true); if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) { @@ -74,16 +73,28 @@ final class PhabricatorProjectProfileController $stories = id(new PhabricatorFeedQuery()) ->setViewer($viewer) - ->setFilterPHIDs( + ->withFilterPHIDs( array( $project->getPHID(), )) ->setLimit(50) ->execute(); + $view_all = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon( + id(new PHUIIconView()) + ->setIcon('fa-list-ul')) + ->setText(pht('View All')) + ->setHref('/feed/?projectPHIDs='.$project->getPHID()); + + $feed_header = id(new PHUIHeaderView()) + ->setHeader(pht('Recent Activity')) + ->addActionLink($view_all); + $feed = $this->renderStories($stories); $feed = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Recent Activity')) + ->setHeader($feed_header) ->addClass('project-view-feed') ->appendChild($feed); @@ -92,6 +103,7 @@ final class PhabricatorProjectProfileController $home = id(new PHUITwoColumnView()) ->setHeader($header) ->addClass('project-view-home') + ->addClass('project-view-people-home') ->setMainColumn( array( $properties, @@ -113,10 +125,7 @@ final class PhabricatorProjectProfileController ->setCrumbs($crumbs) ->setTitle($project->getDisplayName()) ->setPageObjectPHIDs(array($project->getPHID())) - ->appendChild( - array( - $home, - )); + ->appendChild($home); } private function buildPropertyListView( @@ -138,7 +147,7 @@ final class PhabricatorProjectProfileController } $header = id(new PHUIHeaderView()) - ->setHeader(pht('Properties')); + ->setHeader(pht('Details')); $view = id(new PHUIObjectBoxView()) ->setHeader($header) diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php index 4a89bb4cde..269e80a080 100644 --- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -26,6 +26,9 @@ final class PhabricatorProjectSubprojectsController $allows_subprojects = $project->supportsSubprojects(); $allows_milestones = $project->supportsMilestones(); + $subproject_list = null; + $milestone_list = null; + if ($allows_subprojects) { $subprojects = id(new PhabricatorProjectQuery()) ->setViewer($viewer) @@ -33,6 +36,16 @@ final class PhabricatorProjectSubprojectsController ->needImages(true) ->withIsMilestone(false) ->execute(); + + $subproject_list = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('%s Subprojects', $project->getName())) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList( + id(new PhabricatorProjectListView()) + ->setUser($viewer) + ->setProjects($subprojects) + ->setNoDataString(pht('This project has no subprojects.')) + ->renderList()); } else { $subprojects = array(); } @@ -45,52 +58,25 @@ final class PhabricatorProjectSubprojectsController ->withIsMilestone(true) ->setOrderVector(array('milestoneNumber', 'id')) ->execute(); - } else { - $milestones = array(); - } - if ($milestones) { $milestone_list = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Milestones')) + ->setHeaderText(pht('%s Milestones', $project->getName())) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList( id(new PhabricatorProjectListView()) ->setUser($viewer) ->setProjects($milestones) + ->setNoDataString(pht('This project has no milestones.')) ->renderList()); } else { - $milestone_list = null; + $milestones = array(); } - if ($subprojects) { - $subproject_list = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Subprojects')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setObjectList( - id(new PhabricatorProjectListView()) - ->setUser($viewer) - ->setProjects($subprojects) - ->renderList()); - } else { - $subproject_list = null; - } - - $property_list = $this->buildPropertyList( - $project, - $milestones, - $subprojects); - $curtain = $this->buildCurtainView( $project, $milestones, $subprojects); - - $details = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($property_list); - $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorProject::ITEM_SUBPROJECTS); @@ -102,11 +88,24 @@ final class PhabricatorProjectSubprojectsController ->setHeader(pht('Subprojects and Milestones')) ->setHeaderIcon('fa-sitemap'); + require_celerity_resource('project-view-css'); + + // This page isn't reachable via UI, but make it pretty anyways. + $info_view = null; + if (!$milestone_list && !$subproject_list) { + $info_view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild(pht('Milestone projects do not support subprojects '. + 'or milestones.')); + } + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) + ->addClass('project-view-home') + ->addClass('project-view-people-home') ->setMainColumn(array( - $details, + $info_view, $milestone_list, $subproject_list, )); @@ -118,73 +117,6 @@ final class PhabricatorProjectSubprojectsController ->appendChild($view); } - private function buildPropertyList( - PhabricatorProject $project, - array $milestones, - array $subprojects) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer); - - $view->addProperty( - pht('Prototype'), - $this->renderStatus( - 'fa-exclamation-triangle red', - pht('Warning'), - pht('Subprojects and milestones are only partially implemented.'))); - - if (!$project->supportsMilestones()) { - $milestone_status = $this->renderStatus( - 'fa-times grey', - pht('Already Milestone'), - pht( - 'This project is already a milestone, and milestones may not '. - 'have their own milestones.')); - } else { - if (!$milestones) { - $milestone_status = $this->renderStatus( - 'fa-check grey', - pht('None Created'), - pht( - 'You can create milestones for this project.')); - } else { - $milestone_status = $this->renderStatus( - 'fa-check green', - pht('Has Milestones'), - pht('This project has milestones.')); - } - } - - $view->addProperty(pht('Milestones'), $milestone_status); - - if (!$project->supportsSubprojects()) { - $subproject_status = $this->renderStatus( - 'fa-times grey', - pht('Milestone'), - pht( - 'This project is a milestone, and milestones may not have '. - 'subprojects.')); - } else { - if (!$subprojects) { - $subproject_status = $this->renderStatus( - 'fa-check grey', - pht('None Created'), - pht('You can create subprojects for this project.')); - } else { - $subproject_status = $this->renderStatus( - 'fa-check green', - pht('Has Subprojects'), - pht( - 'This project has subprojects.')); - } - } - - $view->addProperty(pht('Subprojects'), $subproject_status); - - return $view; - } - private function buildCurtainView( PhabricatorProject $project, array $milestones, @@ -203,7 +135,7 @@ final class PhabricatorProjectSubprojectsController $allows_milestones = $project->supportsMilestones(); $allows_subprojects = $project->supportsSubprojects(); - $curtain = $this->newCurtainView($project); + $curtain = $this->newCurtainView(); if ($allows_milestones && $milestones) { $milestone_text = pht('Create Next Milestone'); @@ -244,6 +176,39 @@ final class PhabricatorProjectSubprojectsController ->setDisabled($subproject_disabled) ->setWorkflow($subproject_workflow)); + + if (!$project->supportsMilestones()) { + $note = pht( + 'This project is already a milestone, and milestones may not '. + 'have their own milestones.'); + } else { + if (!$milestones) { + $note = pht('Milestones can be created for this project.'); + } else { + $note = pht('This project has milestones.'); + } + } + + $curtain->newPanel() + ->setHeaderText(pht('Milestones')) + ->appendChild($note); + + if (!$project->supportsSubprojects()) { + $note = pht( + 'This project is a milestone, and milestones may not have '. + 'subprojects.'); + } else { + if (!$subprojects) { + $note = pht('Subprojects can be created for this project.'); + } else { + $note = pht('This project has subprojects.'); + } + } + + $curtain->newPanel() + ->setHeaderText(pht('Subprojects')) + ->appendChild($note); + return $curtain; } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index aa8c0852ab..adf4a3138b 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -10,7 +10,7 @@ final class PhabricatorProjectTransactionEditor return $this; } - private function getIsMilestone() { + public function getIsMilestone() { return $this->isMilestone; } @@ -22,6 +22,14 @@ final class PhabricatorProjectTransactionEditor return pht('Projects'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this project.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); @@ -30,193 +38,9 @@ final class PhabricatorProjectTransactionEditor $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; - $types[] = PhabricatorProjectTransaction::TYPE_NAME; - $types[] = PhabricatorProjectTransaction::TYPE_SLUGS; - $types[] = PhabricatorProjectTransaction::TYPE_STATUS; - $types[] = PhabricatorProjectTransaction::TYPE_IMAGE; - $types[] = PhabricatorProjectTransaction::TYPE_ICON; - $types[] = PhabricatorProjectTransaction::TYPE_COLOR; - $types[] = PhabricatorProjectTransaction::TYPE_LOCKED; - $types[] = PhabricatorProjectTransaction::TYPE_PARENT; - $types[] = PhabricatorProjectTransaction::TYPE_MILESTONE; - $types[] = PhabricatorProjectTransaction::TYPE_HASWORKBOARD; - $types[] = PhabricatorProjectTransaction::TYPE_DEFAULT_SORT; - $types[] = PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER; - $types[] = PhabricatorProjectTransaction::TYPE_BACKGROUND; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: - return $object->getName(); - case PhabricatorProjectTransaction::TYPE_SLUGS: - $slugs = $object->getSlugs(); - $slugs = mpull($slugs, 'getSlug', 'getSlug'); - unset($slugs[$object->getPrimarySlug()]); - return array_keys($slugs); - case PhabricatorProjectTransaction::TYPE_STATUS: - return $object->getStatus(); - case PhabricatorProjectTransaction::TYPE_IMAGE: - return $object->getProfileImagePHID(); - case PhabricatorProjectTransaction::TYPE_ICON: - return $object->getIcon(); - case PhabricatorProjectTransaction::TYPE_COLOR: - return $object->getColor(); - case PhabricatorProjectTransaction::TYPE_LOCKED: - return (int)$object->getIsMembershipLocked(); - case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: - return (int)$object->getHasWorkboard(); - case PhabricatorProjectTransaction::TYPE_PARENT: - case PhabricatorProjectTransaction::TYPE_MILESTONE: - return null; - case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: - return $object->getDefaultWorkboardSort(); - case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER: - return $object->getDefaultWorkboardFilter(); - case PhabricatorProjectTransaction::TYPE_BACKGROUND: - return $object->getWorkboardBackgroundColor(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: - 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: - case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: - case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER: - return $xaction->getNewValue(); - case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: - return (int)$xaction->getNewValue(); - case PhabricatorProjectTransaction::TYPE_BACKGROUND: - $value = $xaction->getNewValue(); - if (!strlen($value)) { - return null; - } - return $value; - case PhabricatorProjectTransaction::TYPE_SLUGS: - return $this->normalizeSlugs($xaction->getNewValue()); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: - $name = $xaction->getNewValue(); - $object->setName($name); - if (!$this->getIsMilestone()) { - $object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($name)); - } - return; - case PhabricatorProjectTransaction::TYPE_SLUGS: - return; - case PhabricatorProjectTransaction::TYPE_STATUS: - $object->setStatus($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_IMAGE: - $object->setProfileImagePHID($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_ICON: - $object->setIcon($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_COLOR: - $object->setColor($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_LOCKED: - $object->setIsMembershipLocked($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_PARENT: - $object->setParentProjectPHID($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_MILESTONE: - $number = $object->getParentProject()->loadNextMilestoneNumber(); - $object->setMilestoneNumber($number); - $object->setParentProjectPHID($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: - $object->setHasWorkboard($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: - $object->setDefaultWorkboardSort($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER: - $object->setDefaultWorkboardFilter($xaction->getNewValue()); - return; - case PhabricatorProjectTransaction::TYPE_BACKGROUND: - $object->setWorkboardBackgroundColor($xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: - // First, add the old name as a secondary slug; this is helpful - // for renames and generally a good thing to do. - if (!$this->getIsMilestone()) { - if ($old !== null) { - $this->addSlug($object, $old, false); - } - $this->addSlug($object, $new, false); - } - return; - case PhabricatorProjectTransaction::TYPE_SLUGS: - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - 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: - case PhabricatorProjectTransaction::TYPE_HASWORKBOARD: - case PhabricatorProjectTransaction::TYPE_DEFAULT_SORT: - case PhabricatorProjectTransaction::TYPE_DEFAULT_FILTER: - case PhabricatorProjectTransaction::TYPE_BACKGROUND: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - protected function validateAllTransactions( PhabricatorLiskDAO $object, array $xactions) { @@ -228,8 +52,8 @@ final class PhabricatorProjectTransactionEditor $parent_xaction = null; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_PARENT: - case PhabricatorProjectTransaction::TYPE_MILESTONE: + case PhabricatorProjectParentTransaction::TRANSACTIONTYPE: + case PhabricatorProjectMilestoneTransaction::TRANSACTIONTYPE: if ($xaction->getNewValue() === null) { continue; } @@ -291,223 +115,22 @@ final class PhabricatorProjectTransactionEditor return $errors; } - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhabricatorProjectTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Project name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - - if (!$xactions) { - break; - } - - if ($this->getIsMilestone()) { - break; - } - - $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; - } - - $slug = PhabricatorSlug::normalizeProjectSlug($name); - - $slug_used_already = id(new PhabricatorProjectSlug()) - ->loadOneWhere('slug = %s', $slug); - if ($slug_used_already && - $slug_used_already->getProjectPHID() != $object->getPHID()) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Duplicate'), - pht( - 'Project name generates the same hashtag ("%s") as another '. - 'existing project. Choose a unique name.', - '#'.$slug), - nonempty(last($xactions), null)); - $errors[] = $error; - } - break; - case PhabricatorProjectTransaction::TYPE_SLUGS: - if (!$xactions) { - break; - } - - $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); - } else { - // The project doesn't have any extra slugs. - $slugs_used_already = array(); - } - - $slugs_used_already = mgroup($slugs_used_already, 'getProjectPHID'); - foreach ($slugs_used_already as $project_phid => $used_slugs) { - if ($project_phid == $object->getPHID()) { - continue; - } - - $used_slug_strs = mpull($used_slugs, 'getSlug'); - - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - '%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: - case PhabricatorProjectTransaction::TYPE_MILESTONE: - if (!$xactions) { - break; - } - - $xaction = last($xactions); - - $parent_phid = $xaction->getNewValue(); - if (!$parent_phid) { - continue; - } - - if (!$this->getIsNewObject()) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'You can only set a parent or milestone project when creating a '. - 'project for the first time.'), - $xaction); - break; - } - - $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 or milestone 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 or milestone project PHID ("%s") must not be a '. - 'milestone. Milestones may not have subprojects or milestones.', - $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 or mielstone 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; - } - - protected function requireCapabilities( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_NAME: - case PhabricatorProjectTransaction::TYPE_STATUS: - case PhabricatorProjectTransaction::TYPE_IMAGE: - case PhabricatorProjectTransaction::TYPE_ICON: - case PhabricatorProjectTransaction::TYPE_COLOR: + case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: + case PhabricatorProjectStatusTransaction::TRANSACTIONTYPE: + case PhabricatorProjectImageTransaction::TRANSACTIONTYPE: + case PhabricatorProjectIconTransaction::TRANSACTIONTYPE: + case PhabricatorProjectColorTransaction::TRANSACTIONTYPE: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, PhabricatorPolicyCapability::CAN_EDIT); return; - case PhabricatorProjectTransaction::TYPE_LOCKED: + case PhabricatorProjectLockTransaction::TRANSACTIONTYPE: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), newv($this->getEditorApplicationClass(), array()), @@ -638,22 +261,6 @@ final class PhabricatorProjectTransactionEditor return true; } - protected function extractFilePHIDsFromCustomTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_IMAGE: - $new = $xaction->getNewValue(); - if ($new) { - return array($new); - } - break; - } - - return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); - } - protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { @@ -669,8 +276,8 @@ final class PhabricatorProjectTransactionEditor break; } break; - case PhabricatorProjectTransaction::TYPE_PARENT: - case PhabricatorProjectTransaction::TYPE_MILESTONE: + case PhabricatorProjectParentTransaction::TRANSACTIONTYPE: + case PhabricatorProjectMilestoneTransaction::TRANSACTIONTYPE: $materialize = true; $new_parent = $object->getParentProject(); break; @@ -697,10 +304,6 @@ final class PhabricatorProjectTransactionEditor } } - if ($this->getIsNewObject()) { - $this->setDefaultProfilePicture($object); - } - // TODO: We should dump an informational transaction onto the parent // project to show that we created the sub-thing. @@ -717,7 +320,7 @@ final class PhabricatorProjectTransactionEditor return parent::applyFinalEffects($object, $xactions); } - private function addSlug(PhabricatorProject $project, $slug, $force) { + public function addSlug(PhabricatorProject $project, $slug, $force) { $slug = PhabricatorSlug::normalizeProjectSlug($slug); $table = new PhabricatorProjectSlug(); $project_phid = $project->getPHID(); @@ -748,7 +351,7 @@ final class PhabricatorProjectTransactionEditor ->save(); } - private function removeSlugs(PhabricatorProject $project, array $slugs) { + public function removeSlugs(PhabricatorProject $project, array $slugs) { if (!$slugs) { return; } @@ -770,7 +373,7 @@ final class PhabricatorProjectTransactionEditor } } - private function normalizeSlugs(array $slugs) { + public function normalizeSlugs(array $slugs) { foreach ($slugs as $key => $slug) { $slugs[$key] = PhabricatorSlug::normalizeProjectSlug($slug); } @@ -845,7 +448,7 @@ final class PhabricatorProjectTransactionEditor $is_milestone = $object->isMilestone(); foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_MILESTONE: + case PhabricatorProjectMilestoneTransaction::TRANSACTIONTYPE: if ($xaction->getNewValue() !== null) { $is_milestone = true; } @@ -858,34 +461,6 @@ final class PhabricatorProjectTransactionEditor return $results; } - private function setDefaultProfilePicture(PhabricatorProject $project) { - if ($project->isMilestone()) { - return; - } - - $compose_color = $project->getDisplayIconComposeColor(); - $compose_icon = $project->getDisplayIconComposeIcon(); - - $builtin = id(new PhabricatorFilesComposeIconBuiltinFile()) - ->setColor($compose_color) - ->setIcon($compose_icon); - - $data = $builtin->loadBuiltinFileData(); - - $file = PhabricatorFile::newFromFileData( - $data, - array( - 'name' => $builtin->getBuiltinDisplayName(), - 'profile' => true, - 'canCDN' => true, - )); - - $project - ->setProfileImagePHID($file->getPHID()) - ->save(); - } - - protected function shouldApplyHeraldRules( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php index 286e9bab0f..a7702c345f 100644 --- a/src/applications/project/engine/PhabricatorProjectEditEngine.php +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -112,8 +112,8 @@ final class PhabricatorProjectEditEngine PhabricatorTransactions::TYPE_VIEW_POLICY, PhabricatorTransactions::TYPE_EDIT_POLICY, PhabricatorTransactions::TYPE_JOIN_POLICY, - PhabricatorProjectTransaction::TYPE_ICON, - PhabricatorProjectTransaction::TYPE_COLOR, + PhabricatorProjectIconTransaction::TRANSACTIONTYPE, + PhabricatorProjectColorTransaction::TRANSACTIONTYPE, ); $unavailable = array_fuse($unavailable); @@ -202,7 +202,8 @@ final class PhabricatorProjectEditEngine pht('Choose a parent project to create a subproject beneath.')) ->setConduitTypeDescription(pht('PHID of the parent project.')) ->setAliases(array('parentPHID')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT) + ->setTransactionType( + PhabricatorProjectParentTransaction::TRANSACTIONTYPE) ->setHandleParameterType(new AphrontPHIDHTTPParameterType()) ->setSingleValue($parent_phid) ->setIsReorderable(false) @@ -217,7 +218,8 @@ final class PhabricatorProjectEditEngine pht('Choose a parent project to create a new milestone for.')) ->setConduitTypeDescription(pht('PHID of the parent project.')) ->setAliases(array('milestonePHID')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE) + ->setTransactionType( + PhabricatorProjectMilestoneTransaction::TRANSACTIONTYPE) ->setHandleParameterType(new AphrontPHIDHTTPParameterType()) ->setSingleValue($milestone_phid) ->setIsReorderable(false) @@ -235,7 +237,7 @@ final class PhabricatorProjectEditEngine id(new PhabricatorTextEditField()) ->setKey('name') ->setLabel(pht('Name')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setTransactionType(PhabricatorProjectNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setDescription(pht('Project name.')) ->setConduitDescription(pht('Rename the project')) @@ -244,7 +246,8 @@ final class PhabricatorProjectEditEngine id(new PhabricatorIconSetEditField()) ->setKey('icon') ->setLabel(pht('Icon')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_ICON) + ->setTransactionType( + PhabricatorProjectIconTransaction::TRANSACTIONTYPE) ->setIconSet(new PhabricatorProjectIconSet()) ->setDescription(pht('Project icon.')) ->setConduitDescription(pht('Change the project icon.')) @@ -253,7 +256,8 @@ final class PhabricatorProjectEditEngine id(new PhabricatorSelectEditField()) ->setKey('color') ->setLabel(pht('Color')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_COLOR) + ->setTransactionType( + PhabricatorProjectColorTransaction::TRANSACTIONTYPE) ->setOptions(PhabricatorProjectIconSet::getColorMap()) ->setDescription(pht('Project tag color.')) ->setConduitDescription(pht('Change the project tag color.')) @@ -262,7 +266,8 @@ final class PhabricatorProjectEditEngine id(new PhabricatorStringListEditField()) ->setKey('slugs') ->setLabel(pht('Additional Hashtags')) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setTransactionType( + PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE) ->setDescription(pht('Additional project slugs.')) ->setConduitDescription(pht('Change project slugs.')) ->setConduitTypeDescription(pht('New list of slugs.')) diff --git a/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php b/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php index ef16b2eee8..77b69443da 100644 --- a/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php +++ b/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php @@ -20,6 +20,10 @@ final class PhabricatorProjectProfileMenuEngine protected function getBuiltinProfileItems($object) { $items = array(); + $items[] = $this->newItem() + ->setBuiltinKey(PhabricatorProject::ITEM_PICTURE) + ->setMenuItemKey(PhabricatorProjectPictureProfileMenuItem::MENUITEMKEY); + $items[] = $this->newItem() ->setBuiltinKey(PhabricatorProject::ITEM_PROFILE) ->setMenuItemKey(PhabricatorProjectDetailsProfileMenuItem::MENUITEMKEY); diff --git a/src/applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php b/src/applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php index 857783f811..79f395ebaa 100644 --- a/src/applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php +++ b/src/applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php @@ -9,11 +9,11 @@ final class PhabricatorProjectsFulltextEngineExtension return pht('Projects'); } - public function shouldIndexFulltextObject($object) { + public function shouldEnrichFulltextObject($object) { return ($object instanceof PhabricatorProjectInterface); } - public function indexFulltextObject( + public function enrichFulltextObject( $object, PhabricatorSearchAbstractDocument $document) { diff --git a/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php b/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php index 567f5b749e..5acfaf913a 100644 --- a/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php +++ b/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php @@ -34,29 +34,30 @@ final class PhabricatorProjectsMembershipIndexEngineExtension } 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()); + if ($project->isMilestone()) { + $source_phids = array($project->getParentProjectPHID()); $has_subprojects = false; + } else { + $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'); diff --git a/src/applications/project/icon/PhabricatorProjectIconSet.php b/src/applications/project/icon/PhabricatorProjectIconSet.php index 2283026598..b128a35cad 100644 --- a/src/applications/project/icon/PhabricatorProjectIconSet.php +++ b/src/applications/project/icon/PhabricatorProjectIconSet.php @@ -18,87 +18,104 @@ final class PhabricatorProjectIconSet 'icon' => 'fa-briefcase', 'name' => pht('Project'), 'default' => true, + 'image' => 'v3/briefcase.png', ), array( 'key' => 'tag', 'icon' => 'fa-tags', 'name' => pht('Tag'), + 'image' => 'v3/tag.png', ), array( 'key' => 'policy', 'icon' => 'fa-lock', 'name' => pht('Policy'), + 'image' => 'v3/lock.png', ), array( 'key' => 'group', 'icon' => 'fa-users', 'name' => pht('Group'), + 'image' => 'v3/people.png', ), array( 'key' => 'folder', 'icon' => 'fa-folder', 'name' => pht('Folder'), + 'image' => 'v3/folder.png', ), array( 'key' => 'timeline', 'icon' => 'fa-calendar', 'name' => pht('Timeline'), + 'image' => 'v3/calendar.png', ), array( 'key' => 'goal', 'icon' => 'fa-flag-checkered', 'name' => pht('Goal'), + 'image' => 'v3/flag.png', ), array( 'key' => 'release', 'icon' => 'fa-truck', 'name' => pht('Release'), + 'image' => 'v3/truck.png', ), array( 'key' => 'bugs', 'icon' => 'fa-bug', 'name' => pht('Bugs'), + 'image' => 'v3/bug.png', ), array( 'key' => 'cleanup', 'icon' => 'fa-trash-o', 'name' => pht('Cleanup'), + 'image' => 'v3/trash.png', ), array( 'key' => 'umbrella', 'icon' => 'fa-umbrella', 'name' => pht('Umbrella'), + 'image' => 'v3/umbrella.png', ), array( 'key' => 'communication', 'icon' => 'fa-envelope', 'name' => pht('Communication'), + 'image' => 'v3/mail.png', ), array( 'key' => 'organization', 'icon' => 'fa-building', 'name' => pht('Organization'), + 'image' => 'v3/organization.png', ), array( 'key' => 'infrastructure', 'icon' => 'fa-cloud', 'name' => pht('Infrastructure'), + 'image' => 'v3/cloud.png', ), array( 'key' => 'account', 'icon' => 'fa-credit-card', 'name' => pht('Account'), + 'image' => 'v3/creditcard.png', ), array( 'key' => 'experimental', 'icon' => 'fa-flask', 'name' => pht('Experimental'), + 'image' => 'v3/experimental.png', ), array( 'key' => 'milestone', 'icon' => 'fa-map-marker', 'name' => pht('Milestone'), 'special' => self::SPECIAL_MILESTONE, + 'image' => 'v3/marker.png', ), ); } @@ -149,6 +166,11 @@ final class PhabricatorProjectIconSet return idx($spec, 'name', null); } + public static function getIconImage($key) { + $spec = self::getIconSpec($key); + return idx($spec, 'image', 'v3/briefcase.png'); + } + private static function getIconSpec($key) { $icons = self::getIconSpecifications(); foreach ($icons as $icon) { @@ -190,6 +212,7 @@ final class PhabricatorProjectIconSet 'key' => 'string', 'name' => 'string', 'icon' => 'string', + 'image' => 'optional string', 'special' => 'optional string', 'disabled' => 'optional bool', 'default' => 'optional bool', @@ -239,6 +262,26 @@ final class PhabricatorProjectIconSet $is_disabled = idx($value, 'disabled'); + $image = idx($value, 'image'); + if ($image !== null) { + $builtin = idx($value, 'image'); + $builtin_map = id(new PhabricatorFilesOnDiskBuiltinFile()) + ->getProjectBuiltinFiles(); + $builtin_map = array_flip($builtin_map); + + $root = dirname(phutil_get_library_root('phabricator')); + $image = $root.'/resources/builtin/projects/'.$builtin; + + if (!array_key_exists($image, $builtin_map)) { + throw new Exception( + pht( + 'The project image ("%s") specified for ("%s") '. + 'was not found in the folder "resources/builtin/projects/".', + $builtin, + $key)); + } + } + if (idx($value, 'default')) { if ($default === null) { if ($is_disabled) { diff --git a/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php b/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php index 0fd5bd66e7..e29ee13f2d 100644 --- a/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php +++ b/src/applications/project/lipsum/PhabricatorProjectTestDataGenerator.php @@ -16,11 +16,11 @@ final class PhabricatorProjectTestDataGenerator $xactions = array(); $xactions[] = $this->newTransaction( - PhabricatorProjectTransaction::TYPE_NAME, + PhabricatorProjectNameTransaction::TRANSACTIONTYPE, $this->newProjectTitle()); $xactions[] = $this->newTransaction( - PhabricatorProjectTransaction::TYPE_STATUS, + PhabricatorProjectStatusTransaction::TRANSACTIONTYPE, $this->newProjectStatus()); // Almost always make the author a member. diff --git a/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php index becdd36c16..96619b8ec5 100644 --- a/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php +++ b/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php @@ -46,15 +46,15 @@ final class PhabricatorProjectDetailsProfileMenuItem $project = $config->getProfileObject(); $id = $project->getID(); - $picture = $project->getProfileImageURI(); $name = $project->getName(); + $icon = $project->getDisplayIconIcon(); $href = "/project/profile/{$id}/"; $item = $this->newItem() ->setHref($href) ->setName($name) - ->setProfileImage($picture); + ->setIcon($icon); return array( $item, diff --git a/src/applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php new file mode 100644 index 0000000000..2201687c81 --- /dev/null +++ b/src/applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php @@ -0,0 +1,69 @@ +getDefaultName(); + } + + public function buildEditEngineFields( + PhabricatorProfileMenuItemConfiguration $config) { + return array(); + } + + protected function newNavigationMenuItems( + PhabricatorProfileMenuItemConfiguration $config) { + + $project = $config->getProfileObject(); + require_celerity_resource('people-picture-menu-item-css'); + + $picture = $project->getProfileImageURI(); + $href = $project->getProfileURI(); + + $classes = array(); + $classes[] = 'people-menu-image'; + if ($project->isArchived()) { + $classes[] = 'phui-image-disabled'; + } + + $photo = phutil_tag( + 'img', + array( + 'src' => $picture, + 'class' => implode(' ', $classes), + )); + + $view = phutil_tag_div('people-menu-image-container', $photo); + $view = phutil_tag( + 'a', + array( + 'href' => $href, + ), + $view); + + $item = $this->newItem() + ->appendChild($view); + + return array( + $item, + ); + } + +} diff --git a/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php b/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php index 7a04103a7c..3aa6088780 100644 --- a/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php +++ b/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php @@ -47,6 +47,10 @@ final class PhabricatorProjectProjectPHIDType extends PhabricatorPHIDType { $handle->setObjectName('#'.$slug); $handle->setURI("/tag/{$slug}/"); } else { + // We set the name to the project's PHID to avoid a parse error when a + // project has no hashtag (as is the case with milestones by default). + // See T12659 for more details + $handle->setCommandLineObjectName($project->getPHID()); $handle->setURI("/project/view/{$id}/"); } diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index 44055b7260..955eba9972 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -358,8 +358,6 @@ final class PhabricatorProjectQuery protected function didFilterPage(array $projects) { if ($this->needImages) { - $default = null; - $file_phids = mpull($projects, 'getProfileImagePHID'); $file_phids = array_filter($file_phids); if ($file_phids) { @@ -376,12 +374,10 @@ final class PhabricatorProjectQuery foreach ($projects as $project) { $file = idx($files, $project->getProfileImagePHID()); if (!$file) { - if (!$default) { - $default = PhabricatorFile::loadBuiltin( - $this->getViewer(), - 'project.png'); - } - $file = $default; + $builtin = PhabricatorProjectIconSet::getIconImage( + $project->getIcon()); + $file = PhabricatorFile::loadBuiltin($this->getViewer(), + 'projects/'.$builtin); } $project->attachProfileImageFile($file); } @@ -613,7 +609,8 @@ final class PhabricatorProjectQuery } if ($this->nameTokens !== null) { - foreach ($this->nameTokens as $key => $token) { + $name_tokens = $this->getNameTokensForQuery($this->nameTokens); + foreach ($name_tokens as $key => $token) { $token_table = 'token_'.$key; $joins[] = qsprintf( $conn, @@ -801,4 +798,22 @@ final class PhabricatorProjectQuery } } + private function getNameTokensForQuery(array $tokens) { + // When querying for projects by name, only actually search for the five + // longest tokens. MySQL can get grumpy with a large number of JOINs + // with LIKEs and queries for more than 5 tokens are essentially never + // legitimate searches for projects, but users copy/pasting nonsense. + // See also PHI47. + + $length_map = array(); + foreach ($tokens as $token) { + $length_map[$token] = strlen($token); + } + arsort($length_map); + + $length_map = array_slice($length_map, 0, 5, true); + + return array_keys($length_map); + } + } diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 97a12f8966..e1cad29e67 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -13,7 +13,9 @@ final class PhabricatorProjectSearchEngine public function newQuery() { return id(new PhabricatorProjectQuery()) - ->needImages(true); + ->needImages(true) + ->needMembers(true) + ->needWatchers(true); } protected function buildCustomSearchFields() { @@ -130,6 +132,10 @@ final class PhabricatorProjectSearchEngine $names['joined'] = pht('Joined'); } + if ($this->requireViewer()->isLoggedIn()) { + $names['watching'] = pht('Watching'); + } + $names['all'] = pht('All'); return $names; @@ -154,6 +160,10 @@ final class PhabricatorProjectSearchEngine return $query ->setParameter('memberPHIDs', array($viewer_phid)) ->setParameter('status', 'active'); + case 'watching': + return $query + ->setParameter('watcherPHIDs', array($viewer_phid)) + ->setParameter('status', 'active'); } return parent::buildSavedQueryFromBuiltin($query_key); @@ -202,7 +212,7 @@ final class PhabricatorProjectSearchEngine $options[$color] = array( id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($color) + ->setColor($color) ->setName($name), ); } @@ -220,6 +230,8 @@ final class PhabricatorProjectSearchEngine $list = id(new PhabricatorProjectListView()) ->setUser($viewer) ->setProjects($projects) + ->setShowWatching(true) + ->setShowMember(true) ->renderList(); return id(new PhabricatorApplicationSearchResultView()) diff --git a/src/applications/project/search/PhabricatorProjectFerretEngine.php b/src/applications/project/search/PhabricatorProjectFerretEngine.php new file mode 100644 index 0000000000..459fc24026 --- /dev/null +++ b/src/applications/project/search/PhabricatorProjectFerretEngine.php @@ -0,0 +1,18 @@ +getOldValue(); $new = $this->getNewValue(); @@ -44,31 +34,11 @@ final class PhabricatorProjectTransaction $rem = array_diff($old, $new); $req_phids = array_merge($add, $rem); break; - case self::TYPE_IMAGE: - $req_phids[] = $old; - $req_phids[] = $new; - break; } return array_merge($req_phids, parent::getRequiredHandlePHIDs()); } - public function getColor() { - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_STATUS: - if ($old == 0) { - return 'red'; - } else { - return 'green'; - } - } - return parent::getColor(); - } - public function shouldHide() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_EDGE: @@ -84,24 +54,12 @@ final class PhabricatorProjectTransaction return parent::shouldHide(); } - public function shouldHideForFeed() { - switch ($this->getTransactionType()) { - case self::TYPE_HASWORKBOARD: - case self::TYPE_DEFAULT_SORT: - case self::TYPE_DEFAULT_FILTER: - case self::TYPE_BACKGROUND: - return true; - } - - return parent::shouldHideForFeed(); - } - public function shouldHideForMail(array $xactions) { switch ($this->getTransactionType()) { - case self::TYPE_HASWORKBOARD: - case self::TYPE_DEFAULT_SORT: - case self::TYPE_DEFAULT_FILTER: - case self::TYPE_BACKGROUND: + case PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE: + case PhabricatorProjectSortTransaction::TRANSACTIONTYPE: + case PhabricatorProjectFilterTransaction::TRANSACTIONTYPE: + case PhabricatorProjectWorkboardBackgroundTransaction::TRANSACTIONTYPE: return true; } @@ -109,30 +67,9 @@ final class PhabricatorProjectTransaction } public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - switch ($this->getTransactionType()) { - case self::TYPE_STATUS: - if ($old == 0) { - return 'fa-ban'; - } else { - return 'fa-check'; - } - case self::TYPE_LOCKED: - if ($new) { - return 'fa-lock'; - } else { - return 'fa-unlock'; - } - case self::TYPE_ICON: - return PhabricatorProjectIconSet::getIconIcon($new); - case self::TYPE_IMAGE: - return 'fa-photo'; case self::TYPE_MEMBERS: return 'fa-user'; - case self::TYPE_SLUGS: - return 'fa-tag'; } return parent::getIcon(); } @@ -149,107 +86,6 @@ final class PhabricatorProjectTransaction '%s created this project.', $this->renderHandleLink($author_phid)); - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this project.', - $author_handle); - } else { - return pht( - '%s renamed this project from "%s" to "%s".', - $author_handle, - $old, - $new); - } - break; - - case self::TYPE_STATUS: - if ($old == 0) { - return pht( - '%s archived this project.', - $author_handle); - } else { - return pht( - '%s activated this project.', - $author_handle); - } - break; - - case self::TYPE_IMAGE: - // TODO: Some day, it would be nice to show the images. - if (!$old) { - return pht( - "%s set this project's image to %s.", - $author_handle, - $this->renderHandleLink($new)); - } else if (!$new) { - return pht( - "%s removed this project's image.", - $author_handle); - } else { - return pht( - "%s updated this project's image from %s to %s.", - $author_handle, - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - break; - - case self::TYPE_ICON: - $set = new PhabricatorProjectIconSet(); - - return pht( - "%s set this project's icon to %s.", - $author_handle, - $set->getIconLabel($new)); - break; - - case self::TYPE_COLOR: - return pht( - "%s set this project's color to %s.", - $author_handle, - PHUITagView::getShadeName($new)); - break; - - case self::TYPE_LOCKED: - if ($new) { - return pht( - "%s locked this project's membership.", - $author_handle); - } else { - return pht( - "%s unlocked this project's membership.", - $author_handle); - } - break; - - case self::TYPE_SLUGS: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - if ($add && $rem) { - return pht( - '%s changed project hashtag(s), added %d: %s; removed %d: %s.', - $author_handle, - count($add), - $this->renderSlugList($add), - count($rem), - $this->renderSlugList($rem)); - } else if ($add) { - return pht( - '%s added %d project hashtag(s): %s.', - $author_handle, - count($add), - $this->renderSlugList($add)); - } else if ($rem) { - return pht( - '%s removed %d project hashtag(s): %s.', - $author_handle, - count($rem), - $this->renderSlugList($rem)); - } - break; - case self::TYPE_MEMBERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -288,166 +124,19 @@ final class PhabricatorProjectTransaction } } break; - - case self::TYPE_HASWORKBOARD: - if ($new) { - return pht( - '%s enabled the workboard for this project.', - $author_handle); - } else { - return pht( - '%s disabled the workboard for this project.', - $author_handle); - } - - case self::TYPE_DEFAULT_SORT: - return pht( - '%s changed the default sort order for the project workboard.', - $author_handle); - - case self::TYPE_DEFAULT_FILTER: - return pht( - '%s changed the default filter for the project workboard.', - $author_handle); - - case self::TYPE_BACKGROUND: - return pht( - '%s changed the background color of the project workboard.', - $author_handle); } return parent::getTitle(); } - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - $author_handle = $this->renderHandleLink($author_phid); - $object_handle = $this->renderHandleLink($object_phid); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created %s.', - $author_handle, - $object_handle); - } else { - return pht( - '%s renamed %s from "%s" to "%s".', - $author_handle, - $object_handle, - $old, - $new); - } - case self::TYPE_STATUS: - if ($old == 0) { - return pht( - '%s archived %s.', - $author_handle, - $object_handle); - } else { - return pht( - '%s activated %s.', - $author_handle, - $object_handle); - } - case self::TYPE_IMAGE: - // TODO: Some day, it would be nice to show the images. - if (!$old) { - return pht( - '%s set the image for %s to %s.', - $author_handle, - $object_handle, - $this->renderHandleLink($new)); - } else if (!$new) { - return pht( - '%s removed the image for %s.', - $author_handle, - $object_handle); - } else { - return pht( - '%s updated the image for %s from %s to %s.', - $author_handle, - $object_handle, - $this->renderHandleLink($old), - $this->renderHandleLink($new)); - } - - case self::TYPE_ICON: - $set = new PhabricatorProjectIconSet(); - - return pht( - '%s set the icon for %s to %s.', - $author_handle, - $object_handle, - $set->getIconLabel($new)); - - case self::TYPE_COLOR: - return pht( - '%s set the color for %s to %s.', - $author_handle, - $object_handle, - PHUITagView::getShadeName($new)); - - case self::TYPE_LOCKED: - if ($new) { - return pht( - '%s locked %s membership.', - $author_handle, - $object_handle); - } else { - return pht( - '%s unlocked %s membership.', - $author_handle, - $object_handle); - } - - case self::TYPE_SLUGS: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - if ($add && $rem) { - return pht( - '%s changed %s hashtag(s), added %d: %s; removed %d: %s.', - $author_handle, - $object_handle, - count($add), - $this->renderSlugList($add), - count($rem), - $this->renderSlugList($rem)); - } else if ($add) { - return pht( - '%s added %d %s hashtag(s): %s.', - $author_handle, - count($add), - $object_handle, - $this->renderSlugList($add)); - } else if ($rem) { - return pht( - '%s removed %d %s hashtag(s): %s.', - $author_handle, - count($rem), - $object_handle, - $this->renderSlugList($rem)); - } - - } - - return parent::getTitleForFeed(); - } - public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { - case self::TYPE_NAME: - case self::TYPE_SLUGS: - case self::TYPE_IMAGE: - case self::TYPE_ICON: - case self::TYPE_COLOR: + case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: + case PhabricatorProjectSlugsTransaction::TRANSACTIONTYPE: + case PhabricatorProjectImageTransaction::TRANSACTIONTYPE: + case PhabricatorProjectIconTransaction::TRANSACTIONTYPE: + case PhabricatorProjectColorTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_METADATA; break; case PhabricatorTransactions::TYPE_EDGE: @@ -463,8 +152,8 @@ final class PhabricatorProjectTransaction $tags[] = self::MAILTAG_OTHER; } break; - case self::TYPE_STATUS: - case self::TYPE_LOCKED: + case PhabricatorProjectStatusTransaction::TRANSACTIONTYPE: + case PhabricatorProjectLockTransaction::TRANSACTIONTYPE: default: $tags[] = self::MAILTAG_OTHER; break; @@ -472,8 +161,4 @@ final class PhabricatorProjectTransaction return $tags; } - private function renderSlugList($slugs) { - return implode(', ', $slugs); - } - } diff --git a/src/applications/project/typeahead/PhabricatorProjectLogicalDatasource.php b/src/applications/project/typeahead/PhabricatorProjectLogicalDatasource.php index b724e9f8ca..b7003cb69e 100644 --- a/src/applications/project/typeahead/PhabricatorProjectLogicalDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectLogicalDatasource.php @@ -21,6 +21,7 @@ final class PhabricatorProjectLogicalDatasource new PhabricatorProjectLogicalAncestorDatasource(), new PhabricatorProjectLogicalOrNotDatasource(), new PhabricatorProjectLogicalViewerDatasource(), + new PhabricatorProjectLogicalOnlyDatasource(), new PhabricatorProjectLogicalUserDatasource(), ); } diff --git a/src/applications/project/typeahead/PhabricatorProjectLogicalOnlyDatasource.php b/src/applications/project/typeahead/PhabricatorProjectLogicalOnlyDatasource.php new file mode 100644 index 0000000000..fe521fc6d8 --- /dev/null +++ b/src/applications/project/typeahead/PhabricatorProjectLogicalOnlyDatasource.php @@ -0,0 +1,76 @@ + array( + 'name' => pht('Only Match Other Constraints'), + 'summary' => pht( + 'Find results with only the specified tags.'), + 'description' => pht( + "This function is used with other tags, and causes the query to ". + "match only results with exactly those tags. For example, to find ". + "tasks tagged only iOS:". + "\n\n". + "> ios, only()". + "\n\n". + "This will omit results with any other project tag."), + ), + ); + } + + public function loadResults() { + $results = array( + $this->renderOnlyFunctionToken(), + ); + return $this->filterResultsAgainstTokens($results); + } + + protected function evaluateFunction($function, array $argv_list) { + $results = array(); + + $results[] = new PhabricatorQueryConstraint( + PhabricatorQueryConstraint::OPERATOR_ONLY, + null); + + return $results; + } + + public function renderFunctionTokens( + $function, + array $argv_list) { + + $tokens = array(); + foreach ($argv_list as $argv) { + $tokens[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult( + $this->renderOnlyFunctionToken()); + } + + return $tokens; + } + + private function renderOnlyFunctionToken() { + return $this->newFunctionResult() + ->setName(pht('Only')) + ->setPHID('only()') + ->setIcon('fa-asterisk') + ->setUnique(true) + ->addAttribute( + pht('Select only results with exactly the other specified tags.')); + } + +} diff --git a/src/applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php b/src/applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php index 9bb15ed180..807986457a 100644 --- a/src/applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php @@ -29,7 +29,7 @@ final class PhabricatorProjectLogicalViewerDatasource "\n\n". "This normally means //your// projects, but if you save a query ". "using this function and send it to someone else, it will mean ". - "//their// projects when they run it (they become the currnet ". + "//their// projects when they run it (they become the current ". "viewer). This can be useful for building dashboard panels."), ), ); diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php index 3d6044f2b1..7496a401bb 100644 --- a/src/applications/project/view/PhabricatorProjectListView.php +++ b/src/applications/project/view/PhabricatorProjectListView.php @@ -3,6 +3,9 @@ final class PhabricatorProjectListView extends AphrontView { private $projects; + private $showMember; + private $showWatching; + private $noDataString; public function setProjects(array $projects) { $this->projects = $projects; @@ -13,23 +16,43 @@ final class PhabricatorProjectListView extends AphrontView { return $this->projects; } + public function setShowWatching($watching) { + $this->showWatching = $watching; + return $this; + } + + public function setShowMember($member) { + $this->showMember = $member; + return $this; + } + + public function setNoDataString($text) { + $this->noDataString = $text; + return $this; + } + public function renderList() { $viewer = $this->getUser(); + $viewer_phid = $viewer->getPHID(); $projects = $this->getProjects(); $handles = $viewer->loadHandles(mpull($projects, 'getPHID')); + $no_data = pht('No projects found.'); + if ($this->noDataString) { + $no_data = $this->noDataString; + } + $list = id(new PHUIObjectItemListView()) - ->setUser($viewer); + ->setUser($viewer) + ->setNoDataString($no_data); foreach ($projects as $key => $project) { $id = $project->getID(); $icon = $project->getDisplayIconIcon(); - $color = $project->getColor(); - $icon_icon = id(new PHUIIconView()) - ->setIcon("{$icon} {$color}"); + ->setIcon($icon); $icon_name = $project->getDisplayIconName(); @@ -45,10 +68,24 @@ final class PhabricatorProjectListView extends AphrontView { )); if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED) { - $item->addIcon('delete-grey', pht('Archived')); + $item->addIcon('fa-ban', pht('Archived')); $item->setDisabled(true); } + if ($this->showMember) { + $is_member = $project->isUserMember($viewer_phid); + if ($is_member) { + $item->addIcon('fa-user', pht('Member')); + } + } + + if ($this->showWatching) { + $is_watcher = $project->isUserWatcher($viewer_phid); + if ($is_watcher) { + $item->addIcon('fa-eye', pht('Watching')); + } + } + $list->addItem($item); } diff --git a/src/applications/project/view/PhabricatorProjectMemberListView.php b/src/applications/project/view/PhabricatorProjectMemberListView.php index 12b7cc7a76..cf8a3a1465 100644 --- a/src/applications/project/view/PhabricatorProjectMemberListView.php +++ b/src/applications/project/view/PhabricatorProjectMemberListView.php @@ -4,7 +4,7 @@ final class PhabricatorProjectMemberListView extends PhabricatorProjectUserListView { protected function canEditList() { - $viewer = $this->getUser(); + $viewer = $this->getViewer(); $project = $this->getProject(); if (!$project->supportsEditMembers()) { @@ -31,4 +31,35 @@ final class PhabricatorProjectMemberListView return pht('Members'); } + protected function getMembershipNote() { + $viewer = $this->getViewer(); + $viewer_phid = $viewer->getPHID(); + $project = $this->getProject(); + + if (!$viewer_phid) { + return null; + } + + $note = null; + if ($project->isUserMember($viewer_phid)) { + $edge_type = PhabricatorProjectSilencedEdgeType::EDGECONST; + $silenced = PhabricatorEdgeQuery::loadDestinationPHIDs( + $project->getPHID(), + $edge_type); + $silenced = array_fuse($silenced); + $is_silenced = isset($silenced[$viewer_phid]); + if ($is_silenced) { + $note = pht( + 'You have disabled mail. When mail is sent to project members, '. + 'you will not receive a copy.'); + } else { + $note = pht( + 'You are a member and you will receive mail that is sent to all '. + 'project members.'); + } + } + + return $note; + } + } diff --git a/src/applications/project/view/PhabricatorProjectUserListView.php b/src/applications/project/view/PhabricatorProjectUserListView.php index d590cbb559..51c2ced6d1 100644 --- a/src/applications/project/view/PhabricatorProjectUserListView.php +++ b/src/applications/project/view/PhabricatorProjectUserListView.php @@ -6,6 +6,7 @@ abstract class PhabricatorProjectUserListView extends AphrontView { private $userPHIDs; private $limit; private $background; + private $showNote; public function setProject(PhabricatorProject $project) { $this->project = $project; @@ -39,10 +40,16 @@ abstract class PhabricatorProjectUserListView extends AphrontView { return $this; } + public function setShowNote($show) { + $this->showNote = $show; + return $this; + } + abstract protected function canEditList(); abstract protected function getNoDataString(); abstract protected function getRemoveURI($phid); abstract protected function getHeaderText(); + abstract protected function getMembershipNote(); public function render() { $viewer = $this->getViewer(); @@ -135,6 +142,15 @@ abstract class PhabricatorProjectUserListView extends AphrontView { ->setHeader($header) ->setObjectList($list); + if ($this->showNote) { + if ($this->getMembershipNote()) { + $info = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_PLAIN) + ->appendChild($this->getMembershipNote()); + $box->setInfoView($info); + } + } + if ($this->background) { $box->setBackground($this->background); } diff --git a/src/applications/project/view/PhabricatorProjectWatcherListView.php b/src/applications/project/view/PhabricatorProjectWatcherListView.php index 7aa9638afc..2d16cc7ec2 100644 --- a/src/applications/project/view/PhabricatorProjectWatcherListView.php +++ b/src/applications/project/view/PhabricatorProjectWatcherListView.php @@ -4,7 +4,7 @@ final class PhabricatorProjectWatcherListView extends PhabricatorProjectUserListView { protected function canEditList() { - $viewer = $this->getUser(); + $viewer = $this->getViewer(); $project = $this->getProject(); return PhabricatorPolicyFilter::hasCapability( @@ -27,4 +27,17 @@ final class PhabricatorProjectWatcherListView return pht('Watchers'); } + protected function getMembershipNote() { + $viewer = $this->getViewer(); + $viewer_phid = $viewer->getPHID(); + $project = $this->getProject(); + + $note = null; + if ($project->isUserWatcher($viewer_phid)) { + $note = pht('You are watching this project and will receive mail about '. + 'changes made to any related object.'); + } + return $note; + } + } diff --git a/src/applications/project/view/ProjectBoardTaskCard.php b/src/applications/project/view/ProjectBoardTaskCard.php index ba5213a623..1a6807ec45 100644 --- a/src/applications/project/view/ProjectBoardTaskCard.php +++ b/src/applications/project/view/ProjectBoardTaskCard.php @@ -100,7 +100,7 @@ final class ProjectBoardTaskCard extends Phobject { if ($points !== null) { $points_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade(PHUITagView::COLOR_GREY) + ->setColor(PHUITagView::COLOR_GREY) ->setSlimShady(true) ->setName($points) ->addClass('phui-workcard-points'); @@ -108,6 +108,13 @@ final class ProjectBoardTaskCard extends Phobject { } } + $subtype = $task->newSubtypeObject(); + if ($subtype && $subtype->hasTagView()) { + $subtype_tag = $subtype->newTagView() + ->setSlimShady(true); + $card->addAttribute($subtype_tag); + } + if ($task->isClosed()) { $icon = ManiphestTaskStatus::getStatusIcon($task->getStatus()); $icon = id(new PHUIIconView()) diff --git a/src/applications/project/xaction/PhabricatorProjectColorTransaction.php b/src/applications/project/xaction/PhabricatorProjectColorTransaction.php new file mode 100644 index 0000000000..9f36179b12 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectColorTransaction.php @@ -0,0 +1,33 @@ +getColor(); + } + + public function applyInternalEffects($object, $value) { + $object->setColor($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + return pht( + "%s set this project's color to %s.", + $this->renderAuthor(), + $this->renderValue(PHUITagView::getShadeName($new))); + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + return pht( + '%s set the color for %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue(PHUITagView::getShadeName($new))); + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectFilterTransaction.php b/src/applications/project/xaction/PhabricatorProjectFilterTransaction.php new file mode 100644 index 0000000000..d3d94fbb74 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectFilterTransaction.php @@ -0,0 +1,26 @@ +getDefaultWorkboardFilter(); + } + + public function applyInternalEffects($object, $value) { + $object->setDefaultWorkboardFilter($value); + } + + public function getTitle() { + return pht( + '%s changed the default filter for the project workboard.', + $this->renderAuthor()); + } + + public function shouldHide() { + return true; + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectIconTransaction.php b/src/applications/project/xaction/PhabricatorProjectIconTransaction.php new file mode 100644 index 0000000000..932ce4bdc4 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectIconTransaction.php @@ -0,0 +1,42 @@ +getIcon(); + } + + public function applyInternalEffects($object, $value) { + $object->setIcon($value); + } + + public function getTitle() { + $set = new PhabricatorProjectIconSet(); + $new = $this->getNewValue(); + + return pht( + "%s set this project's icon to %s.", + $this->renderAuthor(), + $this->renderValue($set->getIconLabel($new))); + } + + public function getTitleForFeed() { + $set = new PhabricatorProjectIconSet(); + $new = $this->getNewValue(); + + return pht( + '%s set the icon for %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderValue($set->getIconLabel($new))); + } + + public function getIcon() { + $new = $this->getNewValue(); + return PhabricatorProjectIconSet::getIconIcon($new); + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectImageTransaction.php b/src/applications/project/xaction/PhabricatorProjectImageTransaction.php new file mode 100644 index 0000000000..f6d10f0961 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectImageTransaction.php @@ -0,0 +1,136 @@ +getProfileImagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setProfileImagePHID($value); + } + + public function applyExternalEffects($object, $value) { + $old = $this->getOldValue(); + $new = $value; + $all = array(); + if ($old) { + $all[] = $old; + } + if ($new) { + $all[] = $new; + } + + $files = id(new PhabricatorFileQuery()) + ->setViewer($this->getActor()) + ->withPHIDs($all) + ->execute(); + $files = mpull($files, null, 'getPHID'); + + $old_file = idx($files, $old); + if ($old_file) { + $old_file->detachFromObject($object->getPHID()); + } + + $new_file = idx($files, $new); + if ($new_file) { + $new_file->attachToObject($object->getPHID()); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + // TODO: Some day, it would be nice to show the images. + if (!$old) { + return pht( + "%s set this project's image to %s.", + $this->renderAuthor(), + $this->renderNewHandle()); + } else if (!$new) { + return pht( + "%s removed this project's image.", + $this->renderAuthor()); + } else { + return pht( + "%s updated this project's image from %s to %s.", + $this->renderAuthor(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + // TODO: Some day, it would be nice to show the images. + if (!$old) { + return pht( + '%s set the image for %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewHandle()); + } else if (!$new) { + return pht( + '%s removed the image for %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s updated the image for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldHandle(), + $this->renderNewHandle()); + } + } + + public function getIcon() { + return 'fa-photo'; + } + + public function extractFilePHIDs($object, $value) { + if ($value) { + return array($value); + } + return array(); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + $viewer = $this->getActor(); + + foreach ($xactions as $xaction) { + $file_phid = $xaction->getNewValue(); + + // Only validate if file was uploaded + if ($file_phid) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + + if (!$file) { + $errors[] = $this->newInvalidError( + pht('"%s" is not a valid file PHID.', + $file_phid)); + } else { + if (!$file->isViewableImage()) { + $mime_type = $file->getMimeType(); + $errors[] = $this->newInvalidError( + pht('File mime type of "%s" is not a valid viewable image.', + $mime_type)); + } + } + } + } + + return $errors; + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectLockTransaction.php b/src/applications/project/xaction/PhabricatorProjectLockTransaction.php new file mode 100644 index 0000000000..42551dfb2c --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectLockTransaction.php @@ -0,0 +1,56 @@ +getIsMembershipLocked(); + } + + public function applyInternalEffects($object, $value) { + $object->setIsMembershipLocked($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + "%s locked this project's membership.", + $this->renderAuthor()); + } else { + return pht( + "%s unlocked this project's membership.", + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s locked %s membership.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s unlocked %s membership.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + + if ($new) { + return 'fa-lock'; + } else { + return 'fa-unlock'; + } + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectMilestoneTransaction.php b/src/applications/project/xaction/PhabricatorProjectMilestoneTransaction.php new file mode 100644 index 0000000000..74ac77b55d --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectMilestoneTransaction.php @@ -0,0 +1,31 @@ +setViewer($this->getActor()) + ->withPHIDs(array($parent_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + $object->attachParentProject($project); + + $number = $object->getParentProject()->loadNextMilestoneNumber(); + $object->setMilestoneNumber($number); + $object->setParentProjectPHID($value); + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectNameTransaction.php b/src/applications/project/xaction/PhabricatorProjectNameTransaction.php new file mode 100644 index 0000000000..512d899f0b --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectNameTransaction.php @@ -0,0 +1,112 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + if (!$this->getEditor()->getIsMilestone()) { + $object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($value)); + } + } + + public function applyExternalEffects($object, $value) { + $old = $this->getOldValue(); + + // First, add the old name as a secondary slug; this is helpful + // for renames and generally a good thing to do. + if (!$this->getEditor()->getIsMilestone()) { + if ($old !== null) { + $this->getEditor()->addSlug($object, $old, false); + } + $this->getEditor()->addSlug($object, $value, false); + } + return; + } + + public function getTitle() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s created this project.', + $this->renderAuthor()); + } else { + return pht( + '%s renamed this project from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError(pht('Projects must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Project names must not be longer than %s character(s).', + new PhutilNumber($max_length))); + } + } + + if ($this->getEditor()->getIsMilestone() || !$xactions) { + return $errors; + } + + $name = last($xactions)->getNewValue(); + + if (!PhabricatorSlug::isValidProjectSlug($name)) { + $errors[] = $this->newInvalidError( + pht('Project names must contain at least one letter or number.')); + } + + $slug = PhabricatorSlug::normalizeProjectSlug($name); + + $slug_used_already = id(new PhabricatorProjectSlug()) + ->loadOneWhere('slug = %s', $slug); + if ($slug_used_already && + $slug_used_already->getProjectPHID() != $object->getPHID()) { + + $errors[] = $this->newInvalidError( + pht( + 'Project name generates the same hashtag ("%s") as another '. + 'existing project. Choose a unique name.', + '#'.$slug)); + } + + return $errors; + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectParentTransaction.php b/src/applications/project/xaction/PhabricatorProjectParentTransaction.php new file mode 100644 index 0000000000..6fc850ddde --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectParentTransaction.php @@ -0,0 +1,29 @@ +setViewer($this->getActor()) + ->withPHIDs(array($parent_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + $object->attachParentProject($project); + + $object->setParentProjectPHID($value); + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php b/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php new file mode 100644 index 0000000000..e1f22b3746 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectSlugsTransaction.php @@ -0,0 +1,174 @@ +getSlugs(); + $slugs = mpull($slugs, 'getSlug', 'getSlug'); + unset($slugs[$object->getPrimarySlug()]); + return array_keys($slugs); + } + + public function generateNewValue($object, $value) { + return $this->getEditor()->normalizeSlugs($value); + } + + public function applyInternalEffects($object, $value) { + return; + } + + public function applyExternalEffects($object, $value) { + $old = $this->getOldValue(); + $new = $value; + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + foreach ($add as $slug) { + $this->getEditor()->addSlug($object, $slug, true); + } + + $this->getEditor()->removeSlugs($object, $rem); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + $add = $this->renderHashtags($add); + $rem = $this->renderHashtags($rem); + + if ($add && $rem) { + return pht( + '%s changed project hashtag(s), added %d: %s; removed %d: %s.', + $this->renderAuthor(), + count($add), + $this->renderValueList($add), + count($rem), + $this->renderValueList($rem)); + } else if ($add) { + return pht( + '%s added %d project hashtag(s): %s.', + $this->renderAuthor(), + count($add), + $this->renderValueList($add)); + } else if ($rem) { + return pht( + '%s removed %d project hashtag(s): %s.', + $this->renderAuthor(), + count($rem), + $this->renderValueList($rem)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + $add = $this->renderHashtags($add); + $rem = $this->renderHashtags($rem); + + if ($add && $rem) { + return pht( + '%s changed %s hashtag(s), added %d: %s; removed %d: %s.', + $this->renderAuthor(), + $this->renderObject(), + count($add), + $this->renderValueList($add), + count($rem), + $this->renderValueList($rem)); + } else if ($add) { + return pht( + '%s added %d %s hashtag(s): %s.', + $this->renderAuthor(), + count($add), + $this->renderObject(), + $this->renderValueList($add)); + } else if ($rem) { + return pht( + '%s removed %d %s hashtag(s): %s.', + $this->renderAuthor(), + count($rem), + $this->renderObject(), + $this->renderValueList($rem)); + } + } + + public function getIcon() { + return 'fa-tag'; + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$xactions) { + return $errors; + } + + $slug_xaction = last($xactions); + + $new = $slug_xaction->getNewValue(); + + $invalid = array(); + foreach ($new as $slug) { + if (!PhabricatorSlug::isValidProjectSlug($slug)) { + $invalid[] = $slug; + } + } + + if ($invalid) { + $errors[] = $this->newInvalidError( + pht( + 'Hashtags must contain at least one letter or number. %s '. + 'project hashtag(s) are invalid: %s.', + phutil_count($invalid), + implode(', ', $invalid))); + + return $errors; + } + + $new = $this->getEditor()->normalizeSlugs($new); + + if ($new) { + $slugs_used_already = id(new PhabricatorProjectSlug()) + ->loadAllWhere('slug IN (%Ls)', $new); + } else { + // The project doesn't have any extra slugs. + $slugs_used_already = array(); + } + + $slugs_used_already = mgroup($slugs_used_already, 'getProjectPHID'); + foreach ($slugs_used_already as $project_phid => $used_slugs) { + if ($project_phid == $object->getPHID()) { + continue; + } + + $used_slug_strs = mpull($used_slugs, 'getSlug'); + + $errors[] = $this->newInvalidError( + pht( + '%s project hashtag(s) are already used by other projects: %s.', + phutil_count($used_slug_strs), + implode(', ', $used_slug_strs))); + } + + return $errors; + } + + private function renderHashtags(array $tags) { + $result = array(); + foreach ($tags as $tag) { + $result[] = '#'.$tag; + } + return $result; + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectSortTransaction.php b/src/applications/project/xaction/PhabricatorProjectSortTransaction.php new file mode 100644 index 0000000000..f070f6dd19 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectSortTransaction.php @@ -0,0 +1,26 @@ +getDefaultWorkboardSort(); + } + + public function applyInternalEffects($object, $value) { + $object->setDefaultWorkboardSort($value); + } + + public function getTitle() { + return pht( + '%s changed the default sort order for the project workboard.', + $this->renderAuthor()); + } + + public function shouldHide() { + return true; + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectStatusTransaction.php b/src/applications/project/xaction/PhabricatorProjectStatusTransaction.php new file mode 100644 index 0000000000..e5c8ead4f4 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectStatusTransaction.php @@ -0,0 +1,66 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + + if ($old == 0) { + return pht( + '%s archived this project.', + $this->renderAuthor()); + } else { + return pht( + '%s activated this project.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + + if ($old == 0) { + return pht( + '%s archived %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s activated %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getColor() { + $old = $this->getOldValue(); + + if ($old == 0) { + return 'red'; + } else { + return 'green'; + } + } + + public function getIcon() { + $old = $this->getOldValue(); + + if ($old == 0) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectTransactionType.php b/src/applications/project/xaction/PhabricatorProjectTransactionType.php new file mode 100644 index 0000000000..9ce02dc450 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectTransactionType.php @@ -0,0 +1,4 @@ +getNewValue(); + if (!$parent_phid) { + return $errors; + } + + if (!$this->getEditor()->getIsNewObject()) { + $errors[] = $this->newInvalidError( + pht( + 'You can only set a parent or milestone project when creating a '. + 'project for the first time.')); + return $errors; + } + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($parent_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + if (!$projects) { + $errors[] = $this->newInvalidError( + pht( + 'Parent or milestone project PHID ("%s") must be the PHID of a '. + 'valid, visible project which you have permission to edit.', + $parent_phid)); + return $errors; + } + + $project = head($projects); + + if ($project->isMilestone()) { + $errors[] = $this->newInvalidError( + pht( + 'Parent or milestone project PHID ("%s") must not be a '. + 'milestone. Milestones may not have subprojects or milestones.', + $parent_phid)); + return $errors; + } + + $limit = PhabricatorProject::getProjectDepthLimit(); + if ($project->getProjectDepth() >= ($limit - 1)) { + $errors[] = $this->newInvalidError( + pht( + 'You can not create a subproject or milestone under this parent '. + 'because it would nest projects too deeply. The maximum '. + 'nesting depth of projects is %s.', + new PhutilNumber($limit))); + return $errors; + } + + return $errors; + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectWorkboardBackgroundTransaction.php b/src/applications/project/xaction/PhabricatorProjectWorkboardBackgroundTransaction.php new file mode 100644 index 0000000000..620959b0a2 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectWorkboardBackgroundTransaction.php @@ -0,0 +1,26 @@ +getWorkboardBackgroundColor(); + } + + public function applyInternalEffects($object, $value) { + $object->setWorkboardBackgroundColor($value); + } + + public function getTitle() { + return pht( + '%s changed the background color of the project workboard.', + $this->renderAuthor()); + } + + public function shouldHide() { + return true; + } + +} diff --git a/src/applications/project/xaction/PhabricatorProjectWorkboardTransaction.php b/src/applications/project/xaction/PhabricatorProjectWorkboardTransaction.php new file mode 100644 index 0000000000..b16a1921e0 --- /dev/null +++ b/src/applications/project/xaction/PhabricatorProjectWorkboardTransaction.php @@ -0,0 +1,38 @@ +getHasWorkboard(); + } + + public function generateNewValue($object, $value) { + return (int)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setHasWorkboard($value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s enabled the workboard for this project.', + $this->renderAuthor()); + } else { + return pht( + '%s disabled the workboard for this project.', + $this->renderAuthor()); + } + } + + public function shouldHide() { + return true; + } + +} diff --git a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php index bdb63e6c9c..dd67f5bc59 100644 --- a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php +++ b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php @@ -79,8 +79,7 @@ final class DifferentialReleephRequestFieldSpecification extends Phobject { return null; } - $status = $this->getRevision()->getStatus(); - if ($status == ArcanistDifferentialRevisionStatus::CLOSED) { + if ($this->getRevision()->isClosed()) { $verb = $tense[$this->releephAction]['past']; } else { $verb = $tense[$this->releephAction]['future']; diff --git a/src/applications/repository/codex/PhabricatorRepositoryDestructibleCodex.php b/src/applications/repository/codex/PhabricatorRepositoryDestructibleCodex.php new file mode 100644 index 0000000000..eaa5cf4a77 --- /dev/null +++ b/src/applications/repository/codex/PhabricatorRepositoryDestructibleCodex.php @@ -0,0 +1,23 @@ +getObject(); + + $notes = array(); + + if ($repository->hasLocalWorkingCopy()) { + $notes[] = pht( + 'Database records for repository "%s" were destroyed, but this '. + 'script does not remove working copies on disk. If you also want to '. + 'destroy the repository working copy, manually remove "%s".', + $repository->getDisplayName(), + $repository->getLocalPath()); + } + + return $notes; + } + +} diff --git a/src/applications/repository/conduit/RepositoryQueryConduitAPIMethod.php b/src/applications/repository/conduit/RepositoryQueryConduitAPIMethod.php index 61435e2a1b..8b5005d8ec 100644 --- a/src/applications/repository/conduit/RepositoryQueryConduitAPIMethod.php +++ b/src/applications/repository/conduit/RepositoryQueryConduitAPIMethod.php @@ -14,7 +14,7 @@ final class RepositoryQueryConduitAPIMethod public function getMethodStatusDescription() { return pht( 'This method is frozen and will eventually be deprecated. New code '. - 'should use "diffusion.repository.query" instead.'); + 'should use "diffusion.repository.search" instead.'); } public function getMethodDescription() { diff --git a/src/applications/repository/constants/PhabricatorRepositoryType.php b/src/applications/repository/constants/PhabricatorRepositoryType.php index 998bff0d2e..593732ddd8 100644 --- a/src/applications/repository/constants/PhabricatorRepositoryType.php +++ b/src/applications/repository/constants/PhabricatorRepositoryType.php @@ -26,18 +26,21 @@ final class PhabricatorRepositoryType extends Phobject { self::REPOSITORY_TYPE_GIT => array( 'name' => pht('Git'), 'icon' => 'fa-git', + 'image' => 'repo/repo-git.png', 'create.header' => pht('Create Git Repository'), 'create.subheader' => pht('Create a new Git repository.'), ), self::REPOSITORY_TYPE_MERCURIAL => array( 'name' => pht('Mercurial'), 'icon' => 'fa-code-fork', + 'image' => 'repo/repo-hg.png', 'create.header' => pht('Create Mercurial Repository'), 'create.subheader' => pht('Create a new Mercurial repository.'), ), self::REPOSITORY_TYPE_SVN => array( 'name' => pht('Subversion'), 'icon' => 'fa-database', + 'image' => 'repo/repo-svn.png', 'create.header' => pht('Create Subversion Repository'), 'create.subheader' => pht('Create a new Subversion repository.'), ), diff --git a/src/applications/repository/constants/PhabricatorRepositoryVersion.php b/src/applications/repository/constants/PhabricatorRepositoryVersion.php deleted file mode 100644 index 5f722fa40a..0000000000 --- a/src/applications/repository/constants/PhabricatorRepositoryVersion.php +++ /dev/null @@ -1,40 +0,0 @@ -='); - } - -} diff --git a/src/applications/repository/engine/PhabricatorRepositoryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryEngine.php index 445357e783..244ed0cd45 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryEngine.php @@ -135,6 +135,11 @@ abstract class PhabricatorRepositoryEngine extends Phobject { $exists = true; } + // These URIs may have plaintext HTTP credentials. If they do, censor + // them for display. See T12945. + $display_remote = phutil_censor_credentials($remote_uri); + $display_expect = phutil_censor_credentials($expect_remote); + if (!$valid) { if (!$exists) { // If there's no "origin" remote, just create it regardless of how @@ -172,8 +177,8 @@ abstract class PhabricatorRepositoryEngine extends Phobject { 'set the remote URI correctly. To avoid breaking anything, '. 'Phabricator will not automatically fix this.', $repository->getLocalPath(), - $remote_uri, - $expect_remote); + $display_remote, + $display_expect); throw new Exception($message); } } diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index b144ecfd96..10eddd38d4 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -485,8 +485,8 @@ final class PhabricatorRepositoryPullEngine // On vulnerable versions of Mercurial, we refuse to clone remotes which // contain characters which may be interpreted by the shell. - $hg_version = PhabricatorRepositoryVersion::getMercurialVersion(); - $is_vulnerable = version_compare($hg_version, '3.2.4', '<'); + $hg_binary = PhutilBinaryAnalyzer::getForBinary('hg'); + $is_vulnerable = $hg_binary->isMercurialVulnerableToInjection(); if ($is_vulnerable) { $cleartext = $remote->openEnvelope(); // The use of "%R" here is an attempt to limit collateral damage @@ -501,7 +501,7 @@ final class PhabricatorRepositoryPullEngine 'command injection security vulnerability. The remote URI for '. 'this repository (%s) is potentially unsafe. Upgrade Mercurial '. 'to at least 3.2.4 to clone it.', - $hg_version, + $hg_binary->getBinaryVersion(), $repository->getMonogram())); } } @@ -529,7 +529,7 @@ final class PhabricatorRepositoryPullEngine // This is a local command, but needs credentials. $remote = $repository->getRemoteURIEnvelope(); - $future = $repository->getRemoteCommandFuture('pull -u -- %P', $remote); + $future = $repository->getRemoteCommandFuture('pull -- %P', $remote); $future->setCWD($path); try { diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index 74e42931c9..e1298d372d 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -36,6 +36,7 @@ final class PhabricatorRepositoryQuery private $needCommitCounts; private $needProjectPHIDs; private $needURIs; + private $needProfileImage; public function withIDs(array $ids) { $this->ids = $ids; @@ -160,6 +161,11 @@ final class PhabricatorRepositoryQuery return $this; } + public function needProfileImage($need) { + $this->needProfileImage = $need; + return $this; + } + public function getBuiltinOrders() { return array( 'name' => array( @@ -374,6 +380,36 @@ final class PhabricatorRepositoryQuery } } + if ($this->needProfileImage) { + $default = null; + + $file_phids = mpull($repositories, 'getProfileImagePHID'); + $file_phids = array_filter($file_phids); + if ($file_phids) { + $files = id(new PhabricatorFileQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($file_phids) + ->execute(); + $files = mpull($files, null, 'getPHID'); + } else { + $files = array(); + } + + foreach ($repositories as $repository) { + $file = idx($files, $repository->getProfileImagePHID()); + if (!$file) { + if (!$default) { + $default = PhabricatorFile::loadBuiltin( + $this->getViewer(), + 'repo/code.png'); + } + $file = $default; + } + $repository->attachProfileImageFile($file); + } + } + return $repositories; } diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index e53085a9f7..a731085edb 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -15,7 +15,8 @@ final class PhabricatorRepositorySearchEngine return id(new PhabricatorRepositoryQuery()) ->needProjectPHIDs(true) ->needCommitCounts(true) - ->needMostRecentCommits(true); + ->needMostRecentCommits(true) + ->needProfileImage(true); } protected function buildCustomSearchFields() { @@ -183,7 +184,8 @@ final class PhabricatorRepositorySearchEngine ->setObject($repository) ->setHeader($repository->getName()) ->setObjectName($repository->getMonogram()) - ->setHref($repository->getURI()); + ->setHref($repository->getURI()) + ->setImageURI($repository->getProfileImageURI()); $commit = $repository->getMostRecentCommit(); if ($commit) { diff --git a/src/applications/repository/search/DiffusionCommitFerretEngine.php b/src/applications/repository/search/DiffusionCommitFerretEngine.php new file mode 100644 index 0000000000..a6c5ce42c8 --- /dev/null +++ b/src/applications/repository/search/DiffusionCommitFerretEngine.php @@ -0,0 +1,18 @@ + 'phid?', 'almanacServicePHID' => 'phid?', 'localPath' => 'text128?', + 'profileImagePHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'callsign' => array( @@ -478,6 +483,20 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO } } + public function getProfileImageURI() { + return $this->getProfileImageFile()->getBestURI(); + } + + public function attachProfileImageFile(PhabricatorFile $file) { + $this->profileImageFile = $file; + return $this; + } + + public function getProfileImageFile() { + return $this->assertAttached($this->profileImageFile); + } + + /* -( Remote Command Execution )------------------------------------------- */ @@ -682,6 +701,8 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO $action = idx($params, 'action'); switch ($action) { case 'history': + case 'graph': + case 'clone': case 'browse': case 'change': case 'lastmodified': @@ -759,6 +780,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO switch ($action) { case 'change': case 'history': + case 'graph': case 'browse': case 'lastmodified': case 'tags': @@ -799,6 +821,9 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO // it came from a URI. $uri = rawurldecode("{$path}{$commit}"); break; + case 'clone': + $uri = $this->getPathURI("/{$action}/"); + break; } if ($action == 'rendering-ref') { @@ -2534,6 +2559,14 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO } +/* -( PhabricatorDestructibleCodexInterface )------------------------------ */ + + + public function newDestructibleCodex() { + return new PhabricatorRepositoryDestructibleCodex(); + } + + /* -( PhabricatorSpacesInterface )----------------------------------------- */ @@ -2601,4 +2634,12 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return new PhabricatorRepositoryFulltextEngine(); } + +/* -( PhabricatorFerretInterface )----------------------------------------- */ + + + public function newFerretEngine() { + return new PhabricatorRepositoryFerretEngine(); + } + } diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index c6ddece457..31a06dcbd4 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -15,6 +15,7 @@ final class PhabricatorRepositoryCommit PhabricatorCustomFieldInterface, PhabricatorApplicationTransactionInterface, PhabricatorFulltextInterface, + PhabricatorFerretInterface, PhabricatorConduitResultInterface, PhabricatorDraftInterface { @@ -712,6 +713,14 @@ final class PhabricatorRepositoryCommit } +/* -( PhabricatorFerretInterface )----------------------------------------- */ + + + public function newFerretEngine() { + return new DiffusionCommitFerretEngine(); + } + + /* -( PhabricatorConduitResultInterface )---------------------------------- */ public function getFieldSpecificationsForConduit() { diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index 767cfe5290..fe8abd5925 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -186,11 +186,6 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $revision = $revision_query->executeOne(); if ($revision) { - if (!$data->getCommitDetail('precommitRevisionStatus')) { - $data->setCommitDetail( - 'precommitRevisionStatus', - $revision->getStatus()); - } $commit_drev = DiffusionCommitHasRevisionEdgeType::EDGECONST; id(new PhabricatorEdgeEditor()) ->addEdge($commit->getPHID(), $commit_drev, $revision->getPHID()) @@ -203,14 +198,13 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $revision->getID(), $commit->getPHID()); - $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; - $should_close = ($revision->getStatus() != $status_closed) && - $should_autoclose; - + $should_close = !$revision->isPublished() && $should_autoclose; if ($should_close) { - $commit_close_xaction = id(new DifferentialTransaction()) - ->setTransactionType(DifferentialTransaction::TYPE_ACTION) - ->setNewValue(DifferentialAction::ACTION_CLOSE) + $type_close = DifferentialRevisionCloseTransaction::TRANSACTIONTYPE; + + $commit_close_xaction = id(new DifferentialTransaction()) + ->setTransactionType($type_close) + ->setNewValue(true) ->setMetadataValue('isCommitClose', true); $commit_close_xaction->setMetadataValue( @@ -367,7 +361,8 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker if ($status) { if ($task->getStatus() != $status) { $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setTransactionType( + ManiphestTaskStatusTransaction::TRANSACTIONTYPE) ->setMetadataValue('commitPHID', $commit->getPHID()) ->setNewValue($status); diff --git a/src/applications/search/application/PhabricatorSearchApplication.php b/src/applications/search/application/PhabricatorSearchApplication.php index a2a28c7665..3cf5923b9c 100644 --- a/src/applications/search/application/PhabricatorSearchApplication.php +++ b/src/applications/search/application/PhabricatorSearchApplication.php @@ -33,9 +33,18 @@ final class PhabricatorSearchApplication extends PhabricatorApplication { 'index/(?P[^/]+)/' => 'PhabricatorSearchIndexController', 'hovercard/' => 'PhabricatorSearchHovercardController', - 'edit/(?P[^/]+)/' => 'PhabricatorSearchEditController', - 'delete/(?P[^/]+)/(?P[^/]+)/' - => 'PhabricatorSearchDeleteController', + 'edit/' => array( + 'key/(?P[^/]+)/' => 'PhabricatorSearchEditController', + 'id/(?P[^/]+)/' => 'PhabricatorSearchEditController', + ), + 'default/(?P[^/]+)/(?P[^/]+)/' + => 'PhabricatorSearchDefaultController', + 'delete/' => array( + 'key/(?P[^/]+)/(?P[^/]+)/' + => 'PhabricatorSearchDeleteController', + 'id/(?P[^/]+)/' + => 'PhabricatorSearchDeleteController', + ), 'order/(?P[^/]+)/' => 'PhabricatorSearchOrderController', 'rel/(?P[^/]+)/(?P[^/]+)/' => 'PhabricatorSearchRelationshipController', diff --git a/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php b/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php index 15ecc3f391..6dd0786c0c 100644 --- a/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php +++ b/src/applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php @@ -16,54 +16,61 @@ final class PhabricatorSearchApplicationStorageEnginePanel $viewer = $this->getViewer(); $application = $this->getApplication(); - $active_engine = PhabricatorFulltextStorageEngine::loadEngine(); - $engines = PhabricatorFulltextStorageEngine::loadAllEngines(); + $services = PhabricatorSearchService::getAllServices(); $rows = array(); $rowc = array(); - foreach ($engines as $key => $engine) { + foreach ($services as $key => $service) { try { - $index_exists = $engine->indexExists() ? pht('Yes') : pht('No'); + $name = $service->getDisplayName(); } catch (Exception $ex) { - $index_exists = pht('N/A'); + $name = phutil_tag('em', array(), pht('Error')); } try { - $index_is_sane = $engine->indexIsSane() ? pht('Yes') : pht('No'); + $can_read = $service->isReadable() ? pht('Yes') : pht('No'); } catch (Exception $ex) { - $index_is_sane = pht('N/A'); + $can_read = pht('N/A'); } - if ($engine == $active_engine) { - $rowc[] = 'highlighted'; - } else { - $rowc[] = null; + try { + $can_write = $service->isWritable() ? pht('Yes') : pht('No'); + } catch (Exception $ex) { + $can_write = pht('N/A'); } $rows[] = array( - $key, - get_class($engine), - $index_exists, - $index_is_sane, + $name, + $can_read, + $can_write, ); } + $instructions = pht( + 'To configure the search engines, edit [[ %s | `%s` ]] configuration. '. + 'See **[[ %s | %s ]]** for documentation.', + '/config/edit/cluster.search/', + 'cluster.search', + PhabricatorEnv::getDoclink('Cluster: Search'), + pht('Cluster: Search')); + + $table = id(new AphrontTableView($rows)) ->setNoDataString(pht('No search engines available.')) + ->setNotice(new PHUIRemarkupView($viewer, $instructions)) ->setHeaders( array( - pht('Key'), - pht('Class'), - pht('Index Exists'), - pht('Index Is Sane'), + pht('Engine Name'), + pht('Writable'), + pht('Readable'), )) ->setRowClasses($rowc) ->setColumnClasses( array( - '', 'wide', '', + '', )); $box = id(new PHUIObjectBoxView()) diff --git a/src/applications/search/constants/PhabricatorSearchDocumentFieldType.php b/src/applications/search/constants/PhabricatorSearchDocumentFieldType.php index 10dbf0ca65..42fc2738f3 100644 --- a/src/applications/search/constants/PhabricatorSearchDocumentFieldType.php +++ b/src/applications/search/constants/PhabricatorSearchDocumentFieldType.php @@ -5,5 +5,7 @@ final class PhabricatorSearchDocumentFieldType extends Phobject { const FIELD_TITLE = 'titl'; const FIELD_BODY = 'body'; const FIELD_COMMENT = 'cmnt'; + const FIELD_ALL = 'full'; + const FIELD_CORE = 'core'; } diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index 7723b78d12..c85a6dac0c 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -131,7 +131,7 @@ if (!$found_query_data) { // Otherwise, there's no query data so just run the user's default // query for this application. - $query_key = head_key($engine->loadEnabledNamedQueries()); + $query_key = $engine->getDefaultQueryKey(); } } @@ -178,7 +178,7 @@ if ($run_query && !$named_query && $user->isLoggedIn()) { $save_button = id(new PHUIButtonView()) ->setTag('a') - ->setHref('/search/edit/'.$saved_query->getQueryKey().'/') + ->setHref('/search/edit/key/'.$saved_query->getQueryKey().'/') ->setText(pht('Save Query')) ->setIcon('fa-floppy-o'); $submit->addButton($save_button); @@ -387,7 +387,7 @@ private function processEditRequest() { $parent = $this->getDelegatingController(); $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); $engine = $this->getSearchEngine(); $nav = $this->getNavigation(); @@ -397,64 +397,41 @@ $named_queries = $engine->loadAllNamedQueries(); - $list_id = celerity_generate_unique_node_id(); + $can_global = $viewer->getIsAdmin(); - $list = new PHUIObjectItemListView(); - $list->setUser($user); - $list->setID($list_id); - - Javelin::initBehavior( - 'search-reorder-queries', - array( - 'listID' => $list_id, - 'orderURI' => '/search/order/'.get_class($engine).'/', - )); + $groups = array( + 'personal' => array( + 'name' => pht('Personal Saved Queries'), + 'items' => array(), + 'edit' => true, + ), + 'global' => array( + 'name' => pht('Global Saved Queries'), + 'items' => array(), + 'edit' => $can_global, + ), + ); foreach ($named_queries as $named_query) { - $class = get_class($engine); - $key = $named_query->getQueryKey(); - - $item = id(new PHUIObjectItemView()) - ->setHeader($named_query->getQueryName()) - ->setHref($engine->getQueryResultsPageURI($key)); - - if ($named_query->getIsBuiltin() && $named_query->getIsDisabled()) { - $icon = 'fa-plus'; + if ($named_query->isGlobal()) { + $group = 'global'; } else { - $icon = 'fa-times'; + $group = 'personal'; } - $item->addAction( - id(new PHUIListItemView()) - ->setIcon($icon) - ->setHref('/search/delete/'.$key.'/'.$class.'/') - ->setWorkflow(true)); - - if ($named_query->getIsBuiltin()) { - if ($named_query->getIsDisabled()) { - $item->addIcon('fa-times lightgreytext', pht('Disabled')); - $item->setDisabled(true); - } else { - $item->addIcon('fa-lock lightgreytext', pht('Builtin')); - } - } else { - $item->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-pencil') - ->setHref('/search/edit/'.$key.'/')); - } - - $item->setGrippable(true); - $item->addSigil('named-query'); - $item->setMetadata( - array( - 'queryKey' => $named_query->getQueryKey(), - )); - - $list->addItem($item); + $groups[$group]['items'][] = $named_query; } - $list->setNoDataString(pht('No saved queries.')); + $default_key = $engine->getDefaultQueryKey(); + + $lists = array(); + foreach ($groups as $group) { + $lists[] = $this->newQueryListView( + $group['name'], + $group['items'], + $default_key, + $group['edit']); + } $crumbs = $parent ->buildApplicationCrumbs() @@ -467,20 +444,144 @@ ->setHeader(pht('Saved Queries')) ->setProfileHeader(true); - $box = id(new PHUIObjectBoxView()) + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setObjectList($list) - ->addClass('application-search-results'); - - $nav->addClass('application-search-view'); - require_celerity_resource('application-search-view-css'); + ->setFooter($lists); return $this->newPage() ->setApplicationMenu($this->buildApplicationMenu()) ->setTitle(pht('Saved Queries')) ->setCrumbs($crumbs) ->setNavigation($nav) - ->appendChild($box); + ->appendChild($view); + } + + private function newQueryListView( + $list_name, + array $named_queries, + $default_key, + $can_edit) { + + $engine = $this->getSearchEngine(); + $viewer = $this->getViewer(); + + $list = id(new PHUIObjectItemListView()) + ->setViewer($viewer); + + if ($can_edit) { + $list_id = celerity_generate_unique_node_id(); + $list->setID($list_id); + + Javelin::initBehavior( + 'search-reorder-queries', + array( + 'listID' => $list_id, + 'orderURI' => '/search/order/'.get_class($engine).'/', + )); + } + + foreach ($named_queries as $named_query) { + $class = get_class($engine); + $key = $named_query->getQueryKey(); + + $item = id(new PHUIObjectItemView()) + ->setHeader($named_query->getQueryName()) + ->setHref($engine->getQueryResultsPageURI($key)); + + if ($named_query->getIsDisabled()) { + if ($can_edit) { + $item->setDisabled(true); + } else { + // If an item is disabled and you don't have permission to edit it, + // just skip it. + continue; + } + } + + if ($can_edit) { + if ($named_query->getIsBuiltin() && $named_query->getIsDisabled()) { + $icon = 'fa-plus'; + $disable_name = pht('Enable'); + } else { + $icon = 'fa-times'; + if ($named_query->getIsBuiltin()) { + $disable_name = pht('Disable'); + } else { + $disable_name = pht('Delete'); + } + } + + if ($named_query->getID()) { + $disable_href = '/search/delete/id/'.$named_query->getID().'/'; + } else { + $disable_href = '/search/delete/key/'.$key.'/'.$class.'/'; + } + + $item->addAction( + id(new PHUIListItemView()) + ->setIcon($icon) + ->setHref($disable_href) + ->setRenderNameAsTooltip(true) + ->setName($disable_name) + ->setWorkflow(true)); + } + + $default_disabled = $named_query->getIsDisabled(); + $default_icon = 'fa-thumb-tack'; + + if ($default_key === $key) { + $default_color = 'green'; + } else { + $default_color = null; + } + + $item->addAction( + id(new PHUIListItemView()) + ->setIcon("{$default_icon} {$default_color}") + ->setHref('/search/default/'.$key.'/'.$class.'/') + ->setRenderNameAsTooltip(true) + ->setName(pht('Make Default')) + ->setWorkflow(true) + ->setDisabled($default_disabled)); + + if ($can_edit) { + if ($named_query->getIsBuiltin()) { + $edit_icon = 'fa-lock lightgreytext'; + $edit_disabled = true; + $edit_name = pht('Builtin'); + $edit_href = null; + } else { + $edit_icon = 'fa-pencil'; + $edit_disabled = false; + $edit_name = pht('Edit'); + $edit_href = '/search/edit/id/'.$named_query->getID().'/'; + } + + $item->addAction( + id(new PHUIListItemView()) + ->setIcon($edit_icon) + ->setHref($edit_href) + ->setRenderNameAsTooltip(true) + ->setName($edit_name) + ->setDisabled($edit_disabled)); + } + + $item->setGrippable($can_edit); + $item->addSigil('named-query'); + $item->setMetadata( + array( + 'queryKey' => $named_query->getQueryKey(), + )); + + $list->addItem($item); + } + + $list->setNoDataString(pht('No saved queries.')); + + return id(new PHUIObjectBoxView()) + ->setHeaderText($list_name) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); } public function buildApplicationMenu() { @@ -564,7 +665,7 @@ return id(new PHUIButtonView()) ->setTag('a') ->setHref('#') - ->setText(pht('Use Results...')) + ->setText(pht('Use Results')) ->setIcon('fa-bars') ->setDropdownMenu($action_list) ->addClass('dropdown'); @@ -620,7 +721,7 @@ $engine_class = get_class($engine); $query_key = $this->getQueryKey(); if (!$query_key) { - $query_key = head_key($engine->loadEnabledNamedQueries()); + $query_key = $engine->getDefaultQueryKey(); } $can_use = $engine->canUseInPanelContext(); diff --git a/src/applications/search/controller/PhabricatorSearchDefaultController.php b/src/applications/search/controller/PhabricatorSearchDefaultController.php new file mode 100644 index 0000000000..a4f68e503a --- /dev/null +++ b/src/applications/search/controller/PhabricatorSearchDefaultController.php @@ -0,0 +1,85 @@ +getViewer(); + $engine_class = $request->getURIData('engine'); + + $base_class = 'PhabricatorApplicationSearchEngine'; + if (!is_subclass_of($engine_class, $base_class)) { + return new Aphront400Response(); + } + + $engine = newv($engine_class, array()); + $engine->setViewer($viewer); + + $key = $request->getURIData('queryKey'); + + $named_query = id(new PhabricatorNamedQueryQuery()) + ->setViewer($viewer) + ->withEngineClassNames(array($engine_class)) + ->withQueryKeys(array($key)) + ->withUserPHIDs( + array( + $viewer->getPHID(), + PhabricatorNamedQuery::SCOPE_GLOBAL, + )) + ->executeOne(); + + if (!$named_query && $engine->isBuiltinQuery($key)) { + $named_query = $engine->getBuiltinQuery($key); + } + + if (!$named_query) { + return new Aphront404Response(); + } + + $return_uri = $engine->getQueryManagementURI(); + + $builtin = null; + if ($engine->isBuiltinQuery($key)) { + $builtin = $engine->getBuiltinQuery($key); + } + + if ($request->isFormPost()) { + $config = id(new PhabricatorNamedQueryConfigQuery()) + ->setViewer($viewer) + ->withEngineClassNames(array($engine_class)) + ->withScopePHIDs(array($viewer->getPHID())) + ->executeOne(); + if (!$config) { + $config = PhabricatorNamedQueryConfig::initializeNewQueryConfig() + ->setEngineClassName($engine_class) + ->setScopePHID($viewer->getPHID()); + } + + $config->setConfigProperty( + PhabricatorNamedQueryConfig::PROPERTY_PINNED, + $key); + + $config->save(); + + return id(new AphrontRedirectResponse())->setURI($return_uri); + } + + if ($named_query->getIsBuiltin()) { + $query_name = $builtin->getQueryName(); + } else { + $query_name = $named_query->getQueryName(); + } + + $title = pht('Set Default Query'); + $body = pht( + 'This query will become your default query in the current application.'); + $button = pht('Set Default Query'); + + return $this->newDialog() + ->setTitle($title) + ->appendChild($body) + ->addCancelButton($return_uri) + ->addSubmitButton($button); + } + +} diff --git a/src/applications/search/controller/PhabricatorSearchDeleteController.php b/src/applications/search/controller/PhabricatorSearchDeleteController.php index f72c283519..9cbabd3a2f 100644 --- a/src/applications/search/controller/PhabricatorSearchDeleteController.php +++ b/src/applications/search/controller/PhabricatorSearchDeleteController.php @@ -5,32 +5,45 @@ final class PhabricatorSearchDeleteController public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); - $key = $request->getURIData('queryKey'); - $engine_class = $request->getURIData('engine'); - $base_class = 'PhabricatorApplicationSearchEngine'; - if (!is_subclass_of($engine_class, $base_class)) { - return new Aphront400Response(); - } + $id = $request->getURIData('id'); + if ($id) { + $named_query = id(new PhabricatorNamedQueryQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$named_query) { + return new Aphront404Response(); + } - $engine = newv($engine_class, array()); - $engine->setViewer($viewer); + $engine = newv($named_query->getEngineClassName(), array()); + $engine->setViewer($viewer); - $named_query = id(new PhabricatorNamedQueryQuery()) - ->setViewer($viewer) - ->withEngineClassNames(array($engine_class)) - ->withQueryKeys(array($key)) - ->withUserPHIDs(array($viewer->getPHID())) - ->executeOne(); + $key = $named_query->getQueryKey(); + } else { + $key = $request->getURIData('queryKey'); + $engine_class = $request->getURIData('engine'); + + $base_class = 'PhabricatorApplicationSearchEngine'; + if (!is_subclass_of($engine_class, $base_class)) { + return new Aphront400Response(); + } + + $engine = newv($engine_class, array()); + $engine->setViewer($viewer); + + if (!$engine->isBuiltinQuery($key)) { + return new Aphront404Response(); + } - if (!$named_query && $engine->isBuiltinQuery($key)) { $named_query = $engine->getBuiltinQuery($key); } - if (!$named_query) { - return new Aphront404Response(); - } - $builtin = null; if ($engine->isBuiltinQuery($key)) { $builtin = $engine->getBuiltinQuery($key); diff --git a/src/applications/search/controller/PhabricatorSearchEditController.php b/src/applications/search/controller/PhabricatorSearchEditController.php index c7f6c860e9..a526091503 100644 --- a/src/applications/search/controller/PhabricatorSearchEditController.php +++ b/src/applications/search/controller/PhabricatorSearchEditController.php @@ -6,11 +6,31 @@ final class PhabricatorSearchEditController public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); + $id = $request->getURIData('id'); + if ($id) { + $named_query = id(new PhabricatorNamedQueryQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$named_query) { + return new Aphront404Response(); + } + + $query_key = $named_query->getQueryKey(); + } else { + $query_key = $request->getURIData('queryKey'); + $named_query = null; + } + $saved_query = id(new PhabricatorSavedQueryQuery()) ->setViewer($viewer) - ->withQueryKeys(array($request->getURIData('queryKey'))) + ->withQueryKeys(array($query_key)) ->executeOne(); - if (!$saved_query) { return new Aphront404Response(); } @@ -20,11 +40,6 @@ final class PhabricatorSearchEditController $complete_uri = $engine->getQueryManagementURI(); $cancel_uri = $complete_uri; - $named_query = id(new PhabricatorNamedQueryQuery()) - ->setViewer($viewer) - ->withQueryKeys(array($saved_query->getQueryKey())) - ->withUserPHIDs(array($viewer->getPHID())) - ->executeOne(); if (!$named_query) { $named_query = id(new PhabricatorNamedQuery()) ->setUserPHID($viewer->getPHID()) @@ -36,12 +51,27 @@ final class PhabricatorSearchEditController // management interface. $cancel_uri = $engine->getQueryResultsPageURI( $saved_query->getQueryKey()); + + $is_new = true; + } else { + $is_new = false; } + $can_global = ($viewer->getIsAdmin() && $is_new); + + $v_global = false; + $e_name = true; $errors = array(); if ($request->isFormPost()) { + if ($can_global) { + $v_global = $request->getBool('global'); + if ($v_global) { + $named_query->setUserPHID(PhabricatorNamedQuery::SCOPE_GLOBAL); + } + } + $named_query->setQueryName($request->getStr('name')); if (!strlen($named_query->getQueryName())) { $e_name = pht('Required'); @@ -51,6 +81,7 @@ final class PhabricatorSearchEditController } if (!$errors) { + $named_query->save(); return id(new AphrontRedirectResponse())->setURI($complete_uri); } @@ -66,6 +97,18 @@ final class PhabricatorSearchEditController ->setValue($named_query->getQueryName()) ->setError($e_name)); + if ($can_global) { + $form->appendChild( + id(new AphrontFormCheckboxControl()) + ->addCheckbox( + 'global', + '1', + pht( + 'Save this query as a global query, making it visible to '. + 'all users.'), + $v_global)); + } + $form->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save Query')) diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index 5b2f7827f9..6122e66e23 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -474,8 +474,12 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { if ($this->namedQueries === null) { $named_queries = id(new PhabricatorNamedQueryQuery()) ->setViewer($viewer) - ->withUserPHIDs(array($viewer->getPHID())) ->withEngineClassNames(array(get_class($this))) + ->withUserPHIDs( + array( + $viewer->getPHID(), + PhabricatorNamedQuery::SCOPE_GLOBAL, + )) ->execute(); $named_queries = mpull($named_queries, null, 'getQueryKey'); @@ -494,7 +498,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { unset($builtin[$key]); } - $named_queries = msort($named_queries, 'getSortKey'); + $named_queries = msortv($named_queries, 'getNamedQuerySortVector'); $this->namedQueries = $named_queries; } @@ -511,6 +515,34 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { return $named_queries; } + public function getDefaultQueryKey() { + $viewer = $this->requireViewer(); + + $configs = id(new PhabricatorNamedQueryConfigQuery()) + ->setViewer($viewer) + ->withEngineClassNames(array(get_class($this))) + ->withScopePHIDs( + array( + $viewer->getPHID(), + PhabricatorNamedQueryConfig::SCOPE_GLOBAL, + )) + ->execute(); + $configs = msortv($configs, 'getStrengthSortVector'); + + $key_pinned = PhabricatorNamedQueryConfig::PROPERTY_PINNED; + $map = $this->loadEnabledNamedQueries(); + foreach ($configs as $config) { + $pinned = $config->getConfigProperty($key_pinned); + if (!isset($map[$pinned])) { + continue; + } + + return $pinned; + } + + return head_key($map); + } + protected function setQueryProjects( PhabricatorCursorPagedPolicyAwareQuery $query, PhabricatorSavedQuery $saved) { @@ -603,7 +635,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { $sequence = 0; foreach ($names as $key => $name) { $queries[$key] = id(new PhabricatorNamedQuery()) - ->setUserPHID($this->requireViewer()->getPHID()) + ->setUserPHID(PhabricatorNamedQuery::SCOPE_GLOBAL) ->setEngineClassName(get_class($this)) ->setQueryName($name) ->setQueryKey($key) diff --git a/src/applications/search/engine/PhabricatorProfileMenuEngine.php b/src/applications/search/engine/PhabricatorProfileMenuEngine.php index ba9c2b5e66..172ad51f71 100644 --- a/src/applications/search/engine/PhabricatorProfileMenuEngine.php +++ b/src/applications/search/engine/PhabricatorProfileMenuEngine.php @@ -241,7 +241,14 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { case 'view': $navigation->selectFilter($selected_item->getDefaultMenuItemKey()); - $content = $this->buildItemViewContent($selected_item); + try { + $content = $this->buildItemViewContent($selected_item); + } catch (Exception $ex) { + $content = id(new PHUIInfoView()) + ->setTitle(pht('Unable to Render Dashboard')) + ->setErrors(array($ex->getMessage())); + } + $crumbs->addTextCrumb($selected_item->getDisplayName()); if (!$content) { return new Aphront404Response(); @@ -461,7 +468,7 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { $default = null; $first = null; foreach ($items as $item) { - if (!$item->canMakeDefault()) { + if (!$item->canMakeDefault() || $item->isDisabled()) { continue; } diff --git a/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php b/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php new file mode 100644 index 0000000000..4344fe1623 --- /dev/null +++ b/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php @@ -0,0 +1,233 @@ +getPHID(); + $engine = $object->newFerretEngine(); + + $is_closed = 0; + $author_phid = null; + $owner_phid = null; + foreach ($document->getRelationshipData() as $relationship) { + list($related_type, $related_phid) = $relationship; + switch ($related_type) { + case PhabricatorSearchRelationship::RELATIONSHIP_OPEN: + $is_closed = 0; + break; + case PhabricatorSearchRelationship::RELATIONSHIP_CLOSED: + $is_closed = 1; + break; + case PhabricatorSearchRelationship::RELATIONSHIP_OWNER: + $owner_phid = $related_phid; + break; + case PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED: + $owner_phid = null; + break; + case PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR: + $author_phid = $related_phid; + break; + } + } + + $stemmer = $engine->newStemmer(); + + // Copy all of the "title" and "body" fields to create new "core" fields. + // This allows users to search "in title or body" with the "core:" prefix. + $document_fields = $document->getFieldData(); + $virtual_fields = array(); + foreach ($document_fields as $field) { + $virtual_fields[] = $field; + + list($key, $raw_corpus) = $field; + switch ($key) { + case PhabricatorSearchDocumentFieldType::FIELD_TITLE: + case PhabricatorSearchDocumentFieldType::FIELD_BODY: + $virtual_fields[] = array( + PhabricatorSearchDocumentFieldType::FIELD_CORE, + $raw_corpus, + ); + break; + } + } + + $key_all = PhabricatorSearchDocumentFieldType::FIELD_ALL; + + $empty_template = array( + 'raw' => array(), + 'term' => array(), + 'normal' => array(), + ); + + $ferret_corpus_map = array( + $key_all => $empty_template, + ); + + foreach ($virtual_fields as $field) { + list($key, $raw_corpus) = $field; + if (!strlen($raw_corpus)) { + continue; + } + + $term_corpus = $engine->newTermsCorpus($raw_corpus); + + $normal_corpus = $stemmer->stemCorpus($raw_corpus); + $normal_corpus = $engine->newTermsCorpus($normal_corpus); + + if (!isset($ferret_corpus_map[$key])) { + $ferret_corpus_map[$key] = $empty_template; + } + + $ferret_corpus_map[$key]['raw'][] = $raw_corpus; + $ferret_corpus_map[$key]['term'][] = $term_corpus; + $ferret_corpus_map[$key]['normal'][] = $normal_corpus; + + $ferret_corpus_map[$key_all]['raw'][] = $raw_corpus; + $ferret_corpus_map[$key_all]['term'][] = $term_corpus; + $ferret_corpus_map[$key_all]['normal'][] = $normal_corpus; + } + + $ferret_fields = array(); + $ngrams_source = array(); + foreach ($ferret_corpus_map as $key => $fields) { + $raw_corpus = $fields['raw']; + $raw_corpus = implode("\n", $raw_corpus); + if (strlen($raw_corpus)) { + $ngrams_source[] = $raw_corpus; + } + + $normal_corpus = $fields['normal']; + $normal_corpus = implode("\n", $normal_corpus); + if (strlen($normal_corpus)) { + $ngrams_source[] = $normal_corpus; + } + + $term_corpus = $fields['term']; + $term_corpus = implode("\n", $term_corpus); + if (strlen($term_corpus)) { + $ngrams_source[] = $term_corpus; + } + + $ferret_fields[] = array( + 'fieldKey' => $key, + 'rawCorpus' => $raw_corpus, + 'termCorpus' => $term_corpus, + 'normalCorpus' => $normal_corpus, + ); + } + $ngrams_source = implode("\n", $ngrams_source); + + $ngrams = $engine->getTermNgramsFromString($ngrams_source); + + $object->openTransaction(); + + try { + $conn = $object->establishConnection('w'); + $this->deleteOldDocument($engine, $object, $document); + + queryfx( + $conn, + 'INSERT INTO %T (objectPHID, isClosed, epochCreated, epochModified, + authorPHID, ownerPHID) VALUES (%s, %d, %d, %d, %ns, %ns)', + $engine->getDocumentTableName(), + $object->getPHID(), + $is_closed, + $document->getDocumentCreated(), + $document->getDocumentModified(), + $author_phid, + $owner_phid); + + $document_id = $conn->getInsertID(); + foreach ($ferret_fields as $ferret_field) { + queryfx( + $conn, + 'INSERT INTO %T (documentID, fieldKey, rawCorpus, termCorpus, + normalCorpus) VALUES (%d, %s, %s, %s, %s)', + $engine->getFieldTableName(), + $document_id, + $ferret_field['fieldKey'], + $ferret_field['rawCorpus'], + $ferret_field['termCorpus'], + $ferret_field['normalCorpus']); + } + + $sql = array(); + foreach ($ngrams as $ngram) { + $sql[] = qsprintf( + $conn, + '(%d, %s)', + $document_id, + $ngram); + } + + foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { + queryfx( + $conn, + 'INSERT INTO %T (documentID, ngram) VALUES %Q', + $engine->getNgramsTableName(), + $chunk); + } + } catch (Exception $ex) { + $object->killTransaction(); + throw $ex; + } + + $object->saveTransaction(); + } + + + private function deleteOldDocument( + PhabricatorFerretEngine $engine, + $object, + PhabricatorSearchAbstractDocument $document) { + + $conn = $object->establishConnection('w'); + + $old_document = queryfx_one( + $conn, + 'SELECT * FROM %T WHERE objectPHID = %s', + $engine->getDocumentTableName(), + $object->getPHID()); + if (!$old_document) { + return; + } + + $old_id = $old_document['id']; + + queryfx( + $conn, + 'DELETE FROM %T WHERE id = %d', + $engine->getDocumentTableName(), + $old_id); + + queryfx( + $conn, + 'DELETE FROM %T WHERE documentID = %d', + $engine->getFieldTableName(), + $old_id); + + queryfx( + $conn, + 'DELETE FROM %T WHERE documentID = %d', + $engine->getNgramsTableName(), + $old_id); + } + +} diff --git a/src/applications/search/engineextension/PhabricatorFerretSearchEngineExtension.php b/src/applications/search/engineextension/PhabricatorFerretSearchEngineExtension.php new file mode 100644 index 0000000000..02aadf7336 --- /dev/null +++ b/src/applications/search/engineextension/PhabricatorFerretSearchEngineExtension.php @@ -0,0 +1,70 @@ +newFerretEngine(); + + $raw_query = $map['query']; + + $compiler = id(new PhutilSearchQueryCompiler()) + ->setEnableFunctions(true); + + $raw_tokens = $compiler->newTokens($raw_query); + + $fulltext_tokens = array(); + foreach ($raw_tokens as $raw_token) { + $fulltext_token = id(new PhabricatorFulltextToken()) + ->setToken($raw_token); + + $fulltext_tokens[] = $fulltext_token; + } + + $query->withFerretConstraint($engine, $fulltext_tokens); + } + + public function getSearchFields($object) { + $fields = array(); + + $fields[] = id(new PhabricatorSearchTextField()) + ->setKey('query') + ->setLabel(pht('Query (Prototype)')) + ->setDescription(pht('Fulltext search.')); + + return $fields; + } + + public function getSearchAttachments($object) { + return array(); + } + + +} diff --git a/src/applications/search/engineextension/PhabricatorLiskFulltextEngineExtension.php b/src/applications/search/engineextension/PhabricatorLiskFulltextEngineExtension.php index 9cbe384a10..1626d248da 100644 --- a/src/applications/search/engineextension/PhabricatorLiskFulltextEngineExtension.php +++ b/src/applications/search/engineextension/PhabricatorLiskFulltextEngineExtension.php @@ -9,7 +9,7 @@ final class PhabricatorLiskFulltextEngineExtension return pht('Lisk Builtin Properties'); } - public function shouldIndexFulltextObject($object) { + public function shouldEnrichFulltextObject($object) { if (!($object instanceof PhabricatorLiskDAO)) { return false; } @@ -21,7 +21,7 @@ final class PhabricatorLiskFulltextEngineExtension return true; } - public function indexFulltextObject( + public function enrichFulltextObject( $object, PhabricatorSearchAbstractDocument $document) { diff --git a/src/applications/search/ferret/PhabricatorFerretEngine.php b/src/applications/search/ferret/PhabricatorFerretEngine.php new file mode 100644 index 0000000000..219130c02c --- /dev/null +++ b/src/applications/search/ferret/PhabricatorFerretEngine.php @@ -0,0 +1,277 @@ +getFunctionMap(); + if (!isset($map[$function])) { + throw new PhutilSearchQueryCompilerSyntaxException( + pht( + 'Unknown search function "%s". Supported functions are: %s.', + $function, + implode(', ', array_keys($map)))); + } + + return $map[$function]['field']; + } + + public function getAllFunctionFields() { + $map = $this->getFunctionMap(); + + $fields = array(); + foreach ($map as $key => $spec) { + $fields[] = $spec['field']; + } + + return $fields; + } + + protected function getFunctionMap() { + return array( + 'all' => array( + 'field' => PhabricatorSearchDocumentFieldType::FIELD_ALL, + 'aliases' => array( + 'any', + ), + ), + 'title' => array( + 'field' => PhabricatorSearchDocumentFieldType::FIELD_TITLE, + 'aliases' => array(), + ), + 'body' => array( + 'field' => PhabricatorSearchDocumentFieldType::FIELD_BODY, + 'aliases' => array(), + ), + 'core' => array( + 'field' => PhabricatorSearchDocumentFieldType::FIELD_CORE, + 'aliases' => array(), + ), + 'comment' => array( + 'field' => PhabricatorSearchDocumentFieldType::FIELD_COMMENT, + 'aliases' => array( + 'comments', + ), + ), + ); + } + + public function newStemmer() { + return new PhutilSearchStemmer(); + } + + public function tokenizeString($value) { + $value = trim($value, ' '); + $value = preg_split('/ +/', $value); + return $value; + } + + public function getTermNgramsFromString($string) { + return $this->getNgramsFromString($string, true); + } + + public function getSubstringNgramsFromString($string) { + return $this->getNgramsFromString($string, false); + } + + private function getNgramsFromString($value, $as_term) { + $tokens = $this->tokenizeString($value); + + $ngrams = array(); + foreach ($tokens as $token) { + $token = phutil_utf8_strtolower($token); + + if ($as_term) { + $token = ' '.$token.' '; + } + + $token_v = phutil_utf8v($token); + $len = (count($token_v) - 2); + for ($ii = 0; $ii < $len; $ii++) { + $ngram = array_slice($token_v, $ii, 3); + $ngram = implode('', $ngram); + $ngrams[$ngram] = $ngram; + } + } + + ksort($ngrams); + + return array_keys($ngrams); + } + + public function newTermsCorpus($raw_corpus) { + $term_corpus = strtr( + $raw_corpus, + array( + '!' => ' ', + '"' => ' ', + '#' => ' ', + '$' => ' ', + '%' => ' ', + '&' => ' ', + '(' => ' ', + ')' => ' ', + '*' => ' ', + '+' => ' ', + ',' => ' ', + '-' => ' ', + '/' => ' ', + ':' => ' ', + ';' => ' ', + '<' => ' ', + '=' => ' ', + '>' => ' ', + '?' => ' ', + '@' => ' ', + '[' => ' ', + '\\' => ' ', + ']' => ' ', + '^' => ' ', + '`' => ' ', + '{' => ' ', + '|' => ' ', + '}' => ' ', + '~' => ' ', + '.' => ' ', + '_' => ' ', + "\n" => ' ', + "\r" => ' ', + "\t" => ' ', + )); + + // NOTE: Single quotes divide terms only if they're at a word boundary. + // In contractions, like "whom'st've", the entire word is a single term. + $term_corpus = preg_replace('/(^| )[\']+/', ' ', $term_corpus); + $term_corpus = preg_replace('/[\']+( |$)/', ' ', $term_corpus); + + $term_corpus = preg_replace('/\s+/u', ' ', $term_corpus); + $term_corpus = trim($term_corpus, ' '); + + if (strlen($term_corpus)) { + $term_corpus = ' '.$term_corpus.' '; + } + + return $term_corpus; + } + +/* -( Schema )------------------------------------------------------------- */ + + public function getDocumentTableName() { + $application = $this->getApplicationName(); + $scope = $this->getScopeName(); + + return "{$application}_{$scope}_fdocument"; + } + + public function getDocumentSchemaColumns() { + return array( + 'id' => 'auto', + 'objectPHID' => 'phid', + 'isClosed' => 'bool', + 'authorPHID' => 'phid?', + 'ownerPHID' => 'phid?', + 'epochCreated' => 'epoch', + 'epochModified' => 'epoch', + ); + } + + public function getDocumentSchemaKeys() { + return array( + 'PRIMARY' => array( + 'columns' => array('id'), + 'unique' => true, + ), + 'key_object' => array( + 'columns' => array('objectPHID'), + 'unique' => true, + ), + 'key_author' => array( + 'columns' => array('authorPHID'), + ), + 'key_owner' => array( + 'columns' => array('ownerPHID'), + ), + 'key_created' => array( + 'columns' => array('epochCreated'), + ), + 'key_modified' => array( + 'columns' => array('epochModified'), + ), + ); + } + + public function getFieldTableName() { + $application = $this->getApplicationName(); + $scope = $this->getScopeName(); + + return "{$application}_{$scope}_ffield"; + } + + public function getFieldSchemaColumns() { + return array( + 'id' => 'auto', + 'documentID' => 'uint32', + 'fieldKey' => 'text4', + 'rawCorpus' => 'sort', + 'termCorpus' => 'sort', + 'normalCorpus' => 'sort', + ); + } + + public function getFieldSchemaKeys() { + return array( + 'PRIMARY' => array( + 'columns' => array('id'), + 'unique' => true, + ), + 'key_documentfield' => array( + 'columns' => array('documentID', 'fieldKey'), + 'unique' => true, + ), + ); + } + + public function getNgramsTableName() { + $application = $this->getApplicationName(); + $scope = $this->getScopeName(); + + return "{$application}_{$scope}_fngrams"; + } + + public function getNgramsSchemaColumns() { + return array( + 'id' => 'auto', + 'documentID' => 'uint32', + 'ngram' => 'char3', + ); + } + + public function getNgramsSchemaKeys() { + return array( + 'PRIMARY' => array( + 'columns' => array('id'), + 'unique' => true, + ), + 'key_ngram' => array( + 'columns' => array('ngram', 'documentID'), + ), + 'key_object' => array( + 'columns' => array('documentID'), + ), + ); + } + +} diff --git a/src/applications/search/ferret/PhabricatorFerretInterface.php b/src/applications/search/ferret/PhabricatorFerretInterface.php new file mode 100644 index 0000000000..cdb651b6cf --- /dev/null +++ b/src/applications/search/ferret/PhabricatorFerretInterface.php @@ -0,0 +1,7 @@ +engine = $engine; + return $this; + } + + public function getEngine() { + return $this->engine; + } + + public function setPHID($phid) { + $this->phid = $phid; + return $this; + } + + public function getPHID() { + return $this->phid; + } + + public function setRelevance($relevance) { + $this->relevance = $relevance; + return $this; + } + + public function getRelevance() { + return $this->relevance; + } + + public function getRelevanceSortVector() { + $engine = $this->getEngine(); + + return id(new PhutilSortVector()) + ->addInt($engine->getObjectTypeRelevance()) + ->addInt(-$this->getRelevance()); + } + +} diff --git a/src/applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php b/src/applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php new file mode 100644 index 0000000000..a8690f1d8b --- /dev/null +++ b/src/applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php @@ -0,0 +1,27 @@ + ' Hear ye hear ye ', + "Thou whom'st've art worthy." => " Thou whom'st've art worthy ", + 'Guaranteed to contain "food".' => ' Guaranteed to contain food ', + 'http://example.org/path/to/file.jpg' => + ' http example org path to file jpg ', + ); + + $engine = new ManiphestTaskFerretEngine(); + + foreach ($map as $input => $expect) { + $actual = $engine->newTermsCorpus($input); + + $this->assertEqual( + $expect, + $actual, + pht('Terms corpus for: %s', $input)); + } + } + +} diff --git a/src/applications/search/field/PhabricatorSearchCheckboxesField.php b/src/applications/search/field/PhabricatorSearchCheckboxesField.php index 5a552362bf..e1a72f4576 100644 --- a/src/applications/search/field/PhabricatorSearchCheckboxesField.php +++ b/src/applications/search/field/PhabricatorSearchCheckboxesField.php @@ -18,6 +18,14 @@ final class PhabricatorSearchCheckboxesField return array(); } + protected function didReadValueFromSavedQuery($value) { + if (!is_array($value)) { + return array(); + } + + return $value; + } + protected function getValueFromRequest(AphrontRequest $request, $key) { return $this->getListFromRequest($request, $key); } diff --git a/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php index 8c75c17e36..f6aead3759 100644 --- a/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php @@ -105,42 +105,6 @@ class PhabricatorElasticFulltextStorageEngine $this->executeRequest($host, "/{$type}/{$phid}/", $spec, 'PUT'); } - public function reconstructDocument($phid) { - $type = phid_get_type($phid); - $host = $this->getHostForRead(); - $response = $this->executeRequest($host, "/{$type}/{$phid}", array()); - - if (empty($response['exists'])) { - return null; - } - - $hit = $response['_source']; - - $doc = new PhabricatorSearchAbstractDocument(); - $doc->setPHID($phid); - $doc->setDocumentType($response['_type']); - $doc->setDocumentTitle($hit['title']); - $doc->setDocumentCreated($hit['dateCreated']); - $doc->setDocumentModified($hit[$this->getTimestampField()]); - - foreach ($hit['field'] as $fdef) { - $field_type = $fdef['type']; - $doc->addField($field_type, $hit[$field_type], $fdef['aux']); - } - - foreach ($hit['relationship'] as $rtype => $rships) { - foreach ($rships as $rship) { - $doc->addRelationship( - $rtype, - $rship['phid'], - $rship['phidType'], - $rship['when']); - } - } - - return $doc; - } - private function buildSpec(PhabricatorSavedQuery $query) { $q = new PhabricatorElasticsearchQueryBuilder('bool'); $query_string = $query->getParameter('query'); diff --git a/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php new file mode 100644 index 0000000000..74e8771de7 --- /dev/null +++ b/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php @@ -0,0 +1,123 @@ +setAncestorClass('PhabricatorFerretInterface') + ->execute(); + + $type_map = array(); + foreach ($all_objects as $object) { + $phid_type = phid_get_type($object->generatePHID()); + + $type_map[$phid_type] = array( + 'object' => $object, + 'engine' => $object->newFerretEngine(), + ); + } + + $types = $query->getParameter('types'); + if ($types) { + $type_map = array_select_keys($type_map, $types); + } + + $offset = (int)$query->getParameter('offset', 0); + $limit = (int)$query->getParameter('limit', 25); + + // NOTE: For now, it's okay to query with the omnipotent viewer here + // because we're just returning PHIDs which we'll filter later. + $viewer = PhabricatorUser::getOmnipotentUser(); + + $type_results = array(); + $metadata = array(); + foreach ($type_map as $type => $spec) { + $engine = $spec['engine']; + $object = $spec['object']; + + $local_query = new PhabricatorSavedQuery(); + $local_query->setParameter('query', $query->getParameter('query')); + + $project_phids = $query->getParameter('projectPHIDs'); + if ($project_phids) { + $local_query->setParameter('projectPHIDs', $project_phids); + } + + $subscriber_phids = $query->getParameter('subscriberPHIDs'); + if ($subscriber_phids) { + $local_query->setParameter('subscriberPHIDs', $subscriber_phids); + } + + $search_engine = $engine->newSearchEngine() + ->setViewer($viewer); + + $engine_query = $search_engine->buildQueryFromSavedQuery($local_query) + ->setViewer($viewer); + + $engine_query + ->withFerretQuery($engine, $query) + ->setOrder('relevance') + ->setLimit($offset + $limit); + + $results = $engine_query->execute(); + $results = mpull($results, null, 'getPHID'); + $type_results[$type] = $results; + + $metadata += $engine_query->getFerretMetadata(); + } + + $list = array(); + foreach ($type_results as $type => $results) { + $list += $results; + } + + // Currently, the list is grouped by object type. For example, all the + // tasks might be first, then all the revisions, and so on. In each group, + // the results are ordered properly. + + // Reorder the results so that the highest-ranking results come first, + // no matter which object types they belong to. + + $metadata = msort($metadata, 'getRelevanceSortVector'); + $list = array_select_keys($list, array_keys($metadata)) + $list; + + $result_slice = array_slice($list, $offset, $limit, true); + return array_keys($result_slice); + } + + public function indexExists() { + return true; + } + + public function getIndexStats() { + return false; + } + + public function getFulltextTokens() { + return $this->fulltextTokens; + } + + +} diff --git a/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php index 588ccc3e5e..ba019ea593 100644 --- a/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php @@ -53,15 +53,6 @@ abstract class PhabricatorFulltextStorageEngine extends Phobject { abstract public function reindexAbstractDocument( PhabricatorSearchAbstractDocument $document); - /** - * Reconstruct the document for a given PHID. This is used for debugging - * and does not need to be perfect if it is unreasonable to implement it. - * - * @param phid Document PHID to reconstruct. - * @return PhabricatorSearchAbstractDocument Abstract document. - */ - abstract public function reconstructDocument($phid); - /** * Execute a search query. * diff --git a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php index b22076cf92..c2e38d2db7 100644 --- a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php @@ -91,73 +91,6 @@ final class PhabricatorMySQLFulltextStorageEngine } - /** - * Rebuild the PhabricatorSearchAbstractDocument that was used to index - * an object out of the index itself. This is primarily useful for debugging, - * as it allows you to inspect the search index representation of a - * document. - * - * @param phid PHID of a document which exists in the search index. - * @return null|PhabricatorSearchAbstractDocument Abstract document object - * which corresponds to the original abstract document used to - * build the document index. - */ - public function reconstructDocument($phid) { - $dao_doc = new PhabricatorSearchDocument(); - $dao_field = new PhabricatorSearchDocumentField(); - $dao_relationship = new PhabricatorSearchDocumentRelationship(); - - $t_doc = $dao_doc->getTableName(); - $t_field = $dao_field->getTableName(); - $t_relationship = $dao_relationship->getTableName(); - - $doc = queryfx_one( - $dao_doc->establishConnection('r'), - 'SELECT * FROM %T WHERE phid = %s', - $t_doc, - $phid); - - if (!$doc) { - return null; - } - - $fields = queryfx_all( - $dao_field->establishConnection('r'), - 'SELECT * FROM %T WHERE phid = %s', - $t_field, - $phid); - - $relationships = queryfx_all( - $dao_relationship->establishConnection('r'), - 'SELECT * FROM %T WHERE phid = %s', - $t_relationship, - $phid); - - $adoc = id(new PhabricatorSearchAbstractDocument()) - ->setPHID($phid) - ->setDocumentType($doc['documentType']) - ->setDocumentTitle($doc['documentTitle']) - ->setDocumentCreated($doc['documentCreated']) - ->setDocumentModified($doc['documentModified']); - - foreach ($fields as $field) { - $adoc->addField( - $field['field'], - $field['corpus'], - $field['auxPHID']); - } - - foreach ($relationships as $relationship) { - $adoc->addRelationship( - $relationship['relation'], - $relationship['relatedPHID'], - $relationship['relatedType'], - $relationship['relatedTime']); - } - - return $adoc; - } - public function executeSearch(PhabricatorSavedQuery $query) { $table = new PhabricatorSearchDocument(); $document_table = $table->getTableName(); @@ -235,7 +168,7 @@ final class PhabricatorMySQLFulltextStorageEngine $value = $stemmer->stemToken($value); } - if (phutil_utf8_strlen($value) < $min_length) { + if ($this->isShortToken($value, $min_length)) { $fulltext_token->setIsShort(true); continue; } @@ -549,4 +482,23 @@ final class PhabricatorMySQLFulltextStorageEngine return array($min_len, $stopwords); } + private function isShortToken($value, $min_length) { + // NOTE: The engine tokenizes internally on periods, so terms in the form + // "ab.cd", where short substrings are separated by periods, do not produce + // any queryable tokens. These terms are meaningful if at least one + // substring is longer than the minimum length, like "example.py". See + // T12928. This also applies to words with intermediate apostrophes, like + // "to's". + + $parts = preg_split('/[.\']+/', $value); + + foreach ($parts as $part) { + if (phutil_utf8_strlen($part) >= $min_length) { + return false; + } + } + + return true; + } + } diff --git a/src/applications/search/index/PhabricatorFulltextEngine.php b/src/applications/search/index/PhabricatorFulltextEngine.php index 9f20917b3f..e1b2f4471d 100644 --- a/src/applications/search/index/PhabricatorFulltextEngine.php +++ b/src/applications/search/index/PhabricatorFulltextEngine.php @@ -26,9 +26,16 @@ abstract class PhabricatorFulltextEngine $object = $this->getObject(); $extensions = PhabricatorFulltextEngineExtension::getAllExtensions(); + + $enrich_extensions = array(); + $index_extensions = array(); foreach ($extensions as $key => $extension) { - if (!$extension->shouldIndexFulltextObject($object)) { - unset($extensions[$key]); + if ($extension->shouldEnrichFulltextObject($object)) { + $enrich_extensions[] = $extension; + } + + if ($extension->shouldIndexFulltextObject($object)) { + $index_extensions[] = $extension; } } @@ -36,7 +43,11 @@ abstract class PhabricatorFulltextEngine $this->buildAbstractDocument($document, $object); - foreach ($extensions as $extension) { + foreach ($enrich_extensions as $extension) { + $extension->enrichFulltextObject($object, $document); + } + + foreach ($index_extensions as $extension) { $extension->indexFulltextObject($object, $document); } diff --git a/src/applications/search/index/PhabricatorFulltextEngineExtension.php b/src/applications/search/index/PhabricatorFulltextEngineExtension.php index c52b35a58a..52aabe7c8b 100644 --- a/src/applications/search/index/PhabricatorFulltextEngineExtension.php +++ b/src/applications/search/index/PhabricatorFulltextEngineExtension.php @@ -12,11 +12,25 @@ abstract class PhabricatorFulltextEngineExtension extends Phobject { abstract public function getExtensionName(); - abstract public function shouldIndexFulltextObject($object); + public function shouldEnrichFulltextObject($object) { + return false; + } - abstract public function indexFulltextObject( + public function enrichFulltextObject( $object, - PhabricatorSearchAbstractDocument $document); + PhabricatorSearchAbstractDocument $document) { + return; + } + + public function shouldIndexFulltextObject($object) { + return false; + } + + public function indexFulltextObject( + $object, + PhabricatorSearchAbstractDocument $document) { + return; + } final public static function getAllExtensions() { return id(new PhutilClassMapQuery()) diff --git a/src/applications/search/management/PhabricatorSearchManagementWorkflow.php b/src/applications/search/management/PhabricatorSearchManagementWorkflow.php index 86d8c104cb..43dad986ea 100644 --- a/src/applications/search/management/PhabricatorSearchManagementWorkflow.php +++ b/src/applications/search/management/PhabricatorSearchManagementWorkflow.php @@ -13,7 +13,7 @@ abstract class PhabricatorSearchManagementWorkflow $config_value = PhabricatorEnv::getEnvConfig($config_key); try { - PhabricatorClusterSearchConfigOptionType::validateValue($config_value); + PhabricatorClusterSearchConfigType::validateValue($config_value); } catch (Exception $ex) { throw new PhutilArgumentUsageException( pht( diff --git a/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php index aa42d56cfb..40275a8d7a 100644 --- a/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php @@ -31,7 +31,7 @@ final class PhabricatorApplicationProfileMenuItem return $name; } - return $application->getName(); + return $application->getMenuName(); } public function buildEditEngineFields( diff --git a/src/applications/search/query/PhabricatorFulltextToken.php b/src/applications/search/query/PhabricatorFulltextToken.php index d42c15e9e6..4edeb098a9 100644 --- a/src/applications/search/query/PhabricatorFulltextToken.php +++ b/src/applications/search/query/PhabricatorFulltextToken.php @@ -64,7 +64,7 @@ final class PhabricatorFulltextToken extends Phobject { $tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($shade) + ->setColor($shade) ->setName($token->getValue()); if ($tip !== null) { diff --git a/src/applications/search/query/PhabricatorNamedQueryConfigQuery.php b/src/applications/search/query/PhabricatorNamedQueryConfigQuery.php new file mode 100644 index 0000000000..6c4fad31a3 --- /dev/null +++ b/src/applications/search/query/PhabricatorNamedQueryConfigQuery.php @@ -0,0 +1,64 @@ +ids = $ids; + return $this; + } + + public function withScopePHIDs(array $scope_phids) { + $this->scopePHIDs = $scope_phids; + return $this; + } + + public function withEngineClassNames(array $engine_class_names) { + $this->engineClassNames = $engine_class_names; + return $this; + } + + public function newResultObject() { + return new PhabricatorNamedQueryConfig(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->engineClassNames !== null) { + $where[] = qsprintf( + $conn, + 'engineClassName IN (%Ls)', + $this->engineClassNames); + } + + if ($this->scopePHIDs !== null) { + $where[] = qsprintf( + $conn, + 'scopePHID IN (%Ls)', + $this->scopePHIDs); + } + + return $where; + } + + public function getQueryApplicationClass() { + return 'PhabricatorSearchApplication'; + } + +} diff --git a/src/applications/search/query/PhabricatorNamedQueryQuery.php b/src/applications/search/query/PhabricatorNamedQueryQuery.php index ce54ae9a84..3decff5494 100644 --- a/src/applications/search/query/PhabricatorNamedQueryQuery.php +++ b/src/applications/search/query/PhabricatorNamedQueryQuery.php @@ -28,55 +28,46 @@ final class PhabricatorNamedQueryQuery return $this; } - protected function loadPage() { - $table = new PhabricatorNamedQuery(); - $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); + public function newResultObject() { + return new PhabricatorNamedQuery(); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } - if ($this->ids) { + 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->engineClassNames) { + if ($this->engineClassNames !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'engineClassName IN (%Ls)', $this->engineClassNames); } - if ($this->userPHIDs) { + if ($this->userPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'userPHID IN (%Ls)', $this->userPHIDs); } - if ($this->queryKeys) { + if ($this->queryKeys !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'queryKey IN (%Ls)', $this->queryKeys); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/search/storage/PhabricatorNamedQuery.php b/src/applications/search/storage/PhabricatorNamedQuery.php index ac34a4fa32..44d7a403b1 100644 --- a/src/applications/search/storage/PhabricatorNamedQuery.php +++ b/src/applications/search/storage/PhabricatorNamedQuery.php @@ -12,6 +12,8 @@ final class PhabricatorNamedQuery extends PhabricatorSearchDAO protected $isDisabled = 0; protected $sequence = 0; + const SCOPE_GLOBAL = 'scope.global'; + protected function getConfiguration() { return array( self::CONFIG_COLUMN_SCHEMA => array( @@ -31,8 +33,29 @@ final class PhabricatorNamedQuery extends PhabricatorSearchDAO ) + parent::getConfiguration(); } - public function getSortKey() { - return sprintf('~%010d%010d', $this->sequence, $this->getID()); + public function isGlobal() { + if ($this->getIsBuiltin()) { + return true; + } + + if ($this->getUserPHID() === self::SCOPE_GLOBAL) { + return true; + } + + return false; + } + + public function getNamedQuerySortVector() { + if (!$this->isGlobal()) { + $phase = 0; + } else { + $phase = 1; + } + + return id(new PhutilSortVector()) + ->addInt($phase) + ->addInt($this->sequence) + ->addInt($this->getID()); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -41,6 +64,7 @@ final class PhabricatorNamedQuery extends PhabricatorSearchDAO public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } @@ -49,9 +73,19 @@ final class PhabricatorNamedQuery extends PhabricatorSearchDAO } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - if ($viewer->getPHID() == $this->userPHID) { + if ($viewer->getPHID() == $this->getUserPHID()) { return true; } + + if ($this->isGlobal()) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return true; + case PhabricatorPolicyCapability::CAN_EDIT: + return $viewer->getIsAdmin(); + } + } + return false; } diff --git a/src/applications/search/storage/PhabricatorNamedQueryConfig.php b/src/applications/search/storage/PhabricatorNamedQueryConfig.php new file mode 100644 index 0000000000..d5cdfe88d0 --- /dev/null +++ b/src/applications/search/storage/PhabricatorNamedQueryConfig.php @@ -0,0 +1,92 @@ + array( + 'properties' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'engineClassName' => 'text128', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_scope' => array( + 'columns' => array('engineClassName', 'scopePHID'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public static function initializeNewQueryConfig() { + return new self(); + } + + public function isGlobal() { + return ($this->getScopePHID() == self::SCOPE_GLOBAL); + } + + public function getConfigProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + + public function setConfigProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + + public function getStrengthSortVector() { + // Apply personal preferences before global preferences. + if (!$this->isGlobal()) { + $phase = 0; + } else { + $phase = 1; + } + + return id(new PhutilSortVector()) + ->addInt($phase) + ->addInt($this->getID()); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return PhabricatorPolicies::POLICY_NOONE; + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + if ($this->isGlobal()) { + return true; + } + + if ($viewer->getPHID() == $this->getScopePHID()) { + return true; + } + + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + +} diff --git a/src/applications/settings/application/PhabricatorSettingsApplication.php b/src/applications/settings/application/PhabricatorSettingsApplication.php index d5add43d46..e5e0034e1a 100644 --- a/src/applications/settings/application/PhabricatorSettingsApplication.php +++ b/src/applications/settings/application/PhabricatorSettingsApplication.php @@ -22,10 +22,6 @@ final class PhabricatorSettingsApplication extends PhabricatorApplication { return false; } - public function isLaunchable() { - return false; - } - public function getRoutes() { $panel_pattern = '(?:page/(?P[^/]+)/(?:(?Psaved)/)?)?'; diff --git a/src/applications/settings/controller/PhabricatorSettingsMainController.php b/src/applications/settings/controller/PhabricatorSettingsMainController.php index 4d9b58658e..aec773d7c2 100644 --- a/src/applications/settings/controller/PhabricatorSettingsMainController.php +++ b/src/applications/settings/controller/PhabricatorSettingsMainController.php @@ -112,10 +112,22 @@ final class PhabricatorSettingsMainController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($panel->getPanelName()); + $crumbs->setBorder(true); + + if ($this->user) { + $header_text = pht('Edit Settings (%s)', $user->getUserName()); + } else { + $header_text = pht('Edit Global Settings'); + } + + $header = id(new PHUIHeaderView()) + ->setHeader($header_text); $title = $panel->getPanelName(); $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFixed(true) ->setNavigation($nav) ->setMainColumn($response); @@ -197,7 +209,10 @@ final class PhabricatorSettingsMainController if ($panel->getPanelGroupKey() != $group_key) { $group_key = $panel->getPanelGroupKey(); $group = $panel->getPanelGroup(); - $nav->addLabel($group->getPanelGroupName()); + $panel_name = $group->getPanelGroupName(); + if ($panel_name) { + $nav->addLabel($panel_name); + } } $nav->addFilter($panel->getPanelKey(), $panel->getPanelName()); diff --git a/src/applications/settings/editor/PhabricatorSettingsEditEngine.php b/src/applications/settings/editor/PhabricatorSettingsEditEngine.php index 4bcfa08d1d..30e831543d 100644 --- a/src/applications/settings/editor/PhabricatorSettingsEditEngine.php +++ b/src/applications/settings/editor/PhabricatorSettingsEditEngine.php @@ -63,7 +63,13 @@ final class PhabricatorSettingsEditEngine } protected function getObjectEditTitleText($object) { - return pht('Edit Settings'); + $page = $this->getSelectedPage(); + + if ($page) { + return $page->getLabel(); + } + + return pht('Settings'); } protected function getObjectEditShortText($object) { @@ -92,6 +98,20 @@ final class PhabricatorSettingsEditEngine return pht('Settings'); } + protected function getPageHeader($object) { + $user = $object->getUser(); + if ($user) { + $text = pht('Edit Settings (%s)', $user->getUserName()); + } else { + $text = pht('Edit Global Settings'); + } + + $header = id(new PHUIHeaderView()) + ->setHeader($text); + + return $header; + } + protected function getEditorURI() { throw new PhutilMethodNotImplementedException(); } diff --git a/src/applications/settings/panel/PhabricatorActivitySettingsPanel.php b/src/applications/settings/panel/PhabricatorActivitySettingsPanel.php index a16984c140..50f951d661 100644 --- a/src/applications/settings/panel/PhabricatorActivitySettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorActivitySettingsPanel.php @@ -46,9 +46,7 @@ final class PhabricatorActivitySettingsPanel extends PhabricatorSettingsPanel { ->setLogs($logs) ->setHandles($handles); - $panel = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Account Activity Logs')) - ->setTable($table); + $panel = $this->newBox(pht('Account Activity Logs'), $table); $pager_box = id(new PHUIBoxView()) ->addMargin(PHUI::MARGIN_LARGE) diff --git a/src/applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php b/src/applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php index 87643a08ad..6ed6325d67 100644 --- a/src/applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php @@ -6,7 +6,7 @@ final class PhabricatorConpherencePreferencesSettingsPanel const PANELKEY = 'conpherence'; public function getPanelName() { - return pht('Conpherence Preferences'); + return pht('Conpherence'); } public function getPanelGroupKey() { diff --git a/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php b/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php index 27161218d1..5aad71785e 100644 --- a/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php @@ -22,7 +22,6 @@ abstract class PhabricatorEditEngineSettingsPanel $engine = id(new PhabricatorSettingsEditEngine()) ->setController($this->getController()) ->setNavigation($this->getNavigation()) - ->setHideHeader(true) ->setIsSelfEdit($is_self) ->setProfileURI($profile_uri); diff --git a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php index 31249985e4..1bdb95f568 100644 --- a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php @@ -63,7 +63,7 @@ final class PhabricatorEmailAddressesSettingsPanel $button_verify = javelin_tag( 'a', array( - 'class' => 'button small grey', + 'class' => 'button small button-grey', 'href' => $uri->alter('verify', $email->getID()), 'sigil' => 'workflow', ), @@ -72,7 +72,7 @@ final class PhabricatorEmailAddressesSettingsPanel $button_make_primary = javelin_tag( 'a', array( - 'class' => 'button small grey', + 'class' => 'button small button-grey', 'href' => $uri->alter('primary', $email->getID()), 'sigil' => 'workflow', ), @@ -81,7 +81,7 @@ final class PhabricatorEmailAddressesSettingsPanel $button_remove = javelin_tag( 'a', array( - 'class' => 'button small grey', + 'class' => 'button small button-grey', 'href' => $uri->alter('delete', $email->getID()), 'sigil' => 'workflow', ), @@ -138,23 +138,18 @@ final class PhabricatorEmailAddressesSettingsPanel $editable, )); - $view = new PHUIObjectBoxView(); - $header = new PHUIHeaderView(); - $header->setHeader(pht('Email Addresses')); - + $button = null; if ($editable) { - $button = new PHUIButtonView(); - $button->setText(pht('Add New Address')); - $button->setTag('a'); - $button->setHref($uri->alter('new', 'true')); - $button->setIcon('fa-plus'); - $button->addSigil('workflow'); - $header->addActionLink($button); + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-plus') + ->setText(pht('Add New Address')) + ->setHref($uri->alter('new', 'true')) + ->addSigil('workflow') + ->setColor(PHUIButtonView::GREY); } - $view->setHeader($header); - $view->setTable($table); - return $view; + return $this->newBox(pht('Email Addresses'), $table, array($button)); } private function returnNewAddressResponse( diff --git a/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php index 0c775c5a4d..faa79889ed 100644 --- a/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php @@ -136,6 +136,7 @@ final class PhabricatorEmailPreferencesSettingsPanel ->setHeaderText(pht('Email Preferences')) ->setFormSaved($request->getStr('saved')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); return $form_box; diff --git a/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php b/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php index 662d4e6cf6..e380248a83 100644 --- a/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php @@ -31,12 +31,10 @@ final class PhabricatorExternalAccountsSettingsPanel )) ->execute(); - $linked_head = id(new PHUIHeaderView()) - ->setHeader(pht('Linked Accounts and Authentication')); + $linked_head = pht('Linked Accounts and Authentication'); $linked = id(new PHUIObjectItemListView()) ->setUser($viewer) - ->setFlush(true) ->setNoDataString(pht('You have no linked accounts.')); $login_accounts = 0; @@ -47,7 +45,7 @@ final class PhabricatorExternalAccountsSettingsPanel } foreach ($accounts as $account) { - $item = id(new PHUIObjectItemView()); + $item = new PHUIObjectItemView(); $provider = idx($providers, $account->getProviderKey()); if ($provider) { @@ -94,12 +92,10 @@ final class PhabricatorExternalAccountsSettingsPanel $linked->addItem($item); } - $linkable_head = id(new PHUIHeaderView()) - ->setHeader(pht('Add External Account')); + $linkable_head = pht('Add External Account'); $linkable = id(new PHUIObjectItemListView()) ->setUser($viewer) - ->setFlush(true) ->setNoDataString( pht('Your account is linked with all available providers.')); @@ -118,24 +114,19 @@ final class PhabricatorExternalAccountsSettingsPanel $link_uri = '/auth/link/'.$provider->getProviderKey().'/'; - $item = id(new PHUIObjectItemView()); - $item->setHeader($provider->getProviderName()); - $item->setHref($link_uri); - $item->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-link') - ->setHref($link_uri)); + $item = id(new PHUIObjectItemView()) + ->setHeader($provider->getProviderName()) + ->setHref($link_uri) + ->addAction( + id(new PHUIListItemView()) + ->setIcon('fa-link') + ->setHref($link_uri)); $linkable->addItem($item); } - $linked_box = id(new PHUIObjectBoxView()) - ->setHeader($linked_head) - ->setObjectList($linked); - - $linkable_box = id(new PHUIObjectBoxView()) - ->setHeader($linkable_head) - ->setObjectList($linkable); + $linked_box = $this->newBox($linked_head, $linked); + $linkable_box = $this->newBox($linkable_head, $linkable); return array( $linked_box, diff --git a/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php b/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php index 77eb1c55d5..ae653e0f70 100644 --- a/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php @@ -69,7 +69,7 @@ final class PhabricatorMultiFactorSettingsPanel array( 'href' => $this->getPanelURI('?delete='.$factor->getID()), 'sigil' => 'workflow', - 'class' => 'small grey button', + 'class' => 'small button button-grey', ), pht('Remove')), ); @@ -101,33 +101,27 @@ final class PhabricatorMultiFactorSettingsPanel true, )); - $panel = new PHUIObjectBoxView(); - $header = new PHUIHeaderView(); - $help_uri = PhabricatorEnv::getDoclink( 'User Guide: Multi-Factor Authentication'); - $help_button = id(new PHUIButtonView()) + $buttons = array(); + + $buttons[] = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-plus') + ->setText(pht('Add Auth Factor')) + ->setHref($this->getPanelURI('?new=true')) + ->setWorkflow(true) + ->setColor(PHUIButtonView::GREY); + + $buttons[] = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-book') ->setText(pht('Help')) ->setHref($help_uri) - ->setTag('a') - ->setIcon('fa-info-circle'); + ->setColor(PHUIButtonView::GREY); - $create_button = id(new PHUIButtonView()) - ->setText(pht('Add Authentication Factor')) - ->setHref($this->getPanelURI('?new=true')) - ->setTag('a') - ->setWorkflow(true) - ->setIcon('fa-plus'); - - $header->setHeader(pht('Authentication Factors')); - $header->addActionLink($help_button); - $header->addActionLink($create_button); - - $panel->setHeader($header); - $panel->setTable($table); - - return $panel; + return $this->newBox(pht('Authentication Factors'), $table, $buttons); } private function processNew(AphrontRequest $request) { diff --git a/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php b/src/applications/settings/panel/PhabricatorNotificationsSettingsPanel.php similarity index 80% rename from src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php rename to src/applications/settings/panel/PhabricatorNotificationsSettingsPanel.php index c08a833f1b..e75c2b99ee 100644 --- a/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorNotificationsSettingsPanel.php @@ -1,6 +1,6 @@ getViewer(); $preferences = $this->getPreferences(); - $notifications_key = PhabricatorDesktopNotificationsSetting::SETTINGKEY; + $notifications_key = PhabricatorNotificationsSetting::SETTINGKEY; $notifications_value = $preferences->getSettingValue($notifications_key); if ($request->isFormPost()) { @@ -43,7 +43,7 @@ final class PhabricatorDesktopNotificationsSettingsPanel ->setURI($this->getPanelURI('?saved=true')); } - $title = pht('Desktop Notifications'); + $title = pht('Notifications'); $control_id = celerity_generate_unique_node_id(); $status_id = celerity_generate_unique_node_id(); $browser_status_id = celerity_generate_unique_node_id(); @@ -97,19 +97,31 @@ final class PhabricatorDesktopNotificationsSettingsPanel 'id' => $message_id, )); + $saved_box = null; + if ($request->getBool('saved')) { + $saved_box = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild(pht('Changes saved.')); + } + $status_box = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setID($status_id) ->setIsHidden(true) ->appendChild($message_container); + $status_box = id(new PHUIBoxView()) + ->addClass('mll mlr') + ->appendChild($status_box); + $control_config = array( 'controlID' => $control_id, 'statusID' => $status_id, 'messageID' => $message_id, 'browserStatusID' => $browser_status_id, 'defaultMode' => 0, - 'desktopMode' => 1, + 'desktop' => 1, + 'desktopOnly' => 2, 'cancelAsk' => $cancel_ask, 'grantedAsk' => $accept_ask, 'deniedAsk' => $reject_ask, @@ -127,16 +139,12 @@ final class PhabricatorDesktopNotificationsSettingsPanel ->setControlID($control_id) ->setName($notifications_key) ->setValue($notifications_value) - ->setOptions( - array( - 1 => pht('Send Desktop Notifications Too'), - 0 => pht('Send Application Notifications Only'), - )) + ->setOptions(PhabricatorNotificationsSetting::getOptionsMap()) ->setCaption( pht( - 'Should Phabricator send desktop notifications? These are sent '. - 'in addition to the notifications within the Phabricator '. - 'application.')) + 'Phabricator can send real-time notifications to your web browser '. + 'or to your desktop. Select where you\'d want to receive these '. + 'real-time updates.')) ->initBehavior( 'desktop-notifications-control', $control_config)) @@ -144,21 +152,17 @@ final class PhabricatorDesktopNotificationsSettingsPanel id(new AphrontFormSubmitControl()) ->setValue(pht('Save Preference'))); - $test_button = id(new PHUIButtonView()) + $button = id(new PHUIButtonView()) ->setTag('a') + ->setIcon('fa-send-o') ->setWorkflow(true) ->setText(pht('Send Test Notification')) ->setHref('/notification/test/') - ->setIcon('fa-exclamation-triangle'); + ->setColor(PHUIButtonView::GREY); - $form_box = id(new PHUIObjectBoxView()) - ->setHeader( - id(new PHUIHeaderView()) - ->setHeader(pht('Desktop Notifications')) - ->addActionLink($test_button)) - ->setForm($form) - ->setInfoView($status_box) - ->setFormSaved($request->getBool('saved')); + $form_content = array($saved_box, $status_box, $form); + $form_box = $this->newBox( + pht('Notifications'), $form_content, array($button)); $browser_status_box = id(new PHUIInfoView()) ->setID($browser_status_id) diff --git a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php index 17b8cdde95..c1250fca23 100644 --- a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php @@ -91,7 +91,7 @@ final class PhabricatorPasswordSettingsPanel extends PhabricatorSettingsPanel { // is changed here the CSRF token check will fail. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $envelope = new PhutilOpaqueEnvelope($pass); + $envelope = new PhutilOpaqueEnvelope($pass); id(new PhabricatorUserEditor()) ->setActor($user) ->changePassword($user, $envelope); @@ -172,44 +172,47 @@ final class PhabricatorPasswordSettingsPanel extends PhabricatorSettingsPanel { ->setDisableAutocomplete(true) ->setLabel(pht('New Password')) ->setError($e_new) - ->setName('new_pw')); - $form + ->setName('new_pw')) ->appendChild( id(new AphrontFormPasswordControl()) ->setDisableAutocomplete(true) ->setLabel(pht('Confirm Password')) ->setCaption($len_caption) ->setError($e_conf) - ->setName('conf_pw')); - $form + ->setName('conf_pw')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Change Password'))); - $form->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel(pht('Current Algorithm')) - ->setValue(PhabricatorPasswordHasher::getCurrentAlgorithmName( - new PhutilOpaqueEnvelope($user->getPasswordHash())))); + $properties = id(new PHUIPropertyListView()); - $form->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel(pht('Best Available Algorithm')) - ->setValue(PhabricatorPasswordHasher::getBestAlgorithmName())); + $properties->addProperty( + pht('Current Algorithm'), + PhabricatorPasswordHasher::getCurrentAlgorithmName( + new PhutilOpaqueEnvelope($user->getPasswordHash()))); - $form->appendRemarkupInstructions( - pht( - 'NOTE: Changing your password will terminate any other outstanding '. - 'login sessions.')); + $properties->addProperty( + pht('Best Available Algorithm'), + PhabricatorPasswordHasher::getBestAlgorithmName()); + $info_view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild( + pht('Changing your password will terminate any other outstanding '. + 'login sessions.')); + + $algo_box = $this->newBox(pht('Password Algorithms'), $properties); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Change Password')) ->setFormSaved($request->getStr('saved')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); return array( $form_box, + $algo_box, + $info_view, ); } diff --git a/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php b/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php index 54a217300d..13944411ed 100644 --- a/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php @@ -45,13 +45,7 @@ final class PhabricatorSSHKeysSettingsPanel extends PhabricatorSettingsPanel { $viewer, $user); - $header->setHeader(pht('SSH Public Keys')); - $header->addActionLink($ssh_actions); - - $panel->setHeader($header); - $panel->setTable($table); - - return $panel; + return $this->newBox(pht('SSH Public Keys'), $table, array($ssh_actions)); } } diff --git a/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php b/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php index a468ed53d0..eab18002a1 100644 --- a/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php @@ -58,7 +58,7 @@ final class PhabricatorSessionsSettingsPanel extends PhabricatorSettingsPanel { $button = phutil_tag( 'a', array( - 'class' => 'small grey button disabled', + 'class' => 'small button button-grey disabled', ), pht('Current')); } else { @@ -67,7 +67,7 @@ final class PhabricatorSessionsSettingsPanel extends PhabricatorSettingsPanel { 'a', array( 'href' => '/auth/session/terminate/'.$session->getID().'/', - 'class' => 'small grey button', + 'class' => 'small button button-grey', 'sigil' => 'workflow', ), pht('Terminate')); @@ -112,33 +112,27 @@ final class PhabricatorSessionsSettingsPanel extends PhabricatorSettingsPanel { 'action', )); - $terminate_button = id(new PHUIButtonView()) + $buttons = array(); + $buttons[] = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-warning') ->setText(pht('Terminate All Sessions')) ->setHref('/auth/session/terminate/all/') - ->setTag('a') ->setWorkflow(true) - ->setIcon('fa-exclamation-triangle'); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Active Login Sessions')) - ->addActionLink($terminate_button); + ->setColor(PHUIButtonView::RED); $hisec = ($viewer->getSession()->getHighSecurityUntil() - time()); if ($hisec > 0) { - $hisec_button = id(new PHUIButtonView()) + $buttons[] = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-lock') ->setText(pht('Leave High Security')) ->setHref('/auth/session/downgrade/') - ->setTag('a') ->setWorkflow(true) - ->setIcon('fa-lock'); - $header->addActionLink($hisec_button); + ->setColor(PHUIButtonView::RED); } - $panel = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setTable($table); - - return $panel; + return $this->newBox(pht('Active Login Sessions'), $table, $buttons); } } diff --git a/src/applications/settings/panel/PhabricatorSettingsPanel.php b/src/applications/settings/panel/PhabricatorSettingsPanel.php index f01c67d181..68c673b71a 100644 --- a/src/applications/settings/panel/PhabricatorSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorSettingsPanel.php @@ -284,4 +284,21 @@ abstract class PhabricatorSettingsPanel extends Phobject { $editor->applyTransactions($preferences, $xactions); } + + public function newBox($title, $content, $actions = array()) { + $header = id(new PHUIHeaderView()) + ->setHeader($title); + + foreach ($actions as $action) { + $header->addActionLink($action); + } + + $view = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($content) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG); + + return $view; + } + } diff --git a/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php b/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php index d2e0be4a53..f2021bafa5 100644 --- a/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php @@ -30,7 +30,7 @@ final class PhabricatorTokensSettingsPanel extends PhabricatorSettingsPanel { 'a', array( 'href' => '/auth/token/revoke/'.$token->getID().'/', - 'class' => 'small grey button', + 'class' => 'small button button-grey', 'sigil' => 'workflow', ), pht('Revoke')); @@ -38,7 +38,7 @@ final class PhabricatorTokensSettingsPanel extends PhabricatorSettingsPanel { $button = javelin_tag( 'a', array( - 'class' => 'small grey button disabled', + 'class' => 'small button button-grey disabled', ), pht('Revoke')); } @@ -71,22 +71,15 @@ final class PhabricatorTokensSettingsPanel extends PhabricatorSettingsPanel { 'action', )); - $terminate_button = id(new PHUIButtonView()) + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-warning') ->setText(pht('Revoke All')) ->setHref('/auth/token/revoke/all/') - ->setTag('a') ->setWorkflow(true) - ->setIcon('fa-exclamation-triangle'); + ->setColor(PHUIButtonView::RED); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Temporary Tokens')) - ->addActionLink($terminate_button); - - $panel = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setTable($table); - - return $panel; + return $this->newBox(pht('Temporary Tokens'), $table, array($button)); } } diff --git a/src/applications/settings/panelgroup/PhabricatorSettingsAccountPanelGroup.php b/src/applications/settings/panelgroup/PhabricatorSettingsAccountPanelGroup.php index f81d5d487a..826118b881 100644 --- a/src/applications/settings/panelgroup/PhabricatorSettingsAccountPanelGroup.php +++ b/src/applications/settings/panelgroup/PhabricatorSettingsAccountPanelGroup.php @@ -6,7 +6,7 @@ final class PhabricatorSettingsAccountPanelGroup const PANELGROUPKEY = 'account'; public function getPanelGroupName() { - return pht('Account'); + return null; } protected function getPanelGroupOrder() { diff --git a/src/applications/settings/setting/PhabricatorDesktopNotificationsSetting.php b/src/applications/settings/setting/PhabricatorDesktopNotificationsSetting.php deleted file mode 100644 index f590d37325..0000000000 --- a/src/applications/settings/setting/PhabricatorDesktopNotificationsSetting.php +++ /dev/null @@ -1,12 +0,0 @@ - pht('Web Only'), + self::WEB_AND_DESKTOP => pht('Web and Desktop'), + self::DESKTOP_ONLY => pht('Desktop Only'), + self::NONE => pht('No Notifications'), + ); + } + + public static function desktopReady($option) { + switch ($option) { + case self::WEB_AND_DESKTOP: + case self::DESKTOP_ONLY: + return true; + } + return false; + } + + public static function webReady($option) { + switch ($option) { + case self::WEB_AND_DESKTOP: + case self::WEB_ONLY: + return true; + } + return false; + } + +} diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php index 05c12aec27..a4da0f7f7d 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php @@ -32,7 +32,8 @@ final class PhabricatorSlowvoteCloseController $xactions = array(); $xactions[] = id(new PhabricatorSlowvoteTransaction()) - ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_CLOSE) + ->setTransactionType( + PhabricatorSlowvoteCloseTransaction::TRANSACTIONTYPE) ->setNewValue($new_status); id(new PhabricatorSlowvoteEditor()) diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php index e9f3d48de4..44927fedea 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php @@ -68,7 +68,13 @@ final class PhabricatorSlowvoteEditController } if ($is_new) { - $responses = array_filter($responses); + // NOTE: Make sure common and useful response "0" is preserved. + foreach ($responses as $key => $response) { + if (!strlen($response)) { + unset($responses[$key]); + } + } + if (empty($responses)) { $errors[] = pht('You must offer at least one response.'); $e_response = pht('Required'); @@ -77,23 +83,32 @@ final class PhabricatorSlowvoteEditController } } - $xactions = array(); $template = id(new PhabricatorSlowvoteTransaction()); + $xactions = array(); + + if ($is_new) { + $xactions[] = id(new PhabricatorSlowvoteTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_CREATE); + } $xactions[] = id(clone $template) - ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_QUESTION) + ->setTransactionType( + PhabricatorSlowvoteQuestionTransaction::TRANSACTIONTYPE) ->setNewValue($v_question); $xactions[] = id(clone $template) - ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + PhabricatorSlowvoteDescriptionTransaction::TRANSACTIONTYPE) ->setNewValue($v_description); $xactions[] = id(clone $template) - ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_RESPONSES) + ->setTransactionType( + PhabricatorSlowvoteResponsesTransaction::TRANSACTIONTYPE) ->setNewValue($v_responses); $xactions[] = id(clone $template) - ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_SHUFFLE) + ->setTransactionType( + PhabricatorSlowvoteShuffleTransaction::TRANSACTIONTYPE) ->setNewValue($v_shuffle); $xactions[] = id(clone $template) @@ -130,13 +145,14 @@ final class PhabricatorSlowvoteEditController } return id(new AphrontRedirectResponse()) - ->setURI('/V'.$poll->getID()); + ->setURI($poll->getURI()); } else { $poll->setViewPolicy($v_view_policy); } } $form = id(new AphrontFormView()) + ->setAction($request->getrequestURI()) ->setUser($viewer) ->appendChild( id(new AphrontFormTextControl()) @@ -253,17 +269,12 @@ final class PhabricatorSlowvoteEditController $crumbs->setBorder(true); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Poll')) + ->setHeaderText($title) ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter($form_box); return $this->newPage() diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php index f788f5e94c..1ffab17791 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php @@ -35,9 +35,9 @@ final class PhabricatorSlowvotePollController )); } - $header_icon = $poll->getIsClosed() ? 'fa-ban' : 'fa-circle-o'; + $header_icon = $poll->getIsClosed() ? 'fa-ban' : 'fa-square-o'; $header_name = $poll->getIsClosed() ? pht('Closed') : pht('Open'); - $header_color = $poll->getIsClosed() ? 'dark' : 'bluegrey'; + $header_color = $poll->getIsClosed() ? 'indigo' : 'bluegrey'; $header = id(new PHUIHeaderView()) ->setHeader($poll->getQuestion()) @@ -89,7 +89,7 @@ final class PhabricatorSlowvotePollController $is_closed = $poll->getIsClosed(); $close_poll_text = $is_closed ? pht('Reopen Poll') : pht('Close Poll'); - $close_poll_icon = $is_closed ? 'fa-play-circle-o' : 'fa-ban'; + $close_poll_icon = $is_closed ? 'fa-check' : 'fa-ban'; $curtain->addAction( id(new PhabricatorActionView()) diff --git a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php index 94e31e8b92..38dbfb12d6 100644 --- a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php +++ b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php @@ -11,107 +11,22 @@ final class PhabricatorSlowvoteEditor return pht('Slowvote'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this poll.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); - - $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; - $types[] = PhabricatorSlowvoteTransaction::TYPE_QUESTION; - $types[] = PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION; - $types[] = PhabricatorSlowvoteTransaction::TYPE_RESPONSES; - $types[] = PhabricatorSlowvoteTransaction::TYPE_SHUFFLE; - $types[] = PhabricatorSlowvoteTransaction::TYPE_CLOSE; - return $types; } - protected function transactionHasEffect( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - switch ($xaction->getTransactionType()) { - case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: - if ($old === null) { - return true; - } - return ((int)$old !== (int)$new); - case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: - if ($old === null) { - return true; - } - return ((bool)$old !== (bool)$new); - } - - return parent::transactionHasEffect($object, $xaction); - } - - - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorSlowvoteTransaction::TYPE_QUESTION: - return $object->getQuestion(); - case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: - return $object->getResponseVisibility(); - case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: - return $object->getShuffle(); - case PhabricatorSlowvoteTransaction::TYPE_CLOSE: - return $object->getIsClosed(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorSlowvoteTransaction::TYPE_QUESTION: - case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION: - case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: - case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: - case PhabricatorSlowvoteTransaction::TYPE_CLOSE: - return $xaction->getNewValue(); - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorSlowvoteTransaction::TYPE_QUESTION: - $object->setQuestion($xaction->getNewValue()); - break; - case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION: - $object->setDescription($xaction->getNewValue()); - break; - case PhabricatorSlowvoteTransaction::TYPE_RESPONSES: - $object->setResponseVisibility($xaction->getNewValue()); - break; - case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE: - $object->setShuffle($xaction->getNewValue()); - break; - case PhabricatorSlowvoteTransaction::TYPE_CLOSE: - $object->setIsClosed((int)$xaction->getNewValue()); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - return; - } - - protected function shouldSendMail( + protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { return true; diff --git a/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php b/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php index fee52a908c..d1db26b8a4 100644 --- a/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php +++ b/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php @@ -55,6 +55,7 @@ final class PhabricatorSlowvoteSearchEngine id(new PhabricatorSearchCheckboxesField()) ->setKey('statuses') + ->setLabel(pht('Statuses')) ->setOptions(array( 'open' => pht('Open'), 'closed' => pht('Closed'), diff --git a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php index 78c21f5d65..3b642256d2 100644 --- a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php +++ b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php @@ -21,8 +21,8 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO protected $question; protected $description; protected $authorPHID; - protected $responseVisibility; - protected $shuffle; + protected $responseVisibility = 0; + protected $shuffle = 0; protected $method; protected $mailKey; protected $viewPolicy; @@ -54,7 +54,7 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO self::CONFIG_COLUMN_SCHEMA => array( 'question' => 'text255', 'responseVisibility' => 'uint32', - 'shuffle' => 'uint32', + 'shuffle' => 'bool', 'method' => 'uint32', 'description' => 'text', 'isClosed' => 'bool', @@ -112,6 +112,10 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO return 'V'.$this->getID(); } + public function getURI() { + return '/'.$this->getMonogram(); + } + public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); diff --git a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php index fb1b7fc2f3..1781733acf 100644 --- a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php +++ b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php @@ -1,13 +1,7 @@ getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - case self::TYPE_RESPONSES: - case self::TYPE_SHUFFLE: - case self::TYPE_CLOSE: - return ($old === null); - } - - return parent::shouldHide(); - } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_QUESTION: - if ($old === null) { - return pht( - '%s created this poll.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s changed the poll question from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for this poll.', - $this->renderHandleLink($author_phid)); - case self::TYPE_RESPONSES: - // TODO: This could be more detailed - return pht( - '%s changed who can see the responses.', - $this->renderHandleLink($author_phid)); - case self::TYPE_SHUFFLE: - if ($new) { - return pht( - '%s made poll responses appear in a random order.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s made poll responses appear in a fixed order.', - $this->renderHandleLink($author_phid)); - } - break; - case self::TYPE_CLOSE: - if ($new) { - return pht( - '%s closed this poll.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s reopened this poll.', - $this->renderHandleLink($author_phid)); - } - - break; - } - - return parent::getTitle(); - } - - public function getRemarkupBlocks() { - $blocks = parent::getRemarkupBlocks(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_DESCRIPTION: - $blocks[] = $this->getNewValue(); - break; - } - - return $blocks; - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_QUESTION: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } else { - return pht( - '%s renamed %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_DESCRIPTION: - if ($old === null) { - return pht( - '%s set the description of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } else { - return pht( - '%s edited the description of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_RESPONSES: - // TODO: This could be more detailed - return pht( - '%s changed who can see the responses of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - case self::TYPE_SHUFFLE: - if ($new) { - return pht( - '%s made %s responses appear in a random order.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } else { - return pht( - '%s made %s responses appear in a fixed order.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - case self::TYPE_CLOSE: - if ($new) { - return pht( - '%s closed %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } else { - return pht( - '%s reopened %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - } - - return parent::getTitleForFeed(); - } - - public function getIcon() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_QUESTION: - if ($old === null) { - return 'fa-plus'; - } else { - return 'fa-pencil'; - } - case self::TYPE_DESCRIPTION: - case self::TYPE_RESPONSES: - return 'fa-pencil'; - case self::TYPE_SHUFFLE: - return 'fa-refresh'; - case self::TYPE_CLOSE: - if ($new) { - return 'fa-ban'; - } else { - return 'fa-pencil'; - } - } - - return parent::getIcon(); - } - - - public function getColor() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_QUESTION: - case self::TYPE_DESCRIPTION: - case self::TYPE_RESPONSES: - case self::TYPE_SHUFFLE: - case self::TYPE_CLOSE: - return PhabricatorTransactions::COLOR_BLUE; - } - - return parent::getColor(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return true; - } - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); + public function getBaseTransactionClass() { + return 'PhabricatorSlowvoteTransactionType'; } public function getMailTags() { $tags = parent::getMailTags(); switch ($this->getTransactionType()) { - case self::TYPE_QUESTION: - case self::TYPE_DESCRIPTION: - case self::TYPE_SHUFFLE: - case self::TYPE_CLOSE: + case PhabricatorSlowvoteQuestionTransaction::TRANSACTIONTYPE: + case PhabricatorSlowvoteDescriptionTransaction::TRANSACTIONTYPE: + case PhabricatorSlowvoteShuffleTransaction::TRANSACTIONTYPE: + case PhabricatorSlowvoteCloseTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_DETAILS; break; - case self::TYPE_RESPONSES: + case PhabricatorSlowvoteResponsesTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_RESPONSES; break; default: diff --git a/src/applications/slowvote/xaction/PhabricatorSlowvoteCloseTransaction.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteCloseTransaction.php new file mode 100644 index 0000000000..ce3eaf5879 --- /dev/null +++ b/src/applications/slowvote/xaction/PhabricatorSlowvoteCloseTransaction.php @@ -0,0 +1,60 @@ +getIsClosed(); + } + + public function generateNewValue($object, $value) { + return (bool)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setIsClosed((int)$value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s closed this poll.', + $this->renderAuthor()); + } else { + return pht( + '%s reopened this poll.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s closed %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s reopened %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + + if ($new) { + return 'fa-ban'; + } else { + return 'fa-pencil'; + } + } + +} diff --git a/src/applications/slowvote/xaction/PhabricatorSlowvoteDescriptionTransaction.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteDescriptionTransaction.php new file mode 100644 index 0000000000..3a06dd4a88 --- /dev/null +++ b/src/applications/slowvote/xaction/PhabricatorSlowvoteDescriptionTransaction.php @@ -0,0 +1,66 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + '%s updated the description for this poll.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + + if ($old === null) { + return pht( + '%s set the description of %s.', + $this->renderAuthor(), + $this->renderObject()); + + } else { + return pht( + '%s edited the description of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO POLL DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/slowvote/xaction/PhabricatorSlowvoteQuestionTransaction.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteQuestionTransaction.php new file mode 100644 index 0000000000..09c5f93e66 --- /dev/null +++ b/src/applications/slowvote/xaction/PhabricatorSlowvoteQuestionTransaction.php @@ -0,0 +1,70 @@ +getQuestion(); + } + + public function applyInternalEffects($object, $value) { + $object->setQuestion($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old === null) { + return pht( + '%s created this poll.', + $this->renderAuthor()); + } else { + return pht( + '%s changed the poll question from "%s" to "%s".', + $this->renderAuthor(), + $old, + $new); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + + if ($old === null) { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + + } else { + return pht( + '%s renamed %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $old = $this->getOldValue(); + + if ($old === null) { + return 'fa-plus'; + } else { + return 'fa-pencil'; + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getQuestion(), $xactions)) { + $errors[] = $this->newRequiredError(pht('Polls must have a question.')); + } + + return $errors; + } + +} diff --git a/src/applications/slowvote/xaction/PhabricatorSlowvoteResponsesTransaction.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteResponsesTransaction.php new file mode 100644 index 0000000000..93035cbd6a --- /dev/null +++ b/src/applications/slowvote/xaction/PhabricatorSlowvoteResponsesTransaction.php @@ -0,0 +1,31 @@ +getResponseVisibility(); + } + + public function applyInternalEffects($object, $value) { + $object->setResponseVisibility($value); + } + + public function getTitle() { + // TODO: This could be more detailed + return pht( + '%s changed who can see the responses.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + // TODO: This could be more detailed + return pht( + '%s changed who can see the responses of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + +} diff --git a/src/applications/slowvote/xaction/PhabricatorSlowvoteShuffleTransaction.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteShuffleTransaction.php new file mode 100644 index 0000000000..645e86b393 --- /dev/null +++ b/src/applications/slowvote/xaction/PhabricatorSlowvoteShuffleTransaction.php @@ -0,0 +1,55 @@ +getShuffle(); + } + + public function generateNewValue($object, $value) { + return (bool)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setShuffle((int)$value); + } + + public function getTitle() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s made poll responses appear in a random order.', + $this->renderAuthor()); + } else { + return pht( + '%s made poll responses appear in a fixed order.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + + if ($new) { + return pht( + '%s made %s responses appear in a random order.', + $this->renderAuthor(), + $this->renderObject()); + + } else { + return pht( + '%s made %s responses appear in a fixed order.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + return 'fa-refresh'; + } + +} diff --git a/src/applications/slowvote/xaction/PhabricatorSlowvoteTransactionType.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteTransactionType.php new file mode 100644 index 0000000000..f380aa101c --- /dev/null +++ b/src/applications/slowvote/xaction/PhabricatorSlowvoteTransactionType.php @@ -0,0 +1,4 @@ +getMonogram(); if ($request->isFormPost()) { - $type_archive = PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE; + $type_archive = + PhabricatorSpacesNamespaceArchiveTransaction::TRANSACTIONTYPE; $xactions = array(); $xactions[] = id(new PhabricatorSpacesNamespaceTransaction()) diff --git a/src/applications/spaces/controller/PhabricatorSpacesEditController.php b/src/applications/spaces/controller/PhabricatorSpacesEditController.php index 3f7dae9e82..cf3b6578b6 100644 --- a/src/applications/spaces/controller/PhabricatorSpacesEditController.php +++ b/src/applications/spaces/controller/PhabricatorSpacesEditController.php @@ -67,9 +67,12 @@ final class PhabricatorSpacesEditController $v_view = $request->getStr('viewPolicy'); $v_edit = $request->getStr('editPolicy'); - $type_name = PhabricatorSpacesNamespaceTransaction::TYPE_NAME; - $type_desc = PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION; - $type_default = PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT; + $type_name = + PhabricatorSpacesNamespaceNameTransaction::TRANSACTIONTYPE; + $type_desc = + PhabricatorSpacesNamespaceDescriptionTransaction::TRANSACTIONTYPE; + $type_default = + PhabricatorSpacesNamespaceDefaultTransaction::TRANSACTIONTYPE; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -162,8 +165,8 @@ final class PhabricatorSpacesEditController ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Space')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setValidationException($validation_exception) ->appendChild($form); @@ -176,12 +179,7 @@ final class PhabricatorSpacesEditController $crumbs->addTextCrumb($title); $crumbs->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($header_text) - ->setHeaderIcon('fa-pencil'); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter(array( $box, )); diff --git a/src/applications/spaces/controller/PhabricatorSpacesViewController.php b/src/applications/spaces/controller/PhabricatorSpacesViewController.php index 495a0c8dee..5fa1c01143 100644 --- a/src/applications/spaces/controller/PhabricatorSpacesViewController.php +++ b/src/applications/spaces/controller/PhabricatorSpacesViewController.php @@ -39,7 +39,7 @@ final class PhabricatorSpacesViewController ->setHeaderIcon('fa-th-large'); if ($space->getIsArchived()) { - $header->setStatus('fa-ban', 'red', pht('Archived')); + $header->setStatus('fa-ban', 'indigo', pht('Archived')); } else { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } diff --git a/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php b/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php index caa45f28c2..e4dc9c5b69 100644 --- a/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php +++ b/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php @@ -14,153 +14,18 @@ final class PhabricatorSpacesNamespaceEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorSpacesNamespaceTransaction::TYPE_NAME; - $types[] = PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION; - $types[] = PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT; - $types[] = PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE; - $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: - $name = $object->getNamespaceName(); - if (!strlen($name)) { - return null; - } - return $name; - case PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION: - if ($this->getIsNewObject()) { - return null; - } - return $object->getDescription(); - case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE: - return $object->getIsArchived(); - case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: - return $object->getIsDefaultNamespace() ? 1 : null; - case PhabricatorTransactions::TYPE_VIEW_POLICY: - return $object->getViewPolicy(); - case PhabricatorTransactions::TYPE_EDIT_POLICY: - return $object->getEditPolicy(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); + public function getCreateObjectTitle($author, $object) { + return pht('%s created this space.', $author); } - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: - case PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION: - case PhabricatorTransactions::TYPE_VIEW_POLICY: - case PhabricatorTransactions::TYPE_EDIT_POLICY: - return $xaction->getNewValue(); - case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE: - return $xaction->getNewValue() ? 1 : 0; - case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: - return $xaction->getNewValue() ? 1 : null; - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $new = $xaction->getNewValue(); - - switch ($xaction->getTransactionType()) { - case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: - $object->setNamespaceName($new); - return; - case PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION: - $object->setDescription($new); - return; - case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: - $object->setIsDefaultNamespace($new ? 1 : null); - return; - case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE: - $object->setIsArchived($new ? 1 : 0); - return; - case PhabricatorTransactions::TYPE_VIEW_POLICY: - $object->setViewPolicy($new); - return; - case PhabricatorTransactions::TYPE_EDIT_POLICY: - $object->setEditPolicy($new); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: - case PhabricatorSpacesNamespaceTransaction::TYPE_DESCRIPTION: - case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: - case PhabricatorSpacesNamespaceTransaction::TYPE_ARCHIVE: - case PhabricatorTransactions::TYPE_VIEW_POLICY: - case PhabricatorTransactions::TYPE_EDIT_POLICY: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getNamespaceName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Spaces must have a name.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: - if (!$this->getIsNewObject()) { - foreach ($xactions as $xaction) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Only the first space created can be the default space, and '. - 'it must remain the default space evermore.'), - $xaction); - } - } - break; - - } - - return $errors; + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created space %s.', $author, $object); } } diff --git a/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php b/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php index 4c438537f1..0f50a870f6 100644 --- a/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php +++ b/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php @@ -1,12 +1,7 @@ getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($old === null); - } - - return parent::shouldHide(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return true; - } - - return parent::hasChangeDetails(); - } - - public function getRemarkupBlocks() { - $blocks = parent::getRemarkupBlocks(); - - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - $blocks[] = $this->getNewValue(); - break; - } - - return $blocks; - } - - public function getTitle() { - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $author_phid = $this->getAuthorPHID(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this space.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s renamed this space from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for this space.', - $this->renderHandleLink($author_phid)); - case self::TYPE_DEFAULT: - return pht( - '%s made this the default space.', - $this->renderHandleLink($author_phid)); - case self::TYPE_ARCHIVE: - if ($new) { - return pht( - '%s archived this space.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s activated this space.', - $this->renderHandleLink($author_phid)); - } - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'PhabricatorSpacesNamespaceTransactionType'; } } diff --git a/src/applications/spaces/xaction/PhabricatorSpacesNamespaceArchiveTransaction.php b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceArchiveTransaction.php new file mode 100644 index 0000000000..a1dedbd85f --- /dev/null +++ b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceArchiveTransaction.php @@ -0,0 +1,60 @@ +getIsArchived(); + } + + public function applyInternalEffects($object, $value) { + $object->setIsArchived((int)$value); + } + + public function getTitle() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s archived this space.', + $this->renderAuthor()); + } else { + return pht( + '%s activated this space.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s archived space %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s activated space %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function getIcon() { + $new = $this->getNewValue(); + if ($new) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + + public function getColor() { + $new = $this->getNewValue(); + if ($new) { + return 'indigo'; + } + } + +} diff --git a/src/applications/spaces/xaction/PhabricatorSpacesNamespaceDefaultTransaction.php b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceDefaultTransaction.php new file mode 100644 index 0000000000..bc09b06d91 --- /dev/null +++ b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceDefaultTransaction.php @@ -0,0 +1,44 @@ +getIsDefaultNamespace(); + } + + public function applyInternalEffects($object, $value) { + $object->setIsDefaultNamespace($value); + } + + public function getTitle() { + return pht( + '%s made this the default space.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s made space %s the default space.', + $this->renderAuthor(), + $this->renderObject()); + + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + $errors[] = $this->newInvalidError( + pht('Only the first space created can be the default space, and '. + 'it must remain the default space evermore.')); + } + } + + return $errors; + } + +} diff --git a/src/applications/spaces/xaction/PhabricatorSpacesNamespaceDescriptionTransaction.php b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceDescriptionTransaction.php new file mode 100644 index 0000000000..22b0faa5d1 --- /dev/null +++ b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceDescriptionTransaction.php @@ -0,0 +1,57 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + '%s updated the space description.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the space description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO SPACE DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + +} diff --git a/src/applications/spaces/xaction/PhabricatorSpacesNamespaceNameTransaction.php b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceNameTransaction.php new file mode 100644 index 0000000000..d7fcbc2c7a --- /dev/null +++ b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceNameTransaction.php @@ -0,0 +1,62 @@ +getNamespaceName(); + } + + public function applyInternalEffects($object, $value) { + $object->setNamespaceName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + if (!strlen($old)) { + return pht( + '%s created this space.', + $this->renderAuthor()); + } else { + return pht( + '%s renamed this space from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + return pht( + '%s renamed space %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getNamespaceName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Spaces must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('namespaceName'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The name can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/spaces/xaction/PhabricatorSpacesNamespaceTransactionType.php b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceTransactionType.php new file mode 100644 index 0000000000..a6f206a16c --- /dev/null +++ b/src/applications/spaces/xaction/PhabricatorSpacesNamespaceTransactionType.php @@ -0,0 +1,4 @@ +setKey('subscriberPHIDs') + ->setKey(self::FIELDKEY) ->setLabel(pht('Subscribers')) ->setEditTypeKey('subscribers') ->setAliases(array('subscriber', 'subscribers')) diff --git a/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsFulltextEngineExtension.php b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsFulltextEngineExtension.php index 3c25618ebe..e7876136a1 100644 --- a/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsFulltextEngineExtension.php +++ b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsFulltextEngineExtension.php @@ -9,11 +9,11 @@ final class PhabricatorSubscriptionsFulltextEngineExtension return pht('Subscribers'); } - public function shouldIndexFulltextObject($object) { + public function shouldEnrichFulltextObject($object) { return ($object instanceof PhabricatorSubscribableInterface); } - public function indexFulltextObject( + public function enrichFulltextObject( $object, PhabricatorSearchAbstractDocument $document) { diff --git a/src/applications/system/codex/PhabricatorDestructibleCodex.php b/src/applications/system/codex/PhabricatorDestructibleCodex.php new file mode 100644 index 0000000000..1c37907c34 --- /dev/null +++ b/src/applications/system/codex/PhabricatorDestructibleCodex.php @@ -0,0 +1,66 @@ +viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + final public function setObject( + PhabricatorDestructibleCodexInterface $object) { + $this->object = $object; + return $this; + } + + final public function getObject() { + return $this->object; + } + + final public static function newFromObject( + PhabricatorDestructibleCodexInterface $object, + PhabricatorUser $viewer) { + + if (!($object instanceof PhabricatorDestructibleInterface)) { + throw new Exception( + pht( + 'Object (of class "%s") implements interface "%s", but must also '. + 'implement interface "%s".', + get_class($object), + 'PhabricatorDestructibleCodexInterface', + 'PhabricatorDestructibleInterface')); + } + + $codex = $object->newDestructibleCodex(); + if (!($codex instanceof PhabricatorDestructibleCodex)) { + throw new Exception( + pht( + 'Object (of class "%s") implements interface "%s", but defines '. + 'method "%s" incorrectly: this method must return an object of '. + 'class "%s".', + get_class($object), + 'PhabricatorDestructibleCodexInterface', + 'newDestructibleCodex()', + __CLASS__)); + } + + $codex + ->setObject($object) + ->setViewer($viewer); + + return $codex; + } + +} diff --git a/src/applications/system/engine/PhabricatorDestructionEngine.php b/src/applications/system/engine/PhabricatorDestructionEngine.php index f8e5be2ccc..336df57756 100644 --- a/src/applications/system/engine/PhabricatorDestructionEngine.php +++ b/src/applications/system/engine/PhabricatorDestructionEngine.php @@ -3,6 +3,17 @@ final class PhabricatorDestructionEngine extends Phobject { private $rootLogID; + private $collectNotes; + private $notes = array(); + + public function setCollectNotes($collect_notes) { + $this->collectNotes = $collect_notes; + return $this; + } + + public function getNotes() { + return $this->notes; + } public function getViewer() { return PhabricatorUser::getOmnipotentUser(); @@ -36,6 +47,18 @@ final class PhabricatorDestructionEngine extends Phobject { $this->rootLogID = $log->getID(); } + if ($this->collectNotes) { + if ($object instanceof PhabricatorDestructibleCodexInterface) { + $codex = PhabricatorDestructibleCodex::newFromObject( + $object, + $this->getViewer()); + + foreach ($codex->getDestructionNotes() as $note) { + $this->notes[] = $note; + } + } + } + $object->destroyObjectPermanently($this); if ($object_phid) { diff --git a/src/applications/system/interface/PhabricatorDestructibleCodexInterface.php b/src/applications/system/interface/PhabricatorDestructibleCodexInterface.php new file mode 100644 index 0000000000..3725990ee6 --- /dev/null +++ b/src/applications/system/interface/PhabricatorDestructibleCodexInterface.php @@ -0,0 +1,18 @@ +>DestructibleCodex(); + } + +*/ diff --git a/src/applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php b/src/applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php index 4e9d745540..bd6b4e361a 100644 --- a/src/applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php +++ b/src/applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php @@ -145,6 +145,7 @@ EOBANNER; $console->writeOut("%s\n", pht('Destroying objects...')); + $notes = array(); foreach ($named_objects as $object_name => $object) { $console->writeOut( pht( @@ -152,8 +153,14 @@ EOBANNER; get_class($object), $object_name)); - id(new PhabricatorDestructionEngine()) - ->destroyObject($object); + $engine = id(new PhabricatorDestructionEngine()) + ->setCollectNotes(true); + + $engine->destroyObject($object); + + foreach ($engine->getNotes() as $note) { + $notes[] = $note; + } } $console->writeOut( @@ -162,6 +169,12 @@ EOBANNER; 'Permanently destroyed %s object(s).', phutil_count($named_objects))); + if ($notes) { + id(new PhutilConsoleList()) + ->addItems($notes) + ->draw(); + } + return 0; } diff --git a/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php b/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php new file mode 100644 index 0000000000..43b94874bf --- /dev/null +++ b/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php @@ -0,0 +1,216 @@ + 'phid|string', + ) + $this->getPagerParamTypes(); + } + + protected function defineReturnType() { + return 'list'; + } + + protected function defineErrorTypes() { + return array(); + } + + protected function execute(ConduitAPIRequest $request) { + $viewer = $request->getUser(); + $pager = $this->newPager($request); + + $object_name = $request->getValue('objectIdentifier', null); + if (!strlen($object_name)) { + throw new Exception( + pht( + 'When calling "transaction.search", you must provide an object to '. + 'retrieve transactions for.')); + } + + $object = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames(array($object_name)) + ->executeOne(); + if (!$object) { + throw new Exception( + pht( + 'No object "%s" exists.', + $object_name)); + } + + if (!($object instanceof PhabricatorApplicationTransactionInterface)) { + throw new Exception( + pht( + 'Object "%s" does not implement "%s", so transactions can not '. + 'be loaded for it.')); + } + + $xaction_query = PhabricatorApplicationTransactionQuery::newQueryForObject( + $object); + + $xactions = $xaction_query + ->withObjectPHIDs(array($object->getPHID())) + ->setViewer($viewer) + ->executeWithCursorPager($pager); + + if ($xactions) { + $template = head($xactions)->getApplicationTransactionCommentObject(); + + $query = new PhabricatorApplicationTransactionTemplatedCommentQuery(); + + $comment_map = $query + ->setViewer($viewer) + ->setTemplate($template) + ->withTransactionPHIDs(mpull($xactions, 'getPHID')) + ->execute(); + + $comment_map = msort($comment_map, 'getCommentVersion'); + $comment_map = array_reverse($comment_map); + $comment_map = mgroup($comment_map, 'getTransactionPHID'); + } else { + $comment_map = array(); + } + + $modular_classes = array(); + $modular_objects = array(); + $modular_xactions = array(); + foreach ($xactions as $xaction) { + if (!$xaction instanceof PhabricatorModularTransaction) { + continue; + } + + // TODO: Hack things so certain transactions which don't have a modular + // type yet can use a pseudotype until they modularize. Some day, we'll + // modularize everything and remove this. + switch ($xaction->getTransactionType()) { + case DifferentialTransaction::TYPE_INLINE: + $modular_template = new DifferentialRevisionInlineTransaction(); + break; + default: + $modular_template = $xaction->getModularType(); + break; + } + + $modular_class = get_class($modular_template); + if (!isset($modular_objects[$modular_class])) { + try { + $modular_object = newv($modular_class, array()); + $modular_objects[$modular_class] = $modular_object; + } catch (Exception $ex) { + continue; + } + } + + $modular_classes[$xaction->getPHID()] = $modular_class; + $modular_xactions[$modular_class][] = $xaction; + } + + $modular_data_map = array(); + foreach ($modular_objects as $class => $modular_type) { + $modular_data_map[$class] = $modular_type + ->setViewer($viewer) + ->loadTransactionTypeConduitData($modular_xactions[$class]); + } + + $data = array(); + foreach ($xactions as $xaction) { + $comments = idx($comment_map, $xaction->getPHID()); + + $comment_data = array(); + if ($comments) { + $removed = head($comments)->getIsDeleted(); + + foreach ($comments as $comment) { + if ($removed) { + // If the most recent version of the comment has been removed, + // don't show the history. This is for consistency with the web + // UI, which also prevents users from retrieving the content of + // removed comments. + $content = array( + 'raw' => '', + ); + } else { + $content = array( + 'raw' => (string)$comment->getContent(), + ); + } + + $comment_data[] = array( + 'id' => (int)$comment->getID(), + 'phid' => (string)$comment->getPHID(), + 'version' => (int)$comment->getCommentVersion(), + 'authorPHID' => (string)$comment->getAuthorPHID(), + 'dateCreated' => (int)$comment->getDateCreated(), + 'dateModified' => (int)$comment->getDateModified(), + 'removed' => (bool)$comment->getIsDeleted(), + 'content' => $content, + ); + } + } + + $fields = array(); + $type = null; + + if (isset($modular_classes[$xaction->getPHID()])) { + $modular_class = $modular_classes[$xaction->getPHID()]; + $modular_object = $modular_objects[$modular_class]; + $modular_data = $modular_data_map[$modular_class]; + + $type = $modular_object->getTransactionTypeForConduit($xaction); + $fields = $modular_object->getFieldValuesForConduit( + $xaction, + $modular_data); + } + + if (!$fields) { + $fields = (object)$fields; + } + + // If we haven't found a modular type, fallback for some simple core + // types. Ideally, we'll modularize everything some day. + if ($type === null) { + switch ($xaction->getTransactionType()) { + case PhabricatorTransactions::TYPE_COMMENT: + $type = 'comment'; + break; + } + } + + $data[] = array( + 'id' => (int)$xaction->getID(), + 'phid' => (string)$xaction->getPHID(), + 'type' => $type, + 'authorPHID' => (string)$xaction->getAuthorPHID(), + 'objectPHID' => (string)$xaction->getObjectPHID(), + 'dateCreated' => (int)$xaction->getDateCreated(), + 'dateModified' => (int)$xaction->getDateModified(), + 'comments' => $comment_data, + 'fields' => $fields, + ); + } + + $results = array( + 'data' => $data, + ); + + return $this->addPagerResults($results, $pager); + } +} diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 1b891cddee..e1d6812778 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -29,7 +29,6 @@ abstract class PhabricatorEditEngine private $page; private $pages; private $navigation; - private $hideHeader; final public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -127,15 +126,6 @@ abstract class PhabricatorEditEngine return $this->navigation; } - public function setHideHeader($hide_header) { - $this->hideHeader = $hide_header; - return $this; - } - - public function getHideHeader() { - return $this->hideHeader; - } - /* -( Managing Fields )---------------------------------------------------- */ @@ -322,6 +312,15 @@ abstract class PhabricatorEditEngine } + /** + * @task text + */ + protected function getPageHeader($object) { + return null; + } + + + /** * Return a human-readable header describing what this engine is used to do, * like "Configure Maniphest Task Forms". @@ -1151,10 +1150,8 @@ abstract class PhabricatorEditEngine if ($this->getIsCreate()) { $header_text = $this->getFormHeaderText($object); - $header_icon = 'fa-plus-square'; } else { $header_text = $this->getObjectEditTitleText($object); - $header_icon = 'fa-pencil'; } $show_preview = !$request->isAjax(); @@ -1181,6 +1178,9 @@ abstract class PhabricatorEditEngine $form = $this->buildEditForm($object, $fields); + $crumbs = $this->buildCrumbs($object, $final = true); + $crumbs->setBorder(true); + if ($request->isAjax()) { return $this->getController() ->newDialog() @@ -1192,27 +1192,18 @@ abstract class PhabricatorEditEngine ->addSubmitButton($submit_button); } - $crumbs = $this->buildCrumbs($object, $final = true); - - if ($this->getHideHeader()) { - $header = null; - $crumbs->setBorder(false); - } else { - $header = id(new PHUIHeaderView()) - ->setHeader($header_text) - ->setHeaderIcon($header_icon); - $crumbs->setBorder(true); - } + $box_header = id(new PHUIHeaderView()) + ->setHeader($header_text); if ($action_button) { - $header->addActionLink($action_button); + $box_header->addActionLink($action_button); } $box = id(new PHUIObjectBoxView()) ->setUser($viewer) - ->setHeaderText($this->getObjectName()) + ->setHeader($box_header) ->setValidationException($validation_exception) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->appendChild($form); // This is fairly questionable, but in use by Settings. @@ -1227,23 +1218,26 @@ abstract class PhabricatorEditEngine $view = new PHUITwoColumnView(); - if ($header) { - $view->setHeader($header); + $page_header = $this->getPageHeader($object); + if ($page_header) { + $view->setHeader($page_header); } + $page = $controller->newPage() + ->setTitle($header_text) + ->setCrumbs($crumbs) + ->appendChild($view); + $navigation = $this->getNavigation(); if ($navigation) { - $view - ->setNavigation($navigation) - ->setMainColumn($content); + $view->setFixed(true); + $view->setNavigation($navigation); + $view->setMainColumn($content); } else { $view->setFooter($content); } - return $controller->newPage() - ->setTitle($header_text) - ->setCrumbs($crumbs) - ->appendChild($view); + return $page; } protected function newEditResponse( diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineSubtype.php b/src/applications/transactions/editengine/PhabricatorEditEngineSubtype.php index ff14ae239b..df367955af 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngineSubtype.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngineSubtype.php @@ -8,6 +8,9 @@ final class PhabricatorEditEngineSubtype private $key; private $name; + private $icon; + private $tagText; + private $color; public function setKey($key) { $this->key = $key; @@ -27,8 +30,48 @@ final class PhabricatorEditEngineSubtype return $this->name; } + public function setIcon($icon) { + $this->icon = $icon; + return $this; + } + public function getIcon() { - return 'fa-drivers-license-o'; + return $this->icon; + } + + public function setTagText($text) { + $this->tagText = $text; + return $this; + } + + public function getTagText() { + return $this->tagText; + } + + public function setColor($color) { + $this->color = $color; + return $this; + } + + public function getColor() { + return $this->color; + } + + public function hasTagView() { + return (bool)strlen($this->getTagText()); + } + + public function newTagView() { + $view = id(new PHUITagView()) + ->setType(PHUITagView::TYPE_OUTLINE) + ->setName($this->getTagText()); + + $color = $this->getColor(); + if ($color) { + $view->setColor($color); + } + + return $view; } public static function validateSubtypeKey($subtype) { @@ -72,6 +115,9 @@ final class PhabricatorEditEngineSubtype array( 'key' => 'string', 'name' => 'string', + 'tag' => 'optional string', + 'color' => 'optional string', + 'icon' => 'optional string', )); $key = $value['key']; @@ -113,9 +159,27 @@ final class PhabricatorEditEngineSubtype $key = $entry['key']; $name = $entry['name']; - $map[$key] = id(new self()) + $tag_text = idx($entry, 'tag'); + if ($tag_text === null) { + if ($key != self::SUBTYPE_DEFAULT) { + $tag_text = phutil_utf8_strtoupper($name); + } + } + + $color = idx($entry, 'color', 'blue'); + $icon = idx($entry, 'icon', 'fa-drivers-license-o'); + + $subtype = id(new self()) ->setKey($key) - ->setName($name); + ->setName($name) + ->setTagText($tag_text) + ->setIcon($icon); + + if ($color) { + $subtype->setColor($color); + } + + $map[$key] = $subtype; } return $map; diff --git a/src/applications/transactions/editfield/PhabricatorSelectEditField.php b/src/applications/transactions/editfield/PhabricatorSelectEditField.php index d8e99d610c..fa565dbe61 100644 --- a/src/applications/transactions/editfield/PhabricatorSelectEditField.php +++ b/src/applications/transactions/editfield/PhabricatorSelectEditField.php @@ -4,6 +4,7 @@ final class PhabricatorSelectEditField extends PhabricatorEditField { private $options; + private $optionAliases = array(); public function setOptions(array $options) { $this->options = $options; @@ -17,6 +18,24 @@ final class PhabricatorSelectEditField return $this->options; } + public function setOptionAliases(array $option_aliases) { + $this->optionAliases = $option_aliases; + return $this; + } + + public function getOptionAliases() { + return $this->optionAliases; + } + + protected function getDefaultValueFromConfiguration($value) { + return $this->getCanonicalValue($value); + } + + protected function getValueForControl() { + $value = parent::getValueForControl(); + return $this->getCanonicalValue($value); + } + protected function newControl() { return id(new AphrontFormSelectControl()) ->setOptions($this->getOptions()); @@ -35,4 +54,16 @@ final class PhabricatorSelectEditField return new ConduitStringParameterType(); } + private function getCanonicalValue($value) { + $options = $this->getOptions(); + if (!isset($options[$value])) { + $aliases = $this->getOptionAliases(); + if (isset($aliases[$value])) { + $value = $aliases[$value]; + } + } + + return $value; + } + } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index b183fc3e07..3524d78d90 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -298,6 +298,18 @@ abstract class PhabricatorApplicationTransactionEditor } } + if ($template) { + try { + $comment = $template->getApplicationTransactionCommentObject(); + } catch (PhutilMethodNotImplementedException $ex) { + $comment = null; + } + + if ($comment) { + $types[] = PhabricatorTransactions::TYPE_COMMENT; + } + } + return $types; } @@ -322,6 +334,8 @@ abstract class PhabricatorApplicationTransactionEditor $xtype = $this->getModularTransactionType($type); if ($xtype) { + $xtype = clone $xtype; + $xtype->setStorage($xaction); return $xtype->generateOldValue($object); } @@ -402,6 +416,8 @@ abstract class PhabricatorApplicationTransactionEditor $xtype = $this->getModularTransactionType($type); if ($xtype) { + $xtype = clone $xtype; + $xtype->setStorage($xaction); return $xtype->generateNewValue($object, $xaction->getNewValue()); } @@ -470,8 +486,6 @@ abstract class PhabricatorApplicationTransactionEditor switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_CREATE: return true; - case PhabricatorTransactions::TYPE_COMMENT: - return $xaction->hasComment(); case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getCustomFieldForTransaction($object, $xaction); return $field->getApplicationTransactionHasEffect($xaction); @@ -518,6 +532,10 @@ abstract class PhabricatorApplicationTransactionEditor $xaction->getNewValue()); } + if ($xaction->hasComment()) { + return true; + } + return ($xaction->getOldValue() !== $xaction->getNewValue()); } @@ -541,6 +559,8 @@ abstract class PhabricatorApplicationTransactionEditor $xtype = $this->getModularTransactionType($type); if ($xtype) { + $xtype = clone $xtype; + $xtype->setStorage($xaction); return $xtype->applyInternalEffects($object, $xaction->getNewValue()); } @@ -574,6 +594,8 @@ abstract class PhabricatorApplicationTransactionEditor $xtype = $this->getModularTransactionType($type); if ($xtype) { + $xtype = clone $xtype; + $xtype->setStorage($xaction); return $xtype->applyExternalEffects($object, $xaction->getNewValue()); } @@ -1725,7 +1747,7 @@ abstract class PhabricatorApplicationTransactionEditor return array_values($result); } - protected function mergePHIDOrEdgeTransactions( + public function mergePHIDOrEdgeTransactions( PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { @@ -2151,7 +2173,7 @@ abstract class PhabricatorApplicationTransactionEditor return array_mergev($errors); } - private function validatePolicyTransaction( + public function validatePolicyTransaction( PhabricatorLiskDAO $object, array $xactions, $transaction_type, @@ -2763,7 +2785,11 @@ abstract class PhabricatorApplicationTransactionEditor } if (!$has_support) { - throw new Exception(pht('Capability not supported.')); + throw new Exception( + pht('The object being edited does not implement any standard '. + 'interfaces (like PhabricatorSubscribableInterface) which allow '. + 'CCs to be generated automatically. Override the "getMailCC()" '. + 'method and generate CCs explicitly.')); } return array_mergev($phids); diff --git a/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php b/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php index b95fc0167a..701ad34ca7 100644 --- a/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php +++ b/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php @@ -9,11 +9,11 @@ final class PhabricatorTransactionsFulltextEngineExtension return pht('Comments'); } - public function shouldIndexFulltextObject($object) { + public function shouldEnrichFulltextObject($object) { return ($object instanceof PhabricatorApplicationTransactionInterface); } - public function indexFulltextObject( + public function enrichFulltextObject( $object, PhabricatorSearchAbstractDocument $document) { diff --git a/src/applications/transactions/storage/PhabricatorModularTransaction.php b/src/applications/transactions/storage/PhabricatorModularTransaction.php index 396478c98b..9f3c24b946 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransaction.php +++ b/src/applications/transactions/storage/PhabricatorModularTransaction.php @@ -160,7 +160,7 @@ abstract class PhabricatorModularTransaction return parent::attachViewer($viewer); } - final public function hasChangeDetails() { + /* final */ public function hasChangeDetails() { if ($this->getTransactionImplementation()->hasChangeDetailView()) { return true; } @@ -168,7 +168,7 @@ abstract class PhabricatorModularTransaction return parent::hasChangeDetails(); } - final public function renderChangeDetails(PhabricatorUser $viewer) { + /* final */ public function renderChangeDetails(PhabricatorUser $viewer) { $impl = $this->getTransactionImplementation(); $impl->setViewer($viewer); $view = $impl->newChangeDetailView(); diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index 9d8510cdc9..f2b59c8a2b 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -134,6 +134,10 @@ abstract class PhabricatorModularTransactionType return $this->editor; } + final protected function hasEditor() { + return (bool)$this->editor; + } + final protected function getAuthorPHID() { return $this->getStorage()->getAuthorPHID(); } @@ -208,6 +212,19 @@ abstract class PhabricatorModularTransactionType $value); } + final protected function renderValueList(array $values) { + $result = array(); + foreach ($values as $value) { + $result[] = $this->renderValue($value); + } + + if ($this->isTextMode()) { + return implode(', ', $result); + } + + return phutil_implode_html(', ', $result); + } + final protected function renderOldValue() { return $this->renderValue($this->getOldValue()); } @@ -315,4 +332,20 @@ abstract class PhabricatorModularTransactionType return $editor->getPHIDList($old, $new); } + public function getMetadataValue($key, $default = null) { + return $this->getStorage()->getMetadataValue($key, $default); + } + + public function loadTransactionTypeConduitData(array $xactions) { + return null; + } + + public function getTransactionTypeForConduit($xaction) { + return null; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array(); + } + } diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php index 232420d4c2..a5dcfcb4ae 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php @@ -199,7 +199,7 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView { 'class' => 'login-to-comment button', 'href' => $uri, ), - pht('Login to Comment'))); + pht('Log In to Comment'))); } $data = array(); diff --git a/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php b/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php index 6be978a6a9..025b9511b4 100644 --- a/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php +++ b/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php @@ -17,9 +17,14 @@ final class PhabricatorUIExampleRenderController extends PhabricatorController { $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI('view/'))); - foreach ($classes as $class => $obj) { - $name = $obj->getName(); - $nav->addFilter($class, $name); + $groups = mgroup($classes, 'getCategory'); + ksort($groups); + foreach ($groups as $group => $group_classes) { + $nav->addLabel($group); + foreach ($group_classes as $class => $obj) { + $name = $obj->getName(); + $nav->addFilter($class, $name); + } } $selected = $nav->selectFilter($id, head_key($classes)); diff --git a/src/applications/uiexample/examples/JavelinReactorUIExample.php b/src/applications/uiexample/examples/JavelinReactorUIExample.php deleted file mode 100644 index 9e47545072..0000000000 --- a/src/applications/uiexample/examples/JavelinReactorUIExample.php +++ /dev/null @@ -1,95 +0,0 @@ - true), - ), - array( - pht('Reactive focus detector generates a boolean dynamic value'), - 'ReactorFocusExample', - 'phabricator-uiexample-reactor-focus', - array(), - ), - array( - pht('Reactive input box, with normal and calmed output'), - 'ReactorInputExample', - 'phabricator-uiexample-reactor-input', - array('init' => 'Initial value'), - ), - array( - pht('Reactive mouseover detector generates a boolean dynamic value'), - 'ReactorMouseoverExample', - 'phabricator-uiexample-reactor-mouseover', - array(), - ), - array( - pht('Reactive radio buttons generate a string dynamic value'), - 'ReactorRadioExample', - 'phabricator-uiexample-reactor-radio', - array(), - ), - array( - pht('Reactive select box generates a string dynamic value'), - 'ReactorSelectExample', - 'phabricator-uiexample-reactor-select', - array(), - ), - array( - pht( - '%s makes the class of an element a string dynamic value', - 'sendclass'), - 'ReactorSendClassExample', - 'phabricator-uiexample-reactor-sendclass', - array(), - ), - array( - pht( - '%s makes some properties of an object into dynamic values', - 'sendproperties'), - 'ReactorSendPropertiesExample', - 'phabricator-uiexample-reactor-sendproperties', - array(), - ), - ); - - foreach ($examples as $example) { - list($desc, $name, $resource, $params) = $example; - $template = new AphrontJavelinView(); - $template - ->setName($name) - ->setParameters($params) - ->setCelerityResource($resource); - $rows[] = array($desc, $template->render()); - } - - $table = new AphrontTableView($rows); - - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText(pht('Example')); - $panel->appendChild($table); - - return $panel; - } -} diff --git a/src/applications/uiexample/examples/JavelinUIExample.php b/src/applications/uiexample/examples/JavelinUIExample.php deleted file mode 100644 index c85a2d4cde..0000000000 --- a/src/applications/uiexample/examples/JavelinUIExample.php +++ /dev/null @@ -1,66 +0,0 @@ -getRequest(); - $user = $request->getUser(); - - // toggle-class - - $container_id = celerity_generate_unique_node_id(); - $button_red_id = celerity_generate_unique_node_id(); - $button_blue_id = celerity_generate_unique_node_id(); - - $button_red = javelin_tag( - 'a', - array( - 'class' => 'button', - 'sigil' => 'jx-toggle-class', - 'href' => '#', - 'id' => $button_red_id, - 'meta' => array( - 'map' => array( - $container_id => 'jxui-red-border', - $button_red_id => 'jxui-active', - ), - ), - ), - pht('Toggle Red Border')); - - $button_blue = javelin_tag( - 'a', - array( - 'class' => 'button jxui-active', - 'sigil' => 'jx-toggle-class', - 'href' => '#', - 'id' => $button_blue_id, - 'meta' => array( - 'state' => true, - 'map' => array( - $container_id => 'jxui-blue-background', - $button_blue_id => 'jxui-active', - ), - ), - ), - pht('Toggle Blue Background')); - - $div = phutil_tag( - 'div', - array( - 'id' => $container_id, - 'class' => 'jxui-example-container jxui-blue-background', - ), - array($button_red, $button_blue)); - - return array($div); - } -} diff --git a/src/applications/uiexample/examples/JavelinViewExampleServerView.php b/src/applications/uiexample/examples/JavelinViewExampleServerView.php deleted file mode 100644 index 2d59917a7c..0000000000 --- a/src/applications/uiexample/examples/JavelinViewExampleServerView.php +++ /dev/null @@ -1,14 +0,0 @@ - 'server-view', - ), - $this->renderChildren()); - } - -} diff --git a/src/applications/uiexample/examples/JavelinViewUIExample.php b/src/applications/uiexample/examples/JavelinViewUIExample.php deleted file mode 100644 index f7df1749b4..0000000000 --- a/src/applications/uiexample/examples/JavelinViewUIExample.php +++ /dev/null @@ -1,45 +0,0 @@ -getRequest(); - - $init = $request->getStr('init'); - - $parent_server_template = new JavelinViewExampleServerView(); - - $parent_client_template = new AphrontJavelinView(); - $parent_client_template - ->setName('JavelinViewExample') - ->setCelerityResource('phabricator-uiexample-javelin-view'); - - $child_server_template = new JavelinViewExampleServerView(); - - $child_client_template = new AphrontJavelinView(); - $child_client_template - ->setName('JavelinViewExample') - ->setCelerityResource('phabricator-uiexample-javelin-view'); - - $parent_server_template->appendChild($parent_client_template); - $parent_client_template->appendChild($child_server_template); - $child_server_template->appendChild($child_client_template); - $child_client_template->appendChild(pht('Hey, it worked.')); - - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText(pht('Example')); - $panel->appendChild( - phutil_tag_div('ml', $parent_server_template)); - - return $panel; - } -} diff --git a/src/applications/uiexample/examples/MacroEmojiExample.php b/src/applications/uiexample/examples/MacroEmojiExample.php index fddf20bc32..036a5a8932 100644 --- a/src/applications/uiexample/examples/MacroEmojiExample.php +++ b/src/applications/uiexample/examples/MacroEmojiExample.php @@ -3,11 +3,15 @@ final class MacroEmojiExample extends PhabricatorUIExample { public function getName() { - return pht('Emoji Support'); + return pht('Emoji'); } public function getDescription() { - return pht('Shiny happy people holding hands'); + return pht('Shiny happy people holding hands.'); + } + + public function getCategory() { + return pht('Catalogs'); } public function renderExample() { diff --git a/src/applications/uiexample/examples/PHUIActionPanelExample.php b/src/applications/uiexample/examples/PHUIActionPanelExample.php index 9294dcbea6..e71f2ce846 100644 --- a/src/applications/uiexample/examples/PHUIActionPanelExample.php +++ b/src/applications/uiexample/examples/PHUIActionPanelExample.php @@ -11,10 +11,15 @@ final class PHUIActionPanelExample extends PhabricatorUIExample { } public function renderExample() { + $viewer = $this->getRequest()->getUser(); $view = id(new AphrontMultiColumnView()) ->setFluidLayout(true); + $credit = PhabricatorFile::loadBuiltin( + $viewer, 'projects/v3/creditcard.png'); + $image = $credit->getBestURI(); + /* Action Panels */ $panel1 = id(new PHUIActionPanelView()) ->setIcon('fa-book') @@ -53,7 +58,7 @@ final class PHUIActionPanelExample extends PhabricatorUIExample { /* Action Panels */ $panel1 = id(new PHUIActionPanelView()) - ->setIcon('fa-credit-card') + ->setImage($image) ->setHeader(pht('Account Balance')) ->setHref('#') ->setSubHeader(pht('You were last billed $2,245.12 on Dec 12, 2014.')) diff --git a/src/applications/uiexample/examples/PHUIBadgeExample.php b/src/applications/uiexample/examples/PHUIBadgeExample.php index 14eb3469d0..703595b76c 100644 --- a/src/applications/uiexample/examples/PHUIBadgeExample.php +++ b/src/applications/uiexample/examples/PHUIBadgeExample.php @@ -10,6 +10,10 @@ final class PHUIBadgeExample extends PhabricatorUIExample { return pht('Celebrate the moments of your life.'); } + public function getCategory() { + return pht('Single Use'); + } + public function renderExample() { $badges1 = array(); diff --git a/src/applications/uiexample/examples/PHUIBigInfoExample.php b/src/applications/uiexample/examples/PHUIBigInfoExample.php new file mode 100644 index 0000000000..519cdba88e --- /dev/null +++ b/src/applications/uiexample/examples/PHUIBigInfoExample.php @@ -0,0 +1,48 @@ +getRequest(); + $viewer = $request->getUser(); + + $image = PhabricatorFile::loadBuiltin($viewer, + 'projects/v3/rocket.png'); + + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Launch Away')) + ->setColor(PHUIButtonView::GREEN) + ->setHref('#'); + + $views = array(); + $views[] = id(new PHUIBigInfoView()) + ->setTitle(pht('Simply Slim')) + ->setDescription(pht('A simple description')) + ->addAction($button); + + $views[] = id(new PHUIBigInfoView()) + ->setTitle(pht('Basicly Basic')) + ->setIcon('fa-rocket') + ->setDescription(pht('A more basic description')) + ->addAction($button); + + $views[] = id(new PHUIBigInfoView()) + ->setTitle(pht('A Modern Example')) + ->setImage($image->getBestURI()) + ->setDescription(pht('A modern description with lots of frills.')) + ->addAction($button); + + + return phutil_tag_div('ml', $views); + } +} diff --git a/src/applications/uiexample/examples/PHUIBoxExample.php b/src/applications/uiexample/examples/PHUIBoxExample.php index 7a2674da94..f77ac3afd4 100644 --- a/src/applications/uiexample/examples/PHUIBoxExample.php +++ b/src/applications/uiexample/examples/PHUIBoxExample.php @@ -62,27 +62,16 @@ final class PHUIBoxExample extends PhabricatorUIExample { ); $button = id(new PHUIButtonView()) - ->setTag('a') - ->setColor(PHUIButtonView::SIMPLE) - ->setIcon('fa-heart') - ->setText(pht('Such Wow')) - ->addClass(PHUI::MARGIN_SMALL_RIGHT); - - $badge1 = id(new PHUIBadgeMiniView()) - ->setIcon('fa-bug') - ->setHeader(pht('Bugmeister')); - - $badge2 = id(new PHUIBadgeMiniView()) + ->setTag('a') + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) ->setIcon('fa-heart') - ->setHeader(pht('Funder')) - ->setQuality(PhabricatorBadgesQuality::UNCOMMON); + ->setText(pht('Such Wow')) + ->addClass(PHUI::MARGIN_SMALL_RIGHT); $header = id(new PHUIHeaderView()) ->setHeader(pht('Fancy Box')) ->addActionLink($button) - ->setSubheader(pht('Much Features')) - ->addBadge($badge1) - ->addBadge($badge2); + ->setSubheader(pht('Much Features')); $obj4 = id(new PHUIObjectBoxView()) ->setHeader($header) @@ -116,7 +105,9 @@ final class PHUIBoxExample extends PhabricatorUIExample { return phutil_tag( 'div', - array(), + array( + 'class' => 'ml', + ), array( $head1, $wrap1, diff --git a/src/applications/uiexample/examples/PHUIButtonBarExample.php b/src/applications/uiexample/examples/PHUIButtonBarExample.php index 1501770dcf..d7992aa830 100644 --- a/src/applications/uiexample/examples/PHUIButtonBarExample.php +++ b/src/applications/uiexample/examples/PHUIButtonBarExample.php @@ -36,7 +36,7 @@ final class PHUIButtonBarExample extends PhabricatorUIExample { foreach ($icons as $text => $icon) { $button = id(new PHUIButtonView()) ->setTag('a') - ->setColor(PHUIButtonView::SIMPLE) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) ->setTitle($text) ->setText($text); @@ -47,7 +47,7 @@ final class PHUIButtonBarExample extends PhabricatorUIExample { foreach ($icons as $text => $icon) { $button = id(new PHUIButtonView()) ->setTag('a') - ->setColor(PHUIButtonView::SIMPLE) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) ->setTitle($text) ->setTooltip($text) ->setIcon($icon); diff --git a/src/applications/uiexample/examples/PHUIButtonExample.php b/src/applications/uiexample/examples/PHUIButtonExample.php index ae699eb7a3..89f8e15fe8 100644 --- a/src/applications/uiexample/examples/PHUIButtonExample.php +++ b/src/applications/uiexample/examples/PHUIButtonExample.php @@ -16,63 +16,12 @@ final class PHUIButtonExample extends PhabricatorUIExample { $request = $this->getRequest(); $user = $request->getUser(); - $colors = array('', 'green', 'grey', 'disabled'); - $sizes = array('', 'small'); - $tags = array('a', 'button'); - - // phutil_tag - - $column = array(); - foreach ($tags as $tag) { - foreach ($colors as $color) { - foreach ($sizes as $key => $size) { - $class = implode(' ', array($color, $size)); - - if ($tag == 'a') { - $class .= ' button'; - } - - $column[$key][] = phutil_tag( - $tag, - array( - 'class' => $class, - ), - phutil_utf8_ucwords($size.' '.$color.' '.$tag)); - - $column[$key][] = hsprintf('

'); - } - } - } - - $column3 = array(); - foreach ($colors as $color) { - $caret = phutil_tag('span', array('class' => 'caret'), ''); - $column3[] = phutil_tag( - 'a', - array( - 'class' => $color.' button dropdown', - ), - array( - phutil_utf8_ucwords($color.' Dropdown'), - $caret, - )); - $column3[] = hsprintf('

'); - } - - $layout1 = id(new AphrontMultiColumnView()) - ->addColumn($column[0]) - ->addColumn($column[1]) - ->addColumn($column3) - ->setFluidLayout(true) - ->setGutter(AphrontMultiColumnView::GUTTER_MEDIUM); - // PHUIButtonView - $colors = array( null, PHUIButtonView::GREEN, + PHUIButtonView::RED, PHUIButtonView::GREY, - PHUIButtonView::DISABLED, ); $sizes = array(null, PHUIButtonView::SMALL); $column = array(); @@ -106,18 +55,70 @@ final class PHUIButtonExample extends PhabricatorUIExample { $column = array(); $icons = array( - 'Comment' => 'fa-comment', - 'Give Token' => 'fa-trophy', - 'Reverse Time' => 'fa-clock-o', - 'Implode Earth' => 'fa-exclamation-triangle red', + array( + 'text' => pht('Comment'), + 'icon' => 'fa-comment', + 'dropdown' => true, + ), + array( + 'text' => pht('Give Token'), + 'icon' => 'fa-trophy', + 'dropdown' => true, + ), + array( + 'text' => pht('Reverse Time'), + 'icon' => 'fa-clock-o', + ), + array( + 'text' => pht('Implode Earth'), + 'icon' => 'fa-exclamation-triangle', + ), + array( + 'icon' => 'fa-rocket', + 'dropdown' => true, + ), + array( + 'icon' => 'fa-clipboard', + 'dropdown' => true, + ), + array( + 'icon' => 'fa-upload', + 'disabled' => true, + ), + array( + 'icon' => 'fa-street-view', + 'selected' => true, + ), + array( + 'text' => pht('Copy "Quack" to Clipboard'), + 'icon' => 'fa-clipboard', + 'copy' => pht('Quack'), + ), ); - foreach ($icons as $text => $icon) { - $column[] = id(new PHUIButtonView()) + foreach ($icons as $text => $spec) { + $button = id(new PHUIButtonView()) ->setTag('a') ->setColor(PHUIButtonView::GREY) - ->setIcon($icon) - ->setText($text) - ->addClass(PHUI::MARGIN_SMALL_RIGHT); + ->setIcon(idx($spec, 'icon')) + ->setText(idx($spec, 'text')) + ->setSelected(idx($spec, 'selected')) + ->setDisabled(idx($spec, 'disabled')) + ->addClass(PHUI::MARGIN_SMALL_RIGHT) + ->setDropdown(idx($spec, 'dropdown')); + + $copy = idx($spec, 'copy'); + if ($copy !== null) { + Javelin::initBehavior('phabricator-clipboard-copy'); + + $button->addClass('clipboard-copy'); + $button->addSigil('clipboard-copy'); + $button->setMetadata( + array( + 'text' => $copy, + )); + } + + $column[] = $button; } $layout3 = id(new AphrontMultiColumnView()) @@ -129,21 +130,22 @@ final class PHUIButtonExample extends PhabricatorUIExample { 'Subscribe' => 'fa-check-circle bluegrey', 'Edit' => 'fa-pencil bluegrey', ); - $colors = array( - PHUIButtonView::SIMPLE, - PHUIButtonView::SIMPLE_YELLOW, - PHUIButtonView::SIMPLE_GREY, - PHUIButtonView::SIMPLE_BLUE, + $designs = array( + PHUIButtonView::BUTTONTYPE_SIMPLE, ); + $colors = array('', 'red', 'green', 'yellow'); $column = array(); - foreach ($colors as $color) { - foreach ($icons as $text => $icon) { - $column[] = id(new PHUIButtonView()) - ->setTag('a') - ->setColor($color) - ->setIcon($icon) - ->setText($text) - ->addClass(PHUI::MARGIN_SMALL_RIGHT); + foreach ($designs as $design) { + foreach ($colors as $color) { + foreach ($icons as $text => $icon) { + $column[] = id(new PHUIButtonView()) + ->setTag('a') + ->setButtonType($design) + ->setColor($color) + ->setIcon($icon) + ->setText($text) + ->addClass(PHUI::MARGIN_SMALL_RIGHT); + } } } @@ -166,7 +168,7 @@ final class PHUIButtonExample extends PhabricatorUIExample { ->setSize(PHUIButtonView::BIG) ->setColor(PHUIButtonView::GREY) ->setIcon($image) - ->setText(pht('Login or Register')) + ->setText(pht('Log In or Register')) ->setSubtext($icon) ->addClass(PHUI::MARGIN_MEDIUM_RIGHT); } @@ -179,24 +181,21 @@ final class PHUIButtonExample extends PhabricatorUIExample { // Set it and forget it - $head1 = id(new PHUIHeaderView()) - ->setHeader('phutil_tag'); - $head2 = id(new PHUIHeaderView()) - ->setHeader('PHUIButtonView'); + ->setHeader('PHUIButtonView') + ->addClass('ml'); $head3 = id(new PHUIHeaderView()) - ->setHeader(pht('Icon Buttons')); + ->setHeader(pht('Icon Buttons')) + ->addClass('ml'); $head4 = id(new PHUIHeaderView()) - ->setHeader(pht('Simple Buttons')); + ->setHeader(pht('Simple Buttons')) + ->addClass('ml'); $head5 = id(new PHUIHeaderView()) - ->setHeader(pht('Big Icon Buttons')); - - $wrap1 = id(new PHUIBoxView()) - ->appendChild($layout1) - ->addMargin(PHUI::MARGIN_LARGE); + ->setHeader(pht('Big Icon Buttons')) + ->addClass('ml'); $wrap2 = id(new PHUIBoxView()) ->appendChild($layout2) @@ -215,8 +214,6 @@ final class PHUIButtonExample extends PhabricatorUIExample { ->addMargin(PHUI::MARGIN_LARGE); return array( - $head1, - $wrap1, $head2, $wrap2, $head3, diff --git a/src/applications/uiexample/examples/PHUIColorPalletteExample.php b/src/applications/uiexample/examples/PHUIColorPalletteExample.php index 0e3193d24d..ea8ac538be 100644 --- a/src/applications/uiexample/examples/PHUIColorPalletteExample.php +++ b/src/applications/uiexample/examples/PHUIColorPalletteExample.php @@ -10,6 +10,10 @@ final class PHUIColorPalletteExample extends PhabricatorUIExample { return pht('A Standard Palette of Colors for use.'); } + public function getCategory() { + return pht('Catalogs'); + } + public function renderExample() { $colors = array( @@ -97,7 +101,7 @@ final class PHUIColorPalletteExample extends PhabricatorUIExample { 'a', array( 'href' => 'http://color.hailpixel.com/#'.implode(',', $url), - 'class' => 'button grey mlb', + 'class' => 'button button-grey mlb', ), pht('Color Palette')); diff --git a/src/applications/uiexample/examples/PHUIFeedStoryExample.php b/src/applications/uiexample/examples/PHUIFeedStoryExample.php index 375c6e42fd..0df757cada 100644 --- a/src/applications/uiexample/examples/PHUIFeedStoryExample.php +++ b/src/applications/uiexample/examples/PHUIFeedStoryExample.php @@ -8,7 +8,11 @@ final class PHUIFeedStoryExample extends PhabricatorUIExample { public function getDescription() { return pht( - 'An outlandish exaggeration of intricate tales from around the realm'); + 'An outlandish exaggeration of intricate tales from around the realm.'); + } + + public function getCategory() { + return pht('Single Use'); } public function renderExample() { diff --git a/src/applications/uiexample/examples/PHUIHovercardUIExample.php b/src/applications/uiexample/examples/PHUIHovercardUIExample.php index 8673441d09..b88fdc6639 100644 --- a/src/applications/uiexample/examples/PHUIHovercardUIExample.php +++ b/src/applications/uiexample/examples/PHUIHovercardUIExample.php @@ -12,6 +12,10 @@ final class PHUIHovercardUIExample extends PhabricatorUIExample { phutil_tag('tt', array(), 'PHUIHovercardView')); } + public function getCategory() { + return pht('Single Use'); + } + public function renderExample() { $request = $this->getRequest(); $user = $request->getUser(); @@ -51,14 +55,6 @@ final class PHUIHovercardUIExample extends PhabricatorUIExample { ->addTag($tag)); $elements[] = $panel; - $badge1 = id(new PHUIBadgeMiniView()) - ->setIcon('fa-book') - ->setHeader(pht('Documenter')); - - $badge2 = id(new PHUIBadgeMiniView()) - ->setIcon('fa-star') - ->setHeader(pht('Contributor')); - $user_handle = $this->createBasicDummyHandle( 'gwashington', PhabricatorPeopleUserPHIDType::TYPECONST, @@ -71,8 +67,6 @@ final class PHUIHovercardUIExample extends PhabricatorUIExample { ->addField(pht('Status'), pht('Available')) ->addField(pht('Member since'), '30. February 1750') ->addAction(pht('Send a Message'), '/dev/null') - ->addBadge($badge1) - ->addBadge($badge2) ->setUser($user)); $elements[] = $panel; diff --git a/src/applications/uiexample/examples/PHUIIconExample.php b/src/applications/uiexample/examples/PHUIIconExample.php index 8e01d6e16a..81778b0fd4 100644 --- a/src/applications/uiexample/examples/PHUIIconExample.php +++ b/src/applications/uiexample/examples/PHUIIconExample.php @@ -10,6 +10,10 @@ final class PHUIIconExample extends PhabricatorUIExample { return pht('Easily render icons or images with links and sprites.'); } + public function getCategory() { + return pht('Catalogs'); + } + private function listTransforms() { return array( 'ph-rotate-90', @@ -130,6 +134,23 @@ final class PHUIIconExample extends PhabricatorUIExample { ->addClass('mmr'); } + $circles = array('fa-gear', 'fa-recycle'); + $colors = array('green', 'pink', 'red', 'sky', 'violet'); + foreach ($circles as $circle) { + $states = PHUIIconCircleView::getStateMap(); + foreach ($states as $state => $name) { + $i = array_rand($colors); + $circleview[] = + id(new PHUIIconCircleView()) + ->setIcon($circle) + ->setSize(PHUIIconCircleView::SMALL) + ->setState($state) + ->setColor($colors[$i]) + ->setHref('#') + ->addClass('mmr'); + } + } + $squares = array('fa-briefcase', 'fa-code', 'fa-globe', 'fa-home'); $squareview = array(); foreach ($squares as $icon) { @@ -138,7 +159,8 @@ final class PHUIIconExample extends PhabricatorUIExample { ->setIcon($icon) ->setBackground('bg-blue') ->setHref('#') - ->addClass('mmr'); + ->addClass('mmr') + ->setTooltip($icon); } $layout_cicons = id(new PHUIBoxView()) diff --git a/src/applications/uiexample/examples/PHUIInfoPanelExample.php b/src/applications/uiexample/examples/PHUIInfoPanelExample.php deleted file mode 100644 index 74ed18f37c..0000000000 --- a/src/applications/uiexample/examples/PHUIInfoPanelExample.php +++ /dev/null @@ -1,138 +0,0 @@ -setHeader(pht('Conpherence')); - - $header2 = id(new PHUIHeaderView()) - ->setHeader(pht('Diffusion')); - - $header3 = id(new PHUIHeaderView()) - ->setHeader(pht('Backend Ops Projects')); - - $header4 = id(new PHUIHeaderView()) - ->setHeader(pht('Revamp Liberty')) - ->setSubHeader(pht('For great justice')) - ->setImage( - celerity_get_resource_uri('/rsrc/image/people/washington.png')); - - $header5 = id(new PHUIHeaderView()) - ->setHeader(pht('Phacility Redesign')) - ->setSubHeader(pht('Move them pixels')) - ->setImage( - celerity_get_resource_uri('/rsrc/image/people/harding.png')); - - $header6 = id(new PHUIHeaderView()) - ->setHeader(pht('Python Phlux')) - ->setSubHeader(pht('No. Sleep. Till Brooklyn.')) - ->setImage( - celerity_get_resource_uri('/rsrc/image/people/taft.png')); - - $column1 = id(new PHUIInfoPanelView()) - ->setHeader($header1) - ->setColumns(3) - ->addInfoBlock(3, pht('Needs Triage')) - ->addInfoBlock(5, pht('Unbreak Now')) - ->addInfoBlock(0, pht('High')) - ->addInfoBlock(0, pht('Normal')) - ->addInfoBlock(12, pht('Low')) - ->addInfoBlock(123, pht('Wishlist')); - - $column2 = id(new PHUIInfoPanelView()) - ->setHeader($header2) - ->setColumns(3) - ->addInfoBlock(3, pht('Needs Triage')) - ->addInfoBlock(5, pht('Unbreak Now')) - ->addInfoBlock(0, pht('High')) - ->addInfoBlock(0, pht('Normal')) - ->addInfoBlock(12, pht('Low')) - ->addInfoBlock(123, pht('Wishlist')); - - $column3 = id(new PHUIInfoPanelView()) - ->setHeader($header3) - ->setColumns(3) - ->addInfoBlock(3, pht('Needs Triage')) - ->addInfoBlock(5, pht('Unbreak Now')) - ->addInfoBlock(0, pht('High')) - ->addInfoBlock(0, pht('Normal')) - ->addInfoBlock(12, pht('Low')) - ->addInfoBlock(123, pht('Wishlist')); - - $column4 = id(new PHUIInfoPanelView()) - ->setHeader($header4) - ->setColumns(3) - ->setProgress(90) - ->addInfoBlock(3, pht('Needs Triage')) - ->addInfoBlock(5, pht('Unbreak Now')) - ->addInfoBlock(0, pht('High')) - ->addInfoBlock(0, pht('Normal')) - ->addInfoBlock(0, pht('Wishlist')); - - $column5 = id(new PHUIInfoPanelView()) - ->setHeader($header5) - ->setColumns(2) - ->setProgress(25) - ->addInfoBlock(3, pht('Needs Triage')) - ->addInfoBlock(5, pht('Unbreak Now')) - ->addInfoBlock(0, pht('High')) - ->addInfoBlock(0, pht('Normal')); - - $column6 = id(new PHUIInfoPanelView()) - ->setHeader($header6) - ->setColumns(2) - ->setProgress(50) - ->addInfoBlock(3, pht('Needs Triage')) - ->addInfoBlock(5, pht('Unbreak Now')) - ->addInfoBlock(0, pht('High')) - ->addInfoBlock(0, pht('Normal')); - - $layout1 = id(new AphrontMultiColumnView()) - ->addColumn($column1) - ->addColumn($column2) - ->addColumn($column3) - ->setFluidLayout(true); - - $layout2 = id(new AphrontMultiColumnView()) - ->addColumn($column4) - ->addColumn($column5) - ->addColumn($column6) - ->setFluidLayout(true); - - - $head1 = id(new PHUIHeaderView()) - ->setHeader(pht('Flagged')); - - $head2 = id(new PHUIHeaderView()) - ->setHeader(pht('Sprints')); - - - $wrap1 = id(new PHUIBoxView()) - ->appendChild($layout1) - ->addMargin(PHUI::MARGIN_LARGE_BOTTOM); - - $wrap2 = id(new PHUIBoxView()) - ->appendChild($layout2) - ->addMargin(PHUI::MARGIN_LARGE_BOTTOM); - - - return phutil_tag( - 'div', - array(), - array( - $head1, - $wrap1, - $head2, - $wrap2, - )); - } -} diff --git a/src/applications/uiexample/examples/PHUILeftRightExample.php b/src/applications/uiexample/examples/PHUILeftRightExample.php new file mode 100644 index 0000000000..46d96ddc38 --- /dev/null +++ b/src/applications/uiexample/examples/PHUILeftRightExample.php @@ -0,0 +1,72 @@ +getRequest(); + $user = $request->getUser(); + + $text = pht('This is a sample of some text.'); + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Actions')) + ->setIcon('fa-bars'); + + $content1 = id(new PHUILeftRightView()) + ->setLeft($text) + ->setRight($button) + ->setVerticalAlign(PHUILeftRightView::ALIGN_TOP); + + $content2 = id(new PHUILeftRightView()) + ->setLeft($text) + ->setRight($button) + ->setVerticalAlign(PHUILeftRightView::ALIGN_MIDDLE); + + $content3 = id(new PHUILeftRightView()) + ->setLeft($text) + ->setRight($button) + ->setVerticalAlign(PHUILeftRightView::ALIGN_BOTTOM); + + + $head2 = id(new PHUIHeaderView()) + ->setHeader('Align Top') + ->addClass('ml'); + + $head3 = id(new PHUIHeaderView()) + ->setHeader(pht('Align Middle')) + ->addClass('ml'); + + $head4 = id(new PHUIHeaderView()) + ->setHeader(pht('Align Bottom')) + ->addClass('ml'); + + $wrap2 = id(new PHUIBoxView()) + ->appendChild($content1) + ->addMargin(PHUI::MARGIN_LARGE); + + $wrap3 = id(new PHUIBoxView()) + ->appendChild($content2) + ->addMargin(PHUI::MARGIN_LARGE); + + $wrap4 = id(new PHUIBoxView()) + ->appendChild($content3) + ->addMargin(PHUI::MARGIN_LARGE); + + return array( + $head2, + $wrap2, + $head3, + $wrap3, + $head4, + $wrap4, + ); + } +} diff --git a/src/applications/uiexample/examples/PHUIObjectItemListExample.php b/src/applications/uiexample/examples/PHUIObjectItemListExample.php index 4a75ceede1..6bc521dca2 100644 --- a/src/applications/uiexample/examples/PHUIObjectItemListExample.php +++ b/src/applications/uiexample/examples/PHUIObjectItemListExample.php @@ -202,7 +202,7 @@ final class PHUIObjectItemListExample extends PhabricatorUIExample { $list->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Ace of Hearts')) - ->setSubHead( + ->setDescription( pht('This is a powerful card in the game "Hearts".')) ->setHref('#') ->addAttribute(pht('Suit: Hearts')) @@ -330,6 +330,8 @@ final class PHUIObjectItemListExample extends PhabricatorUIExample { $list->addItem( id(new PHUIObjectItemView()) ->setImageURI($default_project->getViewURI()) + ->setImageHref('#') + ->setHref('$$$') ->setHeader(pht('Default Project Profile Image')) ->setGrippable(true) ->addAttribute(pht('This is the default project profile image.'))); diff --git a/src/applications/uiexample/examples/PHUITagExample.php b/src/applications/uiexample/examples/PHUITagExample.php index 155fe4e9da..027be2f9d8 100644 --- a/src/applications/uiexample/examples/PHUITagExample.php +++ b/src/applications/uiexample/examples/PHUITagExample.php @@ -162,15 +162,15 @@ final class PHUITagExample extends PhabricatorUIExample { $tags = array(); foreach ($shades as $shade) { $tags[] = id(new PHUITagView()) - ->setType(PHUITagView::TYPE_OBJECT) - ->setShade($shade) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor($shade) ->setIcon('fa-tags') ->setName(ucwords($shade)) ->setHref('#'); $tags[] = hsprintf(' '); $tags[] = id(new PHUITagView()) - ->setType(PHUITagView::TYPE_OBJECT) - ->setShade($shade) + ->setType(PHUITagView::TYPE_SHADE) + ->setColor($shade) ->setSlimShady(true) ->setIcon('fa-tags') ->setName(ucwords($shade)) @@ -182,6 +182,26 @@ final class PHUITagExample extends PhabricatorUIExample { ->appendChild($tags) ->addPadding(PHUI::PADDING_LARGE); + $outlines = PHUITagView::getOutlines(); + $tags = array(); + foreach ($outlines as $outline) { + $tags[] = id(new PHUITagView()) + ->setType(PHUITagView::TYPE_OUTLINE) + ->setColor($outline) + ->setName($outline); + $tags[] = hsprintf(' '); + $tags[] = id(new PHUITagView()) + ->setType(PHUITagView::TYPE_OUTLINE) + ->setColor($outline) + ->setSlimShady(true) + ->setName($outline); + $tags[] = hsprintf('

'); + } + + $content5 = id(new PHUIBoxView()) + ->appendChild($tags) + ->addPadding(PHUI::PADDING_LARGE); + $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Inline')) ->appendChild($intro); @@ -202,6 +222,10 @@ final class PHUITagExample extends PhabricatorUIExample { ->setHeaderText(pht('Shades')) ->appendChild($content4); - return array($box, $box1, $box2, $box3, $box4); + $box5 = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Outlines')) + ->appendChild($content5); + + return array($box, $box1, $box2, $box3, $box4, $box5); } } diff --git a/src/applications/uiexample/examples/PHUITimelineExample.php b/src/applications/uiexample/examples/PHUITimelineExample.php index 9bedffd9eb..42c680ea7b 100644 --- a/src/applications/uiexample/examples/PHUITimelineExample.php +++ b/src/applications/uiexample/examples/PHUITimelineExample.php @@ -80,13 +80,35 @@ final class PHUITimelineExample extends PhabricatorUIExample { ->setTitle(pht('Minor Red Event')) ->setColor(PhabricatorTransactions::COLOR_RED); + // Pinboard!! + $pin1 = id(new PHUIPinboardItemView()) + ->setUser($user) + ->setHeader('user0.png') + ->setImageURI(celerity_get_resource_uri('/rsrc/image/people/user0.png')) + ->setURI(celerity_get_resource_uri('/rsrc/image/people/user0.png')) + ->setImageSize(280, 210); + + $pin2 = id(new PHUIPinboardItemView()) + ->setUser($user) + ->setHeader('user1.png') + ->setImageURI(celerity_get_resource_uri('/rsrc/image/people/user1.png')) + ->setURI(celerity_get_resource_uri('/rsrc/image/people/user1.png')) + ->setImageSize(280, 210); + + $pin3 = id(new PHUIPinboardItemView()) + ->setUser($user) + ->setHeader('user2.png') + ->setImageURI(celerity_get_resource_uri('/rsrc/image/people/user2.png')) + ->setURI(celerity_get_resource_uri('/rsrc/image/people/user1.png')) + ->setImageSize(280, 210); + $events[] = id(new PHUITimelineEventView()) ->setUserHandle($handle) - ->setIcon('fa-check') - ->setTitle(pht('Historically Important Action')) - ->setColor(PhabricatorTransactions::COLOR_BLACK) - ->setReallyMajorEvent(true); - + ->setIcon('fa-camera-retro') + ->setTitle(pht('Pinboard Image Event')) + ->addPinboardItem($pin1) + ->addPinboardItem($pin2) + ->addPinboardItem($pin3); $events[] = id(new PHUITimelineEventView()) ->setUserHandle($handle) @@ -102,12 +124,6 @@ final class PHUITimelineExample extends PhabricatorUIExample { ->appendChild(str_repeat(pht('Long Text Body').' ', 64)) ->setColor(PhabricatorTransactions::COLOR_ORANGE); - $events[] = id(new PHUITimelineEventView()) - ->setUserHandle($handle) - ->setTitle(str_repeat('LongTextEventNoSpaces', 1024)) - ->appendChild(str_repeat('LongTextNoSpaces', 1024)) - ->setColor(PhabricatorTransactions::COLOR_RED); - $colors = array( PhabricatorTransactions::COLOR_RED, PhabricatorTransactions::COLOR_ORANGE, diff --git a/src/applications/uiexample/examples/PHUIXComponentsExample.php b/src/applications/uiexample/examples/PHUIXComponentsExample.php new file mode 100644 index 0000000000..0b6d90f28f --- /dev/null +++ b/src/applications/uiexample/examples/PHUIXComponentsExample.php @@ -0,0 +1,139 @@ + 'fa-rocket', + ), + array( + 'icon' => 'fa-cloud', + 'color' => 'indigo', + ), + ); + + foreach ($icons as $spec) { + $icon = new PHUIIconView(); + + $icon->setIcon(idx($spec, 'icon'), idx($spec, 'color')); + + $client_id = celerity_generate_unique_node_id(); + + $server_view = $icon; + $client_view = javelin_tag( + 'div', + array( + 'id' => $client_id, + )); + + Javelin::initBehavior( + 'phuix-example', + array( + 'type' => 'icon', + 'id' => $client_id, + 'spec' => $spec, + )); + + $content[] = id(new AphrontMultiColumnView()) + ->addColumn($server_view) + ->addColumn($client_view); + } + + + $buttons = array( + array( + 'text' => pht('Submit'), + ), + array( + 'text' => pht('Activate'), + 'icon' => 'fa-rocket', + ), + array( + 'type' => PHUIButtonView::BUTTONTYPE_SIMPLE, + 'text' => pht('3 / 5 Comments'), + 'icon' => 'fa-comment', + ), + array( + 'color' => PHUIButtonView::GREEN, + 'text' => pht('Environmental!'), + ), + array( + 'icon' => 'fa-cog', + ), + array( + 'icon' => 'fa-cog', + 'type' => PHUIButtonView::BUTTONTYPE_SIMPLE, + ), + array( + 'text' => array('2 + 2', ' ', '=', ' ', '4'), + ), + array( + 'color' => PHUIButtonView::GREY, + 'text' => pht('Cancel'), + ), + array( + 'text' => array(''), + ), + ); + + foreach ($buttons as $spec) { + $button = new PHUIButtonView(); + + if (idx($spec, 'text') !== null) { + $button->setText($spec['text']); + } + + if (idx($spec, 'icon') !== null) { + $button->setIcon($spec['icon']); + } + + if (idx($spec, 'type') !== null) { + $button->setButtonType($spec['type']); + } + + if (idx($spec, 'color') !== null) { + $button->setColor($spec['color']); + } + + $client_id = celerity_generate_unique_node_id(); + + $server_view = $button; + $client_view = javelin_tag( + 'div', + array( + 'id' => $client_id, + )); + + Javelin::initBehavior( + 'phuix-example', + array( + 'type' => 'button', + 'id' => $client_id, + 'spec' => $spec, + )); + + $content[] = id(new AphrontMultiColumnView()) + ->addColumn($server_view) + ->addColumn($client_view); + } + + return id(new PHUIBoxView()) + ->appendChild($content) + ->addMargin(PHUI::MARGIN_LARGE); + } +} diff --git a/src/applications/uiexample/examples/PhabricatorBarePageUIExample.php b/src/applications/uiexample/examples/PhabricatorBarePageUIExample.php deleted file mode 100644 index 3f377edadd..0000000000 --- a/src/applications/uiexample/examples/PhabricatorBarePageUIExample.php +++ /dev/null @@ -1,25 +0,0 @@ -appendChild( - phutil_tag( - 'h1', - array(), - $this->getDescription())); - - $response = new AphrontWebpageResponse(); - $response->setContent($view->render()); - return $response; - } -} diff --git a/src/applications/uiexample/examples/PhabricatorBusyUIExample.php b/src/applications/uiexample/examples/PhabricatorBusyUIExample.php deleted file mode 100644 index ab23e5cf79..0000000000 --- a/src/applications/uiexample/examples/PhabricatorBusyUIExample.php +++ /dev/null @@ -1,17 +0,0 @@ -getRequest(); $viewer = $request->getUser(); diff --git a/src/applications/uiexample/examples/PhabricatorGestureUIExample.php b/src/applications/uiexample/examples/PhabricatorGestureUIExample.php index 1adb7dee16..2adfb795b2 100644 --- a/src/applications/uiexample/examples/PhabricatorGestureUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorGestureUIExample.php @@ -14,6 +14,10 @@ final class PhabricatorGestureUIExample extends PhabricatorUIExample { phutil_tag('tt', array(), 'touchable')); } + public function getCategory() { + return pht('Technical'); + } + public function renderExample() { $id = celerity_generate_unique_node_id(); diff --git a/src/applications/uiexample/examples/PhabricatorListFilterUIExample.php b/src/applications/uiexample/examples/PhabricatorListFilterUIExample.php deleted file mode 100644 index 6c934a3e47..0000000000 --- a/src/applications/uiexample/examples/PhabricatorListFilterUIExample.php +++ /dev/null @@ -1,35 +0,0 @@ -setUser($this->getRequest()->getUser()); - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Query'))) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Search'))); - - $filter->appendChild($form); - - - return $filter; - } -} diff --git a/src/applications/uiexample/examples/PhabricatorNotificationUIExample.php b/src/applications/uiexample/examples/PhabricatorNotificationUIExample.php index 1b825693fa..4f39df6065 100644 --- a/src/applications/uiexample/examples/PhabricatorNotificationUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorNotificationUIExample.php @@ -12,6 +12,10 @@ final class PhabricatorNotificationUIExample extends PhabricatorUIExample { phutil_tag('tt', array(), 'JX.Notification')); } + public function getCategory() { + return pht('Technical'); + } + public function renderExample() { require_celerity_resource('phabricator-notification-css'); Javelin::initBehavior('phabricator-notification-example'); @@ -20,7 +24,7 @@ final class PhabricatorNotificationUIExample extends PhabricatorUIExample { 'a', array( 'sigil' => 'notification-example', - 'class' => 'button green', + 'class' => 'button button-green', ), pht('Show Notification')); diff --git a/src/applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php b/src/applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php new file mode 100644 index 0000000000..2cc89b56d5 --- /dev/null +++ b/src/applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php @@ -0,0 +1,71 @@ +getRequest()->getUser(); + + $root = dirname(phutil_get_library_root('phabricator')); + $root = $root.'/resources/builtin/projects/v3/'; + + Javelin::initBehavior('phabricator-tooltips', array()); + + $map = array(); + $builtin_map = id(new FileFinder($root)) + ->withType('f') + ->withFollowSymlinks(true) + ->find(); + + $images = array(); + foreach ($builtin_map as $image) { + $file = PhabricatorFile::loadBuiltin($viewer, 'projects/v3/'.$image); + $images[$file->getPHID()] = array( + 'uri' => $file->getBestURI(), + 'tip' => 'v3/'.$image, + ); + } + + $buttons = array(); + foreach ($images as $phid => $spec) { + $button = javelin_tag( + 'img', + array( + 'height' => 100, + 'width' => 100, + 'src' => $spec['uri'], + 'style' => 'float: left; padding: 4px;', + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $spec['tip'], + 'size' => 300, + ), + )); + + $buttons[] = $button; + } + + $wrap1 = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Images')) + ->appendChild($buttons) + ->addClass('grouped'); + + return phutil_tag( + 'div', + array(), + array( + $wrap1, + )); + } +} diff --git a/src/applications/uiexample/examples/PhabricatorRemarkupUIExample.php b/src/applications/uiexample/examples/PhabricatorRemarkupUIExample.php index 46f0262bbb..5044821d82 100644 --- a/src/applications/uiexample/examples/PhabricatorRemarkupUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorRemarkupUIExample.php @@ -11,6 +11,10 @@ final class PhabricatorRemarkupUIExample extends PhabricatorUIExample { 'Demonstrates the visual appearance of various Remarkup elements.'); } + public function getCategory() { + return pht('Technical'); + } + public function renderExample() { $viewer = $this->getRequest()->getUser(); diff --git a/src/applications/uiexample/examples/PhabricatorSetupIssueUIExample.php b/src/applications/uiexample/examples/PhabricatorSetupIssueUIExample.php index e5370d30cf..d6386e59d4 100644 --- a/src/applications/uiexample/examples/PhabricatorSetupIssueUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorSetupIssueUIExample.php @@ -10,6 +10,10 @@ final class PhabricatorSetupIssueUIExample extends PhabricatorUIExample { return pht('Setup errors and warnings.'); } + public function getCategory() { + return pht('Single Use'); + } + public function renderExample() { $request = $this->getRequest(); $user = $request->getUser(); diff --git a/src/applications/uiexample/examples/PhabricatorSortTableUIExample.php b/src/applications/uiexample/examples/PhabricatorSortTableUIExample.php deleted file mode 100644 index 113c38b92a..0000000000 --- a/src/applications/uiexample/examples/PhabricatorSortTableUIExample.php +++ /dev/null @@ -1,96 +0,0 @@ - 'Honda', - 'model' => 'Civic', - 'year' => 2004, - 'price' => 3199, - 'color' => pht('Blue'), - ), - array( - 'make' => 'Ford', - 'model' => 'Focus', - 'year' => 2001, - 'price' => 2549, - 'color' => pht('Red'), - ), - array( - 'make' => 'Toyota', - 'model' => 'Camry', - 'year' => 2009, - 'price' => 4299, - 'color' => pht('Black'), - ), - array( - 'make' => 'NASA', - 'model' => 'Shuttle', - 'year' => 1998, - 'price' => 1000000000, - 'color' => pht('White'), - ), - ); - - $request = $this->getRequest(); - - $orders = array( - 'make', - 'model', - 'year', - 'price', - ); - - $sort = $request->getStr('sort'); - list($sort, $reverse) = AphrontTableView::parseSort($sort); - if (!in_array($sort, $orders)) { - $sort = 'make'; - } - - $rows = isort($rows, $sort); - if ($reverse) { - $rows = array_reverse($rows); - } - - $table = new AphrontTableView($rows); - $table->setHeaders( - array( - pht('Make'), - pht('Model'), - pht('Year'), - pht('Price'), - pht('Color'), - )); - $table->setColumnClasses( - array( - '', - 'wide', - 'n', - 'n', - '', - )); - $table->makeSortable( - $request->getRequestURI(), - 'sort', - $sort, - $reverse, - $orders); - - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText(pht('Sortable Table of Vehicles')); - $panel->setTable($table); - - return $panel; - } -} diff --git a/src/applications/uiexample/examples/PhabricatorTooltipUIExample.php b/src/applications/uiexample/examples/PhabricatorTooltipUIExample.php deleted file mode 100644 index 373161612a..0000000000 --- a/src/applications/uiexample/examples/PhabricatorTooltipUIExample.php +++ /dev/null @@ -1,102 +0,0 @@ - array( - 'tip' => 'Hi', - ), - 'lorem (north)' => array( - 'tip' => $lorem, - ), - 'lorem (east)' => array( - 'tip' => $lorem, - 'align' => 'E', - ), - 'lorem (south)' => array( - 'tip' => $lorem, - 'align' => 'S', - ), - 'lorem (west)' => array( - 'tip' => $lorem, - 'align' => 'W', - ), - 'lorem (large, north)' => array( - 'tip' => $lorem, - 'size' => 300, - ), - 'lorem (large, east)' => array( - 'tip' => $lorem, - 'size' => 300, - 'align' => 'E', - ), - 'lorem (large, west)' => array( - 'tip' => $lorem, - 'size' => 300, - 'align' => 'W', - ), - 'lorem (large, south)' => array( - 'tip' => $lorem, - 'size' => 300, - 'align' => 'S', - ), - 'overflow (north)' => array( - 'tip' => $overflow, - ), - 'overflow (east)' => array( - 'tip' => $overflow, - 'align' => 'E', - ), - 'overflow (south)' => array( - 'tip' => $overflow, - 'align' => 'S', - ), - 'overflow (west)' => array( - 'tip' => $overflow, - 'align' => 'W', - ), - ); - - $content = array(); - foreach ($metas as $key => $meta) { - $content[] = javelin_tag( - 'div', - array( - 'sigil' => 'has-tooltip', - 'meta' => $meta, - 'style' => $style, - ), - $key); - } - - return $content; - } -} diff --git a/src/applications/uiexample/examples/PhabricatorUIExample.php b/src/applications/uiexample/examples/PhabricatorUIExample.php index 5326cba3fe..7c84f6c9e4 100644 --- a/src/applications/uiexample/examples/PhabricatorUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorUIExample.php @@ -17,6 +17,10 @@ abstract class PhabricatorUIExample extends Phobject { abstract public function getDescription(); abstract public function renderExample(); + public function getCategory() { + return pht('General'); + } + protected function createBasicDummyHandle($name, $type, $fullname = null, $uri = null) { diff --git a/src/docs/contributor/bug_reports.diviner b/src/docs/contributor/bug_reports.diviner index 91cfbce947..fcf3eac5c5 100644 --- a/src/docs/contributor/bug_reports.diviner +++ b/src/docs/contributor/bug_reports.diviner @@ -3,37 +3,36 @@ Describes how to file an effective Phabricator bug report. -Include Reproduction Steps! -=========================== +Level Requirements +================== -IMPORTANT: When filing a bug report, you **MUST** include reproduction -instructions. We can not help fix bugs we can not reproduce, and will not -accept reports which omit reproduction instructions. +We accept bug reports through two channels: paid support and community +support. + +If you are a paying customer, use the +[[ https://admin.phacility.com/u/support | Support Channel ]] for your account +to report bugs. This document may help you file reports which we can resolve +more quickly, but you do not need to read it or follow the guidelines. + +Other users can follow the guidelines in this document to file bug reports on +the community forum. -For help, see @{article:Providing Reproduction Steps}. Overview ======== -Found a bug with Phabricator? Let us know! This article describes how to file -an effective bug report so we can get your issue fixed or help you work around -it. +This article describes how to file an effective Phabricator bug report. The most important things to do are: - check the list of common fixes below; - make sure Phabricator is up to date; - make sure we support your setup; - - gather debugging information; - - explain how to reproduce the issue; and - - create a task in - [[ http://secure.phabricator.com/u/newbug/ | Maniphest ]]. + - gather debugging information; and + - explain how to reproduce the issue. The rest of this article walks through these points in detail. -If you have a feature request (not a bug report), see -@{article:Contributing Feature Requests} for a more tailored guide. - For general information on contributing to Phabricator, see @{article:Contributor Introduction}. @@ -141,8 +140,8 @@ Reproducibility The most important part of your report content is instructions on how to reproduce the issue. What did you do? If you do it again, does it still break? -Does it depend on a specific browser? Can you reproduce the issue on -`secure.phabricator.com`? +Does it depend on a specific browser? Can you reproduce the issue on a test +instance on `admin.phabricator.com`? It is nearly impossible for us to resolve many issues if we can not reproduce them. We will not accept reports which do not contain the information required @@ -151,28 +150,18 @@ to reproduce problems. For help, see @{article:Providing Reproduction Steps}. -Create a Task in Maniphest -========================== +File a Bug Report +================= -If you're up to date, supported, have collected information about the problem, -and have the best reproduction instructions you can come up with, you're ready -to file an issue. +If you're up to date, have collected information about the problem, and have +the best reproduction instructions you can come up with, you're ready +to file a report. -It is **particularly critical** that you include reproduction steps. We will -not accept reports which describe issues we can not reproduce. +It is **particularly critical** that you include reproduction steps. -(NOTE) https://secure.phabricator.com/u/newbug/ +You can file a report on the community forum, here: -If you don't want to file there (or, for example, your bug relates to being -unable to log in or unable to file an issue in Maniphest) you can file on any -other channel, but we can address reports much more effectively if they're -filed against the upstream than if they're filed somewhere else. - -| Effectiveness | Filing Method | -|---|---| -| Best | Upstream Maniphest | -| Ehhh | Quora, StackOverflow, Facebook, Jelly, email, etc. | -| What | Passive-aggressive tweet | +(NOTE) https://discourse.phabricator-community.org/c/bug Next Steps @@ -180,6 +169,5 @@ Next Steps Continue by: - - learning about @{article: Contributing Feature Requests}; or - reading general support information in @{article:Support Resources}; or - returning to the @{article:Contributor Introduction}. diff --git a/src/docs/contributor/contrib_intro.diviner b/src/docs/contributor/contrib_intro.diviner index a93c8530dd..e298edc080 100644 --- a/src/docs/contributor/contrib_intro.diviner +++ b/src/docs/contributor/contrib_intro.diviner @@ -21,9 +21,10 @@ Without writing any code, learning the whole codebase, making a big time commitment, or having to touch PHP, here are some ways you can materially contribute to Phabricator: - - Send us an email or drop by IRC just to say "thanks". A big part of the - reason we build this software is to help people solve problems, and knowing - that our efforts are appreciated is really rewarding. + - Drop by the [[ https://phurl.io/u/discourse | community forum ]] just to + say "thanks". A big part of the reason we build this software is to help + people solve problems, and knowing that our efforts are appreciated is + really rewarding. - Recommend Phabricator to people who you think might find it useful. Our most powerful growth channel is word of mouth, and mentioning or tweeting about Phabricator helps the project grow. If writing a tweet sounds like @@ -36,16 +37,8 @@ contribute to Phabricator: > Phabricator is objectively the best thing. Source: I am a certified, internationally recognized expert. - - Report bugs and request features. We may not always be able to fix or build - things right away, but knowing about issues users are encountering or - features they'd like to see improves our ability to plan and prioritize. - - For details on reporting bugs, see @{article:Contributing Bug Reports}. - - For details on requesting features, see @{article:Contributing Feature - Requests}. - - Give us feedback on planned features. Most of what we'll build in the next - 6-12 months currently exists on the [[ Roadmap ]] or in Maniphest. Telling - us about use cases you have can help us build better products when the time - comes to write the code. + - Submit high-quality bug reports by carefully following the guide in + @{article:Contributing Bug Reports}. If all of this sounds nice but you really just want to write some code, be aware that this project often presents a high barrier to entry for new @@ -58,6 +51,4 @@ Next Steps Continue by: - learning about bug reports in @{article:Contributing Bug Reports}; - - learning about feature requests in - @{article:Contributing Feature Requests}; or - learning about code contributions in @{article:Contributing Code}. diff --git a/src/docs/contributor/contributing_code.diviner b/src/docs/contributor/contributing_code.diviner index 52b96d18fe..faaf5b16da 100644 --- a/src/docs/contributor/contributing_code.diviner +++ b/src/docs/contributor/contributing_code.diviner @@ -3,6 +3,22 @@ Describes how to contribute code to Phabricator. +Level Requirements +================== + +To contribute to the Phabricator upstream, you must first pass a series of +ancient trials and be invited to register an account in the ancestral +homeland of Phabricator, here on `secure.phabricator.com`. The nature and +location of these trials is a closely guarded secret. + +If you have passed these trials, this document can guide you through +contributing code. + +If you have not yet passed these trials, writing code is normally not the best +way to contribute to Phabricator. See @{article:Contributor Introduction} for +more information. + + Overview ======== @@ -32,11 +48,7 @@ For general information on contributing to Phabricator, see Coordinate First ================ -Before sending code, you should file a bug report or feature request describing -what you'd like to write. For details on how to do this, see these articles: - - - @{article:Contributing Bug Reports} - - @{article:Contributing Feature Requests} +Before sending code, you should file a task describing what you'd like to write. When you file a task, mention that you'd like to write the code to fix it. We can help contextualize your request or bug and guide you through writing an @@ -74,8 +86,7 @@ consider valuable enough to put long-term support resources behind, and that you're building it in a way that we're comfortable taking over. **Not a Good Fit**: Many patches aren't good fits for the upstream: they -implement features we simply don't want. You can find more information in -@{article:Contributing Feature Requests}. Coordinating with us first helps +implement features we simply don't want. Coordinating with us first helps make sure we're on the same page and interested in a feature. The most common type of patch along these lines is a patch which adds new @@ -218,35 +229,6 @@ In general, if you're coordinating with us first, we can usually provide guidance on how to implement things. The other articles in this section also provide information on how to work in the Phabricator codebase. -Not Sure Where To Get Started? -============================== - -If you don't have a specific bug or feature in mind and just want to write -some code, you can try to find something simple to get started with. - -Because we're usually quick to fix easy bugs and issues, we often don't have a -very good backlog of starter tasks. - -You can try searching in Maniphest for tasks tagged with #easy, which might -have something, but a lot of time this list is small and the tasks on it aren't -very fun or interesting even if they aren't technically too difficult. - -In general, the best way to contribute is to come to us with a problem you -encountered or something you're interested in building, and then work with us -to find a solution to it or a plan to build it. We can help turn a hacky patch -into something that's upstreamable, and you'll get a fix or feature you want. - -You can also look though the rest of the open tasks for something more -substantive that you're interested in. This will give you a better chance of -finding something that's relevant to you, but many tasks are large or blocked -by other large tasks. - -If you do find something, feel free to leave a comment like "I'm interested in -working on this, is this something I could reasonably help with?". We're happy -to walk through things, break larger tasks down into more detail, provide -pointers to similar changes and the right places in the codebase to get started, -and generally figure out how to attack a problem. - Next Steps ========== diff --git a/src/docs/contributor/feature_requests.diviner b/src/docs/contributor/feature_requests.diviner index a7a2a32af6..19a9f79cd6 100644 --- a/src/docs/contributor/feature_requests.diviner +++ b/src/docs/contributor/feature_requests.diviner @@ -3,27 +3,34 @@ Describes how to file an effective Phabricator feature request. -Describe Your Problem! -====================== -IMPORTANT: When filing a feature request, you **MUST** describe the root -problem you are facing. We will not accept feature requests which do not -include a problem description. See below for details. +Level Requirements +================== + +We accept feature requests through two channels: paid support and community +support. + +If you are a paying customer, use the +[[ https://admin.phacility.com/u/support | Support Channel ]] for your account +to request features. This document may help you frame your requests in a way +that lets us address them more quickly, but you do not need to read it or +follow the guidelines. + +Other users can file requests on the +[[ https://phurl.io/u/discourse | community forum ]]. + Overview ======== -Have a feature you'd like to see in Phabricator? This article describes how -to file an effective feature request. +This article describes how to file an effective feature request. The most important things to do are: - understand the upstream; - make sure your feature makes sense in the project; - align your expectations around timelines and priorities; - - describe your problem, not your solution; and - - file a task in - [[ http://secure.phabricator.com/u/newfeature/ | Maniphest ]]. + - describe your problem, not your solution. The rest of this article walks through these points in detail. @@ -139,10 +146,6 @@ give you any updates or predictions about timelines. One day, out of nowhere, your feature will materialize. That day may be a decade from now. You should have realistic expectations about this when filing a feature request. -If you want a concrete timeline, you can work with us to pay for some control -over our roadmap. For details, see -[[ https://secure.phabricator.com/w/prioritization/ | Prioritization ]]. - Describe Problems ================= @@ -219,20 +222,6 @@ Generally, you should wait until a problem actually occurs before filing a request about it. -Create a Task in Maniphest -========================== - -If you think your feature might be a good fit for the upstream, have reasonable -expectations about it, and have a good description of the problem you're trying -to solve, you're ready to file a feature request. - -It is **particularly critical** that you describe the problem you are facing, -not just the feature you want. We will not accept feature requests which do -not describe the root problem the feature is intended to resolve. - -(NOTE) https://secure.phabricator.com/u/newfeature/ - - Next Steps ========== diff --git a/src/docs/user/configuration/configuration_guide.diviner b/src/docs/user/configuration/configuration_guide.diviner index b4c494449b..98c27b9736 100644 --- a/src/docs/user/configuration/configuration_guide.diviner +++ b/src/docs/user/configuration/configuration_guide.diviner @@ -143,6 +143,14 @@ Finally, you should run the following commands to enable php support: Restart lighttpd after making your edits, then continue below. + +Load Balancer Health Checks +=========================== + +If you're using a load balancer in front of your webserver, you can configure +it to perform health checks using the path `/status/`. + + = Setup = Now, navigate to whichever subdomain you set up. You should see instructions to diff --git a/src/docs/user/configuration/configuring_backups.diviner b/src/docs/user/configuration/configuring_backups.diviner index 1f13e0e17f..9776448216 100644 --- a/src/docs/user/configuration/configuring_backups.diviner +++ b/src/docs/user/configuration/configuring_backups.diviner @@ -24,6 +24,12 @@ same steps you would if you were creating a backup and then restoring it, you will just backup the old machine and then restore the data onto the new machine. +WARNING: You need to restart Phabricator after restoring data. + +Restarting Phabricator after performing a restore makes sure that caches are +flushed properly. For complete instructions, see +@{article:Restarting Phabricator}. + Backup: MySQL Databases ======================= diff --git a/src/docs/user/support.diviner b/src/docs/user/support.diviner index 18de79a9a1..9dc06b15a3 100644 --- a/src/docs/user/support.diviner +++ b/src/docs/user/support.diviner @@ -30,19 +30,13 @@ Reporting Bugs ============== The upstream will accept **reproducible** bug reports in modern, first-party -production code running in reasonable environments. +production code running in reasonable environments. Before submitting a bug +report you **must update** to the latest version of Phabricator. This reduces +support costs on the upstream, please be mindful. To report bugs, see @{article:Contributing Bug Reports}. -Requesting Features -=================== - -The upstream accepts feature requests which **describe problems** you would -like Phabricator to be able to solve. - -To request features, see @{article:Contributing Feature Requests}. - Contributing ============ @@ -64,8 +58,8 @@ See [[ https://secure.phabricator.com/w/consulting/ | Consulting ]] for details. Helping individual installs navigate unique setup problems takes our time away from developing Phabricator, so we can not offer this service for free. -You may be able to get free help with these issues from the community. See -below for details. +You may be able to get free help with these issues from the +[[ https://phurl.io/u/discourse | community ]]. See below for details. Hosting @@ -82,42 +76,12 @@ endlessly to make installation a perplexing nightmare that none other than ourselves can hope to navigate. -Prioritization -============== +Phabricator Community +===================== -The upstream offers prioritization, a service which allows you to control -our roadmap and get features you're interested in built sooner at reasonable -rates. See -[[ https://secure.phabricator.com/w/prioritization/ | Prioritization ]] for -details. - - -Consulting -========== - -The upstream offers general-purpose consulting services. See -[[ https://secure.phabricator.com/w/consulting/ | Consulting ]] for details. - - -Community -========= - -These resources are not provided by the upstream. They are not official support -channels and you may not receive support here, or you may receive help which is -misleading or wrong. - -You may be able to get answers to questions on sites like -[[ http://stackoverflow.com | Stack Overflow ]], -[[ https://www.quora.com | Quora ]], -[[ https://jelly.co | Jelly ]], or -[[ https://twitter.com | Twitter ]]. The upstream occasionally participates on -these sites but these are not official support channels and you should not -expect to receive a response. - -There is a -[[ https://secure.phabricator.com/conpherence/1336/ | General Chat ]] -Conpherence room on this install, and you can ask questions in -[[ https://secure.phabricator.com/ponder/ | Ponder ]]. These -are not upstream support channels and you may not receive a response to -questions, but someone in the community may be able to point you in the right -direction. +We provide hosting for a +[[ https://phurl.io/u/discourse | Discussion Forum ]] +where admins and users help and answer questions from other community members. +Upstream developers may occasionally participate, but this is mostly +a user to user community. If you run into general problems, but are not +interested in paid support, this is the main place to find help. diff --git a/src/docs/user/userguide/diffusion_autoclose.diviner b/src/docs/user/userguide/diffusion_autoclose.diviner index f2533bbdc6..796a3b0404 100644 --- a/src/docs/user/userguide/diffusion_autoclose.diviner +++ b/src/docs/user/userguide/diffusion_autoclose.diviner @@ -17,21 +17,19 @@ troubleshoot it. Troubleshooting Autoclose ========================= -You can check if a branch is currently configured to autoclose on the main -repository view, or in the branches list view. Hover over the {icon check} or -{icon times} icon and you should see one of these messages: +You can check if a branch is currently configured to autoclose on the +management page for the given repository under the branches menu item. +You should see one of these statuses next to the name of the branch: - - {icon check} **Autoclose Enabled** Autoclose is active for this branch. - - {icon times} **Repository Importing** This repository is still importing. + - **Autoclose On** Autoclose is active for this branch. + - **Repository Importing** This repository is still importing. Autoclose does not activate until a repository finishes importing for the first time. This prevents situations where you import a repository and accidentally close hundreds of related objects during import. Autoclose will activate for new commits after the initial import completes. - - {icon times} **Repository Autoclose Disabled** Autoclose is disabled for - this entire repository. You can enable it in **Edit Repository**. - - {icon times} **Branch Untracked** This branch is not tracked. Because it + - **Tracking Off** This branch is not tracked. Because it is not tracked, commits on it won't be seen and won't be discovered. - - {icon times} **Branch Autoclose Disabled** Autoclose is not enabled for + - **Autoclose Off** Autoclose is not enabled for this branch. You can adjust which branches autoclose in **Edit Repository**. This option is only available in Git. diff --git a/src/docs/user/userguide/diffusion_existing.diviner b/src/docs/user/userguide/diffusion_existing.diviner index b5b1fbdf76..bf09d9ef1f 100644 --- a/src/docs/user/userguide/diffusion_existing.diviner +++ b/src/docs/user/userguide/diffusion_existing.diviner @@ -44,8 +44,8 @@ Importing Repositories There are two primary ways to import an existing repository: **Observe First**: In Git or Mercurial, you can observe the repository first. -Once the import completes, disable the **Observe** URI to automatically convert -it into a hosted repository. +Once the import completes, change the "I/O Type" on the **Observe** URI to +"No I/O" mode to automatically convert it into a hosted repository. **Push to Empty Repository**: Create an activate an empty repository, then push all of your changes to the empty repository. diff --git a/src/docs/user/userguide/profile_menu.diviner b/src/docs/user/userguide/profile_menu.diviner index 2725e71851..88eb32d3ff 100644 --- a/src/docs/user/userguide/profile_menu.diviner +++ b/src/docs/user/userguide/profile_menu.diviner @@ -153,6 +153,12 @@ to a Project or to a Home menu, that Dashboard will be presented in the context of that menu. This allows customization of different pages of content without having the user leave Home or the Project. +To use a Dashboard to replace the default Home menu, install it as a Global +Menu Item and move it to the topmost item. By default, the first Dashboard +the menu renders will be selected as the default. Users that modify their +personal Home menu, will have their topmost Dashboard be their default, +overriding the Global settings. + Writing New Item Types ====================== diff --git a/src/infrastructure/cluster/config/PhabricatorClusterDatabasesConfigOptionType.php b/src/infrastructure/cluster/config/PhabricatorClusterDatabasesConfigType.php similarity index 82% rename from src/infrastructure/cluster/config/PhabricatorClusterDatabasesConfigOptionType.php rename to src/infrastructure/cluster/config/PhabricatorClusterDatabasesConfigType.php index 48e65dc938..c44a6e8ffd 100644 --- a/src/infrastructure/cluster/config/PhabricatorClusterDatabasesConfigOptionType.php +++ b/src/infrastructure/cluster/config/PhabricatorClusterDatabasesConfigType.php @@ -1,19 +1,17 @@ $spec) { if (!is_array($spec)) { - throw new Exception( + throw $this->newException( pht( 'Database cluster configuration is not valid: each entry in the '. 'list must be a dictionary describing a database host, but '. @@ -40,7 +38,7 @@ final class PhabricatorClusterDatabasesConfigOptionType 'persistent' => 'optional bool', )); } catch (Exception $ex) { - throw new Exception( + throw $this->newException( pht( 'Database cluster configuration has an invalid host '. 'specification (at index "%s"): %s.', @@ -57,7 +55,7 @@ final class PhabricatorClusterDatabasesConfigOptionType case 'replica': break; default: - throw new Exception( + throw $this->newException( pht( 'Database cluster configuration describes an invalid '. 'host ("%s", at index "%s") with an unrecognized role ("%s"). '. @@ -78,7 +76,7 @@ final class PhabricatorClusterDatabasesConfigOptionType // mistakes. $key = "{$host}:{$port}"; if (isset($map[$key])) { - throw new Exception( + throw $this->newException( pht( 'Database cluster configuration is invalid: it describes the '. 'same host ("%s") multiple times. Each host should appear only '. diff --git a/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php b/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php similarity index 86% rename from src/infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php rename to src/infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php index 90ead23e6d..ae54ff1a11 100644 --- a/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php +++ b/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php @@ -1,20 +1,17 @@ $spec) { diff --git a/src/infrastructure/cluster/exception/PhabricatorClusterExceptionHandler.php b/src/infrastructure/cluster/exception/PhabricatorClusterExceptionHandler.php index 2abda20b8f..ed0e0cc605 100644 --- a/src/infrastructure/cluster/exception/PhabricatorClusterExceptionHandler.php +++ b/src/infrastructure/cluster/exception/PhabricatorClusterExceptionHandler.php @@ -11,24 +11,24 @@ final class PhabricatorClusterExceptionHandler return pht('Handles runtime problems with cluster configuration.'); } - public function canHandleRequestException( + public function canHandleRequestThrowable( AphrontRequest $request, - Exception $ex) { - return ($ex instanceof PhabricatorClusterException); + $throwable) { + return ($throwable instanceof PhabricatorClusterException); } - public function handleRequestException( + public function handleRequestThrowable( AphrontRequest $request, - Exception $ex) { + $throwable) { $viewer = $this->getViewer($request); - $title = $ex->getExceptionTitle(); + $title = $throwable->getExceptionTitle(); $dialog = id(new AphrontDialogView()) ->setTitle($title) ->setUser($viewer) - ->appendParagraph($ex->getMessage()) + ->appendParagraph($throwable->getMessage()) ->addCancelButton('/', pht('Proceed With Caution')); return id(new AphrontDialogResponse()) diff --git a/src/infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php b/src/infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php index 8186ff5311..9e0684cacc 100644 --- a/src/infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php +++ b/src/infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php @@ -9,11 +9,11 @@ final class PhabricatorCustomFieldFulltextEngineExtension return pht('Custom Fields'); } - public function shouldIndexFulltextObject($object) { + public function shouldEnrichFulltextObject($object) { return ($object instanceof PhabricatorCustomFieldInterface); } - public function indexFulltextObject( + public function enrichFulltextObject( $object, PhabricatorSearchAbstractDocument $document) { diff --git a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php index 57a69843a4..c2276843db 100644 --- a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php +++ b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php @@ -23,11 +23,15 @@ final class PhabricatorTaskmasterDaemon extends PhabricatorDaemon { $ex = $task->getExecutionException(); if ($ex) { if ($ex instanceof PhabricatorWorkerPermanentFailureException) { - $this->log( + // NOTE: Make sure these reach the daemon log, even when not + // running in "phd.verbose" mode. See T12803 for discussion. + $log_exception = new PhutilProxyException( pht( - 'Task %d was cancelled: %s', - $id, - $ex->getMessage())); + 'Task "%s" encountered a permanent failure and was '. + 'cancelled.', + $id), + $ex); + phlog($log_exception); } else if ($ex instanceof PhabricatorWorkerYieldException) { $this->log(pht('Task %s yielded.', $id)); } else { diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php index a05caa06a1..f4588b5f70 100644 --- a/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php +++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php @@ -84,7 +84,7 @@ final class PhabricatorWorkerTriggerManagementFireWorkflow $console->writeOut( "%s\n", pht( - 'Trigger is not scheduled to execute. Use --next to simluate '. + 'Trigger is not scheduled to execute. Use --next to simulate '. 'a scheduled event.')); continue; } else { diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php index 1c92b167b0..d7f1a9dd3a 100644 --- a/src/infrastructure/diff/PhabricatorInlineCommentController.php +++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php @@ -94,19 +94,6 @@ abstract class PhabricatorInlineCommentController $op = $this->getOperation(); switch ($op) { - case 'busy': - if ($request->isFormPost()) { - return new AphrontAjaxResponse(); - } - - return $this->newDialog() - ->setTitle(pht('Already Editing')) - ->appendParagraph( - pht( - 'You are already editing an inline comment. Finish editing '. - 'your current comment before adding new comments.')) - ->addCancelButton('/') - ->addSubmitButton(pht('Jump to Inline')); case 'hide': case 'show': if (!$request->validateCSRF()) { @@ -130,12 +117,14 @@ abstract class PhabricatorInlineCommentController $inline = $this->loadCommentForDone($this->getCommentID()); $is_draft_state = false; + $is_checked = false; switch ($inline->getFixedState()) { case PhabricatorInlineCommentInterface::STATE_DRAFT: $next_state = PhabricatorInlineCommentInterface::STATE_UNDONE; break; case PhabricatorInlineCommentInterface::STATE_UNDRAFT: $next_state = PhabricatorInlineCommentInterface::STATE_DONE; + $is_checked = true; break; case PhabricatorInlineCommentInterface::STATE_DONE: $next_state = PhabricatorInlineCommentInterface::STATE_UNDRAFT; @@ -145,6 +134,7 @@ abstract class PhabricatorInlineCommentController case PhabricatorInlineCommentInterface::STATE_UNDONE: $next_state = PhabricatorInlineCommentInterface::STATE_DRAFT; $is_draft_state = true; + $is_checked = true; break; } @@ -153,6 +143,7 @@ abstract class PhabricatorInlineCommentController return id(new AphrontAjaxResponse()) ->setContent( array( + 'isChecked' => $is_checked, 'draftState' => $is_draft_state, )); case 'delete': diff --git a/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php b/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php index 13bf3ad83b..5c2bafdc86 100644 --- a/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php +++ b/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php @@ -60,4 +60,7 @@ interface PhabricatorInlineCommentInterface extends PhabricatorMarkupInterface { public function supportsHiding(); public function isHidden(); + public function getDateModified(); + public function getDateCreated(); + } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index f01bfbdb65..27a489a705 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -99,6 +99,27 @@ final class PHUIDiffInlineCommentDetailView 'differential-inline-comment', ); + $is_fixed = false; + switch ($inline->getFixedState()) { + case PhabricatorInlineCommentInterface::STATE_DONE: + case PhabricatorInlineCommentInterface::STATE_DRAFT: + $is_fixed = true; + break; + } + + $is_draft_done = false; + switch ($inline->getFixedState()) { + case PhabricatorInlineCommentInterface::STATE_DRAFT: + case PhabricatorInlineCommentInterface::STATE_UNDRAFT: + $is_draft_done = true; + break; + } + + $is_synthetic = false; + if ($inline->getSyntheticAuthor()) { + $is_synthetic = true; + } + $metadata = array( 'id' => $inline->getID(), 'phid' => $inline->getPHID(), @@ -109,6 +130,11 @@ final class PHUIDiffInlineCommentDetailView 'on_right' => $this->getIsOnRight(), 'original' => $inline->getContent(), 'replyToCommentPHID' => $inline->getReplyToCommentPHID(), + 'isDraft' => $inline->isDraft(), + 'isFixed' => $is_fixed, + 'isGhost' => $inline->getIsGhost(), + 'isSynthetic' => $is_synthetic, + 'isDraftDone' => $is_draft_done, ); $sigil = 'differential-inline-comment'; @@ -125,11 +151,6 @@ final class PHUIDiffInlineCommentDetailView $links = array(); - $is_synthetic = false; - if ($inline->getSyntheticAuthor()) { - $is_synthetic = true; - } - $draft_text = null; if (!$is_synthetic) { // This display is controlled by CSS @@ -137,7 +158,7 @@ final class PHUIDiffInlineCommentDetailView ->setType(PHUITagView::TYPE_SHADE) ->setName(pht('Unsubmitted')) ->setSlimShady(true) - ->setShade(PHUITagView::COLOR_RED) + ->setColor(PHUITagView::COLOR_RED) ->addClass('mml inline-draft-text'); } @@ -190,61 +211,31 @@ final class PHUIDiffInlineCommentDetailView } } - $nextprev = null; - if (!$this->preview) { - $nextprev = new PHUIButtonBarView(); - $nextprev->setBorderless(true); - $nextprev->addClass('inline-button-divider'); - - - $up = id(new PHUIButtonView()) - ->setTag('a') - ->setTooltip(pht('Previous')) - ->setIcon('fa-chevron-up') - ->addSigil('differential-inline-prev') - ->setMustCapture(true); - - $down = id(new PHUIButtonView()) - ->setTag('a') - ->setTooltip(pht('Next')) - ->setIcon('fa-chevron-down') - ->addSigil('differential-inline-next') - ->setMustCapture(true); - - if ($this->canHide()) { - $hide = id(new PHUIButtonView()) - ->setTag('a') - ->setTooltip(pht('Hide Comment')) - ->setIcon('fa-times') - ->addSigil('hide-inline') - ->setMustCapture(true); - - $nextprev->addButton($hide); - } - - $nextprev->addButton($up); - $nextprev->addButton($down); - - $action_buttons = array(); - if ($this->allowReply) { - if (!$is_synthetic) { - // NOTE: No product reason why you can't reply to these, but the reply - // mechanism currently sends the inline comment ID to the server, not - // file/line information, and synthetic comments don't have an inline - // comment ID. - - $action_buttons[] = id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-reply') - ->setTooltip(pht('Reply')) - ->addSigil('differential-inline-reply') - ->setMustCapture(true); - } - } - } - $anchor_name = $this->getAnchorName(); + $action_buttons = array(); + + $can_reply = + (!$this->editable) && + (!$this->preview) && + ($this->allowReply) && + + // NOTE: No product reason why you can't reply to synthetic comments, + // but the reply mechanism currently sends the inline comment ID to the + // server, not file/line information, and synthetic comments don't have + // an inline comment ID. + (!$is_synthetic); + + + if ($can_reply) { + $action_buttons[] = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-reply') + ->setTooltip(pht('Reply')) + ->addSigil('differential-inline-reply') + ->setMustCapture(true); + } + if ($this->editable && !$this->preview) { $action_buttons[] = id(new PHUIButtonView()) ->setTag('a') @@ -280,6 +271,15 @@ final class PHUIDiffInlineCommentDetailView ->setMustCapture(true); } + if (!$this->preview && $this->canHide()) { + $action_buttons[] = id(new PHUIButtonView()) + ->setTag('a') + ->setTooltip(pht('Collapse')) + ->setIcon('fa-times') + ->addSigil('hide-inline') + ->setMustCapture(true); + } + $done_button = null; if (!$is_synthetic) { @@ -393,7 +393,7 @@ final class PHUIDiffInlineCommentDetailView ->setType(PHUITagView::TYPE_SHADE) ->setName(pht('Author')) ->setSlimShady(true) - ->setShade(PHUITagView::COLOR_YELLOW) + ->setColor(PHUITagView::COLOR_YELLOW) ->addClass('mml'); } } @@ -426,13 +426,16 @@ final class PHUIDiffInlineCommentDetailView 'class' => 'inline-head-right', ), array( - $anchor, $done_button, $links, $actions, - $nextprev, )); + $snippet = id(new PhutilUTF8StringTruncator()) + ->setMaximumGlyphs(96) + ->truncateString($inline->getContent()); + $metadata['snippet'] = pht('%s: %s', $author, $snippet); + $markup = javelin_tag( 'div', array( @@ -441,16 +444,37 @@ final class PHUIDiffInlineCommentDetailView 'meta' => $metadata, ), array( - phutil_tag_div('differential-inline-comment-head grouped', array( - $group_left, - $group_right, - )), + javelin_tag( + 'div', + array( + 'class' => 'differential-inline-comment-head grouped', + 'sigil' => 'differential-inline-header', + ), + array( + $group_left, + $group_right, + )), phutil_tag_div( 'differential-inline-comment-content', phutil_tag_div('phabricator-remarkup', $content)), )); - return $markup; + $summary = phutil_tag( + 'div', + array( + 'class' => 'differential-inline-summary', + ), + array( + phutil_tag('strong', array(), pht('%s:', $author)), + ' ', + $snippet, + )); + + return array( + $anchor, + $markup, + $summary, + ); } private function canHide() { diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php index a7c10b5b48..7ad3b1f1a6 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php @@ -121,14 +121,13 @@ final class PHUIDiffInlineCommentEditView private function renderBody() { $buttons = array(); - $buttons[] = phutil_tag('button', array(), pht('Save Draft')); - $buttons[] = javelin_tag( - 'button', - array( - 'sigil' => 'inline-edit-cancel', - 'class' => 'grey', - ), - pht('Cancel')); + $buttons[] = id(new PHUIButtonView()) + ->setText(pht('Save Draft')); + + $buttons[] = id(new PHUIButtonView()) + ->setText(pht('Cancel')) + ->setColor(PHUIButtonView::GREY) + ->addSigil('inline-edit-cancel'); $title = phutil_tag( 'div', diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php index b0c0b4ccac..7c6b1c54b5 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php @@ -21,21 +21,28 @@ abstract class PHUIDiffInlineCommentRowScaffold extends AphrontView { } protected function getRowAttributes() { - // TODO: This is semantic information used by the JS when placing comments - // and using keyboard navigation; we should move it out of class names. - - $style = null; + $is_hidden = false; foreach ($this->getInlineViews() as $view) { if ($view->isHidden()) { - $style = 'display: none'; + $is_hidden = true; } } - return array( - 'class' => 'inline', + $classes = array(); + $classes[] = 'inline'; + if ($is_hidden) { + $classes[] = 'inline-hidden'; + } + + $result = array( + 'class' => implode(' ', $classes), 'sigil' => 'inline-row', - 'style' => $style, + 'meta' => array( + 'hidden' => $is_hidden, + ), ); + + return $result; } } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php index c4bdd65bf8..4abdb00e0b 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php @@ -9,6 +9,10 @@ final class PHUIDiffInlineCommentUndoView extends PHUIDiffInlineCommentView { + public function isHideable() { + return false; + } + public function render() { $link = javelin_tag( 'a', diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php index b62160e232..e2c89e238c 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php @@ -21,4 +21,16 @@ abstract class PHUIDiffInlineCommentView extends AphrontView { return false; } + public function isHideable() { + return true; + } + + public function newHiddenIcon() { + if ($this->isHideable()) { + return new PHUIDiffRevealIconView(); + } else { + return null; + } + } + } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineThreader.php b/src/infrastructure/diff/view/PHUIDiffInlineThreader.php new file mode 100644 index 0000000000..5209f1f359 --- /dev/null +++ b/src/infrastructure/diff/view/PHUIDiffInlineThreader.php @@ -0,0 +1,66 @@ + $comment) { + $reply_phid = $comment->getReplyToCommentPHID(); + if (isset($replies[$reply_phid])) { + $replies[$reply_phid][] = $comment; + unset($comments[$key]); + } + } + + // For each top level comment, add the comment, then add any replies + // to it. Do this recursively so threads are shown in threaded order. + $results = array(); + foreach ($comments as $comment) { + $results[] = $comment; + $phid = $comment->getPHID(); + $descendants = $this->getInlineReplies($replies, $phid, 1); + foreach ($descendants as $descendant) { + $results[] = $descendant; + } + } + + // If we have anything left, they were cyclic references. Just dump + // them in a the end. This should be impossible, but users are very + // creative. + foreach ($replies as $phid => $comments) { + foreach ($comments as $comment) { + $results[] = $comment; + } + } + + return $results; + } + + private function getInlineReplies(array &$replies, $phid, $depth) { + $comments = idx($replies, $phid, array()); + unset($replies[$phid]); + + $results = array(); + foreach ($comments as $comment) { + $results[] = $comment; + $descendants = $this->getInlineReplies( + $replies, + $comment->getPHID(), + $depth + 1); + foreach ($descendants as $descendant) { + $results[] = $descendant; + } + } + + return $results; + } +} diff --git a/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php index 708b70b360..53c2255dc8 100644 --- a/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php @@ -22,9 +22,17 @@ final class PHUIDiffOneUpInlineCommentRowScaffold 'id' => $inline->getScaffoldCellID(), ); + if ($inline->getIsOnRight()) { + $left_hidden = null; + $right_hidden = $inline->newHiddenIcon(); + } else { + $left_hidden = $inline->newHiddenIcon(); + $right_hidden = null; + } + $cells = array( - phutil_tag('th', array()), - phutil_tag('th', array()), + phutil_tag('th', array(), $left_hidden), + phutil_tag('th', array(), $right_hidden), phutil_tag('td', $attrs, $inline), ); diff --git a/src/infrastructure/diff/view/PHUIDiffRevealIconView.php b/src/infrastructure/diff/view/PHUIDiffRevealIconView.php index 284b72b2be..dca19725d8 100644 --- a/src/infrastructure/diff/view/PHUIDiffRevealIconView.php +++ b/src/infrastructure/diff/view/PHUIDiffRevealIconView.php @@ -8,7 +8,7 @@ final class PHUIDiffRevealIconView extends AphrontView { ->addSigil('has-tooltip') ->setMetadata( array( - 'tip' => pht('Show Hidden Comments'), + 'tip' => pht('Expand'), 'align' => 'E', 'size' => 275, )); @@ -17,8 +17,8 @@ final class PHUIDiffRevealIconView extends AphrontView { 'a', array( 'href' => '#', - 'class' => 'reveal-inlines', - 'sigil' => 'reveal-inlines', + 'class' => 'reveal-inline', + 'sigil' => 'reveal-inline', 'mustcapture' => true, ), $icon); diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php index e6ca6cc53d..47c8f633e7 100644 --- a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php @@ -103,22 +103,6 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { } } - $reveal_link = javelin_tag( - 'a', - array( - 'sigil' => 'differential-reveal-all', - 'mustcapture' => true, - 'class' => 'button differential-toc-reveal-all', - ), - pht('Show All Context')); - - $buttons = phutil_tag( - 'div', - array( - 'class' => 'differential-toc-buttons grouped', - ), - $reveal_link); - $table = id(new AphrontTableView($rows)) ->setRowClasses($rowc) ->setHeaders( @@ -185,8 +169,7 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { ->setHeader($header) ->setBackground($this->background) ->setTable($table) - ->appendChild($anchor) - ->appendChild($buttons); + ->appendChild($anchor); if ($this->infoView) { $box->setInfoView($this->infoView); diff --git a/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php index 4fac5088d1..81b0edaf49 100644 --- a/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php @@ -27,9 +27,15 @@ final class PHUIDiffTwoUpInlineCommentRowScaffold if ($inline->getIsOnRight()) { $left_side = null; $right_side = $inline; + + $left_hidden = null; + $right_hidden = $inline->newHiddenIcon(); } else { $left_side = $inline; $right_side = null; + + $left_hidden = $inline->newHiddenIcon(); + $right_hidden = null; } } else { list($u, $v) = $inlines; @@ -48,6 +54,9 @@ final class PHUIDiffTwoUpInlineCommentRowScaffold $left_side = $v; $right_side = $u; } + + $left_hidden = null; + $right_hidden = null; } $left_attrs = array( @@ -62,9 +71,9 @@ final class PHUIDiffTwoUpInlineCommentRowScaffold ); $cells = array( - phutil_tag('th', array()), + phutil_tag('th', array(), $left_hidden), phutil_tag('td', $left_attrs, $left_side), - phutil_tag('th', array()), + phutil_tag('th', array(), $right_hidden), phutil_tag('td', $right_attrs, $right_side), ); diff --git a/src/infrastructure/env/PhabricatorConfigLocalSource.php b/src/infrastructure/env/PhabricatorConfigLocalSource.php index 3a2295eb5e..16dc43a9bc 100644 --- a/src/infrastructure/env/PhabricatorConfigLocalSource.php +++ b/src/infrastructure/env/PhabricatorConfigLocalSource.php @@ -21,17 +21,35 @@ final class PhabricatorConfigLocalSource extends PhabricatorConfigProxySource { private function loadConfig() { $path = $this->getConfigPath(); - if (@file_exists($path)) { - $data = @file_get_contents($path); - if ($data) { - $data = json_decode($data, true); - if (is_array($data)) { - return $data; - } - } + + if (!Filesystem::pathExists($path)) { + return array(); } - return array(); + try { + $data = Filesystem::readFile($path); + } catch (FilesystemException $ex) { + throw new PhutilProxyException( + pht( + 'Configuration file "%s" exists, but could not be read.', + $path), + $ex); + } + + try { + $result = phutil_json_decode($data); + } catch (PhutilJSONParserException $ex) { + throw new PhutilProxyException( + pht( + 'Configuration file "%s" exists and is readable, but the content '. + 'is not valid JSON. You may have edited this file manually and '. + 'introduced a syntax error by mistake. Correct the file syntax '. + 'to continue.', + $path), + $ex); + } + + return $result; } private function saveConfig() { diff --git a/src/infrastructure/graph/DifferentialRevisionGraph.php b/src/infrastructure/graph/DifferentialRevisionGraph.php index 892540f610..715820e9e3 100644 --- a/src/infrastructure/graph/DifferentialRevisionGraph.php +++ b/src/infrastructure/graph/DifferentialRevisionGraph.php @@ -27,10 +27,12 @@ final class DifferentialRevisionGraph if ($object) { $status_icon = $object->getStatusIcon(); + $status_color = $object->getStatusIconColor(); $status_name = $object->getStatusDisplayName(); $status = array( - id(new PHUIIconView())->setIcon($status_icon), + id(new PHUIIconView()) + ->setIcon($status_icon, $status_color), ' ', $status_name, ); diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php index ad380414f6..0c56d9ed41 100644 --- a/src/infrastructure/markup/PhabricatorMarkupEngine.php +++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -42,7 +42,7 @@ final class PhabricatorMarkupEngine extends Phobject { private $objects = array(); private $viewer; private $contextObject; - private $version = 16; + private $version = 17; private $engineCaches = array(); private $auxiliaryConfig = array(); diff --git a/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php index 458ee5b834..3fc3373001 100644 --- a/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php @@ -60,7 +60,7 @@ final class PhabricatorNavigationRemarkupRule extends PhutilRemarkupRule { $tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($item_color) + ->setColor($item_color) ->setName($item_name); if ($item['icon']) { diff --git a/src/infrastructure/markup/rule/PhabricatorYoutubeRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorYoutubeRemarkupRule.php index bad8e0eacf..25422b970d 100644 --- a/src/infrastructure/markup/rule/PhabricatorYoutubeRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorYoutubeRemarkupRule.php @@ -2,34 +2,37 @@ final class PhabricatorYoutubeRemarkupRule extends PhutilRemarkupRule { - private $uri; - public function getPriority() { return 350.0; } public function apply($text) { - $this->uri = new PhutilURI($text); - - if ($this->uri->getDomain() && - preg_match('/(^|\.)youtube\.com$/', $this->uri->getDomain()) && - idx($this->uri->getQueryParams(), 'v')) { - return $this->markupYoutubeLink(); + try { + $uri = new PhutilURI($text); + } catch (Exception $ex) { + return $text; } - return $text; - } + $domain = $uri->getDomain(); + if (!preg_match('/(^|\.)youtube\.com\z/', $domain)) { + return $text; + } + + $params = $uri->getQueryParams(); + $v_param = idx($params, 'v'); + if (!strlen($v_param)) { + return $text; + } - public function markupYoutubeLink() { - $v = idx($this->uri->getQueryParams(), 'v'); $text_mode = $this->getEngine()->isTextMode(); $mail_mode = $this->getEngine()->isHTMLMailMode(); if ($text_mode || $mail_mode) { - return $this->getEngine()->storeText('http://youtu.be/'.$v); + return $text; } - $youtube_src = 'https://www.youtube.com/embed/'.$v; + $youtube_src = 'https://www.youtube.com/embed/'.$v_param; + $iframe = $this->newTag( 'div', array( @@ -45,6 +48,7 @@ final class PhabricatorYoutubeRemarkupRule extends PhutilRemarkupRule { 'frameborder' => 0, ), '')); + return $this->getEngine()->storeText($iframe); } diff --git a/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php b/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php index 74b3830676..54cd7ae51f 100644 --- a/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php +++ b/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php @@ -8,6 +8,7 @@ final class PhabricatorQueryConstraint extends Phobject { const OPERATOR_NULL = 'null'; const OPERATOR_ANCESTOR = 'ancestor'; const OPERATOR_EMPTY = 'empty'; + const OPERATOR_ONLY = 'only'; private $operator; private $value; diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 2bd374d5db..18e860ebb9 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -27,6 +27,11 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery private $spacePHIDs; private $spaceIsArchived; private $ngrams = array(); + private $ferretEngine; + private $ferretTokens = array(); + private $ferretTables = array(); + private $ferretQuery; + private $ferretMetadata = array(); protected function getPageCursors(array $page) { return array( @@ -78,6 +83,18 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery return $this->beforeID; } + final public function getFerretMetadata() { + if (!$this->supportsFerretEngine()) { + throw new Exception( + pht( + 'Unable to retrieve Ferret engine metadata, this class ("%s") does '. + 'not support the Ferret engine.', + get_class($this))); + } + + return $this->ferretMetadata; + } + protected function loadStandardPage(PhabricatorLiskDAO $table) { $rows = $this->loadStandardPageRows($table); return $table->loadAllFromArray($rows); @@ -107,6 +124,27 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $this->buildOrderClause($conn), $this->buildLimitClause($conn)); + $rows = $this->didLoadRawRows($rows); + + return $rows; + } + + protected function didLoadRawRows(array $rows) { + if ($this->ferretEngine) { + foreach ($rows as $row) { + $phid = $row['phid']; + + $metadata = id(new PhabricatorFerretMetadata()) + ->setPHID($phid) + ->setEngine($this->ferretEngine) + ->setRelevance(idx($row, '_ft_rank')); + + $this->ferretMetadata[$phid] = $metadata; + + unset($row['_ft_rank']); + } + } + return $rows; } @@ -168,6 +206,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery if ($this->beforeID) { $results = array_reverse($results, $preserve_keys = true); } + return $results; } @@ -248,6 +287,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } $select[] = $this->buildEdgeLogicSelectClause($conn); + $select[] = $this->buildFerretSelectClause($conn); return $select; } @@ -270,6 +310,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $joins[] = $this->buildEdgeLogicJoinClause($conn); $joins[] = $this->buildApplicationSearchJoinClause($conn); $joins[] = $this->buildNgramsJoinClause($conn); + $joins[] = $this->buildFerretJoinClause($conn); return $joins; } @@ -292,6 +333,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $where[] = $this->buildEdgeLogicWhereClause($conn); $where[] = $this->buildSpacesWhereClause($conn); $where[] = $this->buildNgramsWhereClause($conn); + $where[] = $this->buildFerretWhereClause($conn); return $where; } @@ -346,6 +388,10 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery return true; } + if ($this->shouldGroupFerretResultRows()) { + return true; + } + return false; } @@ -760,6 +806,13 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } } + if ($this->supportsFerretEngine()) { + $orders['relevance'] = array( + 'vector' => array('rank', 'fulltext-modified', 'id'), + 'name' => pht('Relevence'), + ); + } + return $orders; } @@ -952,6 +1005,24 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } } + if ($this->supportsFerretEngine()) { + $columns['rank'] = array( + 'table' => null, + 'column' => '_ft_rank', + 'type' => 'int', + ); + $columns['fulltext-created'] = array( + 'table' => 'ft_doc', + 'column' => 'epochCreated', + 'type' => 'int', + ); + $columns['fulltext-modified'] = array( + 'table' => 'ft_doc', + 'column' => 'epochModified', + 'type' => 'int', + ); + } + $cache->setKey($cache_key, $columns); return $columns; @@ -1373,6 +1444,501 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } +/* -( Ferret )------------------------------------------------------------- */ + + + public function supportsFerretEngine() { + $object = $this->newResultObject(); + return ($object instanceof PhabricatorFerretInterface); + } + + public function withFerretQuery( + PhabricatorFerretEngine $engine, + PhabricatorSavedQuery $query) { + + if (!$this->supportsFerretEngine()) { + throw new Exception( + pht( + 'Query ("%s") does not support the Ferret fulltext engine.', + get_class($this))); + } + + $this->ferretEngine = $engine; + $this->ferretQuery = $query; + + return $this; + } + + public function withFerretConstraint( + PhabricatorFerretEngine $engine, + array $fulltext_tokens) { + + if (!$this->supportsFerretEngine()) { + throw new Exception( + pht( + 'Query ("%s") does not support the Ferret fulltext engine.', + get_class($this))); + } + + if ($this->ferretEngine) { + throw new Exception( + pht( + 'Query may not have multiple fulltext constraints.')); + } + + if (!$fulltext_tokens) { + return $this; + } + + $this->ferretEngine = $engine; + $this->ferretTokens = $fulltext_tokens; + + $current_function = $engine->getDefaultFunctionKey(); + $table_map = array(); + $idx = 1; + foreach ($this->ferretTokens as $fulltext_token) { + $raw_token = $fulltext_token->getToken(); + $function = $raw_token->getFunction(); + + if ($function === null) { + $function = $current_function; + } + + $raw_field = $engine->getFieldForFunction($function); + + if (!isset($table_map[$function])) { + $alias = 'ftfield_'.$idx++; + $table_map[$function] = array( + 'alias' => $alias, + 'key' => $raw_field, + ); + } + + $current_function = $function; + } + + // Join the title field separately so we can rank results. + $table_map['rank'] = array( + 'alias' => 'ft_rank', + 'key' => PhabricatorSearchDocumentFieldType::FIELD_TITLE, + ); + + $this->ferretTables = $table_map; + + return $this; + } + + protected function buildFerretSelectClause(AphrontDatabaseConnection $conn) { + $select = array(); + + if (!$this->supportsFerretEngine()) { + return $select; + } + + if (!$this->ferretEngine) { + $select[] = '0 _ft_rank'; + return $select; + } + + $engine = $this->ferretEngine; + $stemmer = $engine->newStemmer(); + + $op_sub = PhutilSearchQueryCompiler::OPERATOR_SUBSTRING; + $op_not = PhutilSearchQueryCompiler::OPERATOR_NOT; + $table_alias = 'ft_rank'; + + $parts = array(); + foreach ($this->ferretTokens as $fulltext_token) { + $raw_token = $fulltext_token->getToken(); + $value = $raw_token->getValue(); + + if ($raw_token->getOperator() == $op_not) { + // Ignore "not" terms when ranking, since they aren't useful. + continue; + } + + if ($raw_token->getOperator() == $op_sub) { + $is_substring = true; + } else { + $is_substring = false; + } + + if ($is_substring) { + $parts[] = qsprintf( + $conn, + 'IF(%T.rawCorpus LIKE %~, 2, 0)', + $table_alias, + $value); + continue; + } + + if ($raw_token->isQuoted()) { + $is_quoted = true; + $is_stemmed = false; + } else { + $is_quoted = false; + $is_stemmed = true; + } + + $term_constraints = array(); + + $term_value = $engine->newTermsCorpus($value); + + $parts[] = qsprintf( + $conn, + 'IF(%T.termCorpus LIKE %~, 2, 0)', + $table_alias, + $term_value); + + if ($is_stemmed) { + $stem_value = $stemmer->stemToken($value); + $stem_value = $engine->newTermsCorpus($stem_value); + + $parts[] = qsprintf( + $conn, + 'IF(%T.normalCorpus LIKE %~, 1, 0)', + $table_alias, + $stem_value); + } + } + + $parts[] = '0'; + + $select[] = qsprintf( + $conn, + '%Q _ft_rank', + implode(' + ', $parts)); + + return $select; + } + + protected function buildFerretJoinClause(AphrontDatabaseConnection $conn) { + if (!$this->ferretEngine) { + return array(); + } + + $op_sub = PhutilSearchQueryCompiler::OPERATOR_SUBSTRING; + $op_not = PhutilSearchQueryCompiler::OPERATOR_NOT; + + $engine = $this->ferretEngine; + $stemmer = $engine->newStemmer(); + + $ngram_table = $engine->getNgramsTableName(); + + $flat = array(); + foreach ($this->ferretTokens as $fulltext_token) { + $raw_token = $fulltext_token->getToken(); + + // If this is a negated term like "-pomegranate", don't join the ngram + // table since we aren't looking for documents with this term. (We could + // LEFT JOIN the table and require a NULL row, but this is probably more + // trouble than it's worth.) + if ($raw_token->getOperator() == $op_not) { + continue; + } + + $value = $raw_token->getValue(); + + $length = count(phutil_utf8v($value)); + + if ($raw_token->getOperator() == $op_sub) { + $is_substring = true; + } else { + $is_substring = false; + } + + // If the user specified a substring query for a substring which is + // shorter than the ngram length, we can't use the ngram index, so + // don't do a join. We'll fall back to just doing LIKE on the full + // corpus. + if ($is_substring) { + if ($length < 3) { + continue; + } + } + + if ($raw_token->isQuoted()) { + $is_stemmed = false; + } else { + $is_stemmed = true; + } + + if ($is_substring) { + $ngrams = $engine->getSubstringNgramsFromString($value); + } else { + $ngrams = $engine->getTermNgramsFromString($value); + + // If this is a stemmed term, only look for ngrams present in both the + // unstemmed and stemmed variations. + if ($is_stemmed) { + $stem_value = $stemmer->stemToken($value); + $stem_ngrams = $engine->getTermNgramsFromString($stem_value); + $ngrams = array_intersect($ngrams, $stem_ngrams); + } + } + + foreach ($ngrams as $ngram) { + $flat[] = array( + 'table' => $ngram_table, + 'ngram' => $ngram, + ); + } + } + + // 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) { + $phid_column = qsprintf($conn, '%T.%T', $alias, 'phid'); + } else { + $phid_column = qsprintf($conn, '%T', 'phid'); + } + + $document_table = $engine->getDocumentTableName(); + $field_table = $engine->getFieldTableName(); + + $joins = array(); + $joins[] = qsprintf( + $conn, + 'JOIN %T ft_doc ON ft_doc.objectPHID = %Q', + $document_table, + $phid_column); + + $idx = 1; + foreach ($flat as $spec) { + $table = $spec['table']; + $ngram = $spec['ngram']; + + $alias = 'ftngram_'.$idx++; + + $joins[] = qsprintf( + $conn, + 'JOIN %T %T ON %T.documentID = ft_doc.id AND %T.ngram = %s', + $table, + $alias, + $alias, + $alias, + $ngram); + } + + foreach ($this->ferretTables as $table) { + $alias = $table['alias']; + + $joins[] = qsprintf( + $conn, + 'JOIN %T %T ON ft_doc.id = %T.documentID + AND %T.fieldKey = %s', + $field_table, + $alias, + $alias, + $alias, + $table['key']); + } + + return $joins; + } + + protected function buildFerretWhereClause(AphrontDatabaseConnection $conn) { + if (!$this->ferretEngine) { + return array(); + } + + $engine = $this->ferretEngine; + $stemmer = $engine->newStemmer(); + $table_map = $this->ferretTables; + + $op_sub = PhutilSearchQueryCompiler::OPERATOR_SUBSTRING; + $op_not = PhutilSearchQueryCompiler::OPERATOR_NOT; + + $where = array(); + $current_function = 'all'; + foreach ($this->ferretTokens as $fulltext_token) { + $raw_token = $fulltext_token->getToken(); + $value = $raw_token->getValue(); + + $function = $raw_token->getFunction(); + if ($function === null) { + $function = $current_function; + } + $current_function = $function; + + $table_alias = $table_map[$function]['alias']; + + $is_not = ($raw_token->getOperator() == $op_not); + + if ($raw_token->getOperator() == $op_sub) { + $is_substring = true; + } else { + $is_substring = false; + } + + // If we're doing substring search, we just match against the raw corpus + // and we're done. + if ($is_substring) { + if ($is_not) { + $where[] = qsprintf( + $conn, + '(%T.rawCorpus NOT LIKE %~)', + $table_alias, + $value); + } else { + $where[] = qsprintf( + $conn, + '(%T.rawCorpus LIKE %~)', + $table_alias, + $value); + } + continue; + } + + // Otherwise, we need to match against the term corpus and the normal + // corpus, so that searching for "raw" does not find "strawberry". + if ($raw_token->isQuoted()) { + $is_quoted = true; + $is_stemmed = false; + } else { + $is_quoted = false; + $is_stemmed = true; + } + + // Never stem negated queries, since this can exclude results users + // did not mean to exclude and generally confuse things. + if ($is_not) { + $is_stemmed = false; + } + + $term_constraints = array(); + + $term_value = $engine->newTermsCorpus($value); + if ($is_not) { + $term_constraints[] = qsprintf( + $conn, + '(%T.termCorpus NOT LIKE %~)', + $table_alias, + $term_value); + } else { + $term_constraints[] = qsprintf( + $conn, + '(%T.termCorpus LIKE %~)', + $table_alias, + $term_value); + } + + if ($is_stemmed) { + $stem_value = $stemmer->stemToken($value); + $stem_value = $engine->newTermsCorpus($stem_value); + + $term_constraints[] = qsprintf( + $conn, + '(%T.normalCorpus LIKE %~)', + $table_alias, + $stem_value); + } + + if ($is_not) { + $where[] = qsprintf( + $conn, + '(%Q)', + implode(' AND ', $term_constraints)); + } else if ($is_quoted) { + $where[] = qsprintf( + $conn, + '(%T.rawCorpus LIKE %~ AND (%Q))', + $table_alias, + $value, + implode(' OR ', $term_constraints)); + } else { + $where[] = qsprintf( + $conn, + '(%Q)', + implode(' OR ', $term_constraints)); + } + } + + if ($this->ferretQuery) { + $query = $this->ferretQuery; + + $author_phids = $query->getParameter('authorPHIDs'); + if ($author_phids) { + $where[] = qsprintf( + $conn, + 'ft_doc.authorPHID IN (%Ls)', + $author_phids); + } + + $with_unowned = $query->getParameter('withUnowned'); + $with_any = $query->getParameter('withAnyOwner'); + + if ($with_any && $with_unowned) { + throw new PhabricatorEmptyQueryException( + pht( + 'This query matches only unowned documents owned by anyone, '. + 'which is impossible.')); + } + + $owner_phids = $query->getParameter('ownerPHIDs'); + if ($owner_phids && !$with_any) { + if ($with_unowned) { + $where[] = qsprintf( + $conn, + 'ft_doc.ownerPHID IN (%Ls) OR ft_doc.ownerPHID IS NULL', + $owner_phids); + } else { + $where[] = qsprintf( + $conn, + 'ft_doc.ownerPHID IN (%Ls)', + $owner_phids); + } + } else if ($with_unowned) { + $where[] = qsprintf( + $conn, + 'ft_doc.ownerPHID IS NULL'); + } + + if ($with_any) { + $where[] = qsprintf( + $conn, + 'ft_doc.ownerPHID IS NOT NULL'); + } + + $rel_open = PhabricatorSearchRelationship::RELATIONSHIP_OPEN; + + $statuses = $query->getParameter('statuses'); + $is_closed = null; + if ($statuses) { + $statuses = array_fuse($statuses); + if (count($statuses) == 1) { + if (isset($statuses[$rel_open])) { + $is_closed = 0; + } else { + $is_closed = 1; + } + } + } + + if ($is_closed !== null) { + $where[] = qsprintf( + $conn, + 'ft_doc.isClosed = %d', + $is_closed); + } + } + + return $where; + } + + protected function shouldGroupFerretResultRows() { + return (bool)$this->ferretTokens; + } + + /* -( Ngrams )------------------------------------------------------------- */ @@ -1633,6 +2199,27 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $op_null = PhabricatorQueryConstraint::OPERATOR_NULL; $has_null = isset($constraints[$op_null]); + // If we're going to process an only() operator, build a list of the + // acceptable set of PHIDs first. We'll only match results which have + // no edges to any other PHIDs. + $all_phids = array(); + if (isset($constraints[PhabricatorQueryConstraint::OPERATOR_ONLY])) { + foreach ($constraints as $operator => $list) { + switch ($operator) { + case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: + case PhabricatorQueryConstraint::OPERATOR_AND: + case PhabricatorQueryConstraint::OPERATOR_OR: + foreach ($list as $constraint) { + $value = (array)$constraint->getValue(); + foreach ($value as $v) { + $all_phids[$v] = $v; + } + } + break; + } + } + } + foreach ($constraints as $operator => $list) { $alias = $this->getEdgeLogicTableAlias($operator, $type); @@ -1697,6 +2284,20 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $alias, $type); break; + case PhabricatorQueryConstraint::OPERATOR_ONLY: + $joins[] = qsprintf( + $conn, + 'LEFT JOIN %T %T ON %Q = %T.src AND %T.type = %d + AND %T.dst NOT IN (%Ls)', + $edge_table, + $alias, + $phid_column, + $alias, + $alias, + $type, + $alias, + $all_phids); + break; } } } @@ -1723,6 +2324,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $alias = $this->getEdgeLogicTableAlias($operator, $type); switch ($operator) { case PhabricatorQueryConstraint::OPERATOR_NOT: + case PhabricatorQueryConstraint::OPERATOR_ONLY: $full[] = qsprintf( $conn, '%T.dst IS NULL', @@ -1811,12 +2413,18 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery case PhabricatorQueryConstraint::OPERATOR_NOT: case PhabricatorQueryConstraint::OPERATOR_AND: case PhabricatorQueryConstraint::OPERATOR_OR: - case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: if (count($list) > 1) { return true; } break; + case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: + // NOTE: We must always group query results rows when using an + // "ANCESTOR" operator because a single task may be related to + // two different descendants of a particular ancestor. For + // discussion, see T12753. + return true; case PhabricatorQueryConstraint::OPERATOR_NULL: + case PhabricatorQueryConstraint::OPERATOR_ONLY: return true; } } @@ -1934,6 +2542,34 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } } + $op_and = PhabricatorQueryConstraint::OPERATOR_AND; + $op_or = PhabricatorQueryConstraint::OPERATOR_OR; + $op_ancestor = PhabricatorQueryConstraint::OPERATOR_ANCESTOR; + + foreach ($this->edgeLogicConstraints as $type => $constraints) { + foreach ($constraints as $operator => $list) { + switch ($operator) { + case PhabricatorQueryConstraint::OPERATOR_ONLY: + if (count($list) > 1) { + throw new PhabricatorEmptyQueryException( + pht( + 'This query specifies only() more than once.')); + } + + $have_and = idx($constraints, $op_and); + $have_or = idx($constraints, $op_or); + $have_ancestor = idx($constraints, $op_ancestor); + if (!$have_and && !$have_or && !$have_ancestor) { + throw new PhabricatorEmptyQueryException( + pht( + 'This query specifies only(), but no other constraints '. + 'which it can apply to.')); + } + break; + } + } + } + $this->edgeLogicConstraintsAreValid = true; return $this; diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAnalyzeWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAnalyzeWorkflow.php new file mode 100644 index 0000000000..6fe3e60926 --- /dev/null +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAnalyzeWorkflow.php @@ -0,0 +1,20 @@ +setName('analyze') + ->setExamples('**analyze**') + ->setSynopsis( + pht('Run "ANALYZE TABLE" on tables to improve performance.')); + } + + public function didExecute(PhutilArgumentParser $args) { + $api = $this->getSingleAPI(); + $this->analyzeTables($api); + return 0; + } + +} diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php index e915cedb8d..a03ef41c4d 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php @@ -137,6 +137,15 @@ abstract class PhabricatorStorageManagementWorkflow try { $err = $this->doAdjustSchemata($api, $unsafe); + + // Analyze tables if we're not doing a dry run and adjustments are either + // all clear or have minor errors like surplus tables. + if (!$this->dryRun) { + $should_analyze = (($err == 0) || ($err == 2)); + if ($should_analyze) { + $this->analyzeTables($api); + } + } } catch (Exception $ex) { $lock->unlock(); throw $ex; @@ -1090,7 +1099,9 @@ abstract class PhabricatorStorageManagementWorkflow } $t_begin = microtime(true); - $api->applyPatch($patch); + if (!$is_dryrun) { + $api->applyPatch($patch); + } $t_end = microtime(true); $duration = ($t_end - $t_begin); @@ -1100,7 +1111,9 @@ abstract class PhabricatorStorageManagementWorkflow // If we're explicitly reapplying this patch, we don't need to // mark it as applied. if (!isset($state_map[$ref_key][$key])) { - $api->markPatchApplied($key, ($t_end - $t_begin)); + if (!$is_dryrun) { + $api->markPatchApplied($key, ($t_end - $t_begin)); + } $applied_map[$ref_key][$key] = true; } } @@ -1159,4 +1172,54 @@ abstract class PhabricatorStorageManagementWorkflow ->lock(); } + final protected function analyzeTables( + PhabricatorStorageManagementAPI $api) { + + // Analyzing tables can sometimes have a significant effect on query + // performance, particularly for the fulltext ngrams tables. See T12819 + // for some specific examples. + + $conn = $api->getConn(null); + + $patches = $this->getPatches(); + $databases = $api->getDatabaseList($patches, true); + + $this->logInfo( + pht('ANALYZE'), + pht('Analyzing tables...')); + + $targets = array(); + foreach ($databases as $database) { + queryfx($conn, 'USE %C', $database); + $tables = queryfx_all($conn, 'SHOW TABLE STATUS'); + foreach ($tables as $table) { + $table_name = $table['Name']; + + $targets[] = array( + 'database' => $database, + 'table' => $table_name, + ); + } + } + + $bar = id(new PhutilConsoleProgressBar()) + ->setTotal(count($targets)); + foreach ($targets as $target) { + queryfx( + $conn, + 'ANALYZE TABLE %T.%T', + $target['database'], + $target['table']); + + $bar->update(1); + } + $bar->done(); + + $this->logOkay( + pht('ANALYZED'), + pht( + 'Analyzed %d table(s).', + count($targets))); + } + } diff --git a/src/view/AphrontDialogView.php b/src/view/AphrontDialogView.php index 4616393813..12bec92843 100644 --- a/src/view/AphrontDialogView.php +++ b/src/view/AphrontDialogView.php @@ -248,7 +248,7 @@ final class AphrontDialogView 'a', array( 'href' => $this->cancelURI, - 'class' => 'button grey', + 'class' => 'button button-grey', 'name' => '__cancel__', 'sigil' => 'jx-workflow-button', 'meta' => $meta, diff --git a/src/view/form/control/AphrontFormPolicyControl.php b/src/view/form/control/AphrontFormPolicyControl.php index 2ce13491af..0b0e608048 100644 --- a/src/view/form/control/AphrontFormPolicyControl.php +++ b/src/view/form/control/AphrontFormPolicyControl.php @@ -324,7 +324,8 @@ final class AphrontFormPolicyControl extends AphrontFormControl { javelin_tag( 'a', array( - 'class' => 'grey button dropdown has-icon policy-control', + 'class' => 'button button-grey dropdown has-icon has-text '. + 'policy-control', 'href' => '#', 'mustcapture' => true, 'sigil' => 'policy-control', diff --git a/src/view/form/control/AphrontFormTypeaheadControl.php b/src/view/form/control/AphrontFormTypeaheadControl.php index e4bee7c1ca..1e859ee132 100644 --- a/src/view/form/control/AphrontFormTypeaheadControl.php +++ b/src/view/form/control/AphrontFormTypeaheadControl.php @@ -3,6 +3,7 @@ final class AphrontFormTypeaheadControl extends AphrontFormControl { private $hardpointID; + private $placeholder; public function setHardpointID($hardpoint_id) { $this->hardpointID = $hardpoint_id; @@ -13,6 +14,11 @@ final class AphrontFormTypeaheadControl extends AphrontFormControl { return $this->hardpointID; } + public function setPlaceholder($placeholder) { + $this->placeholder = $placeholder; + return $this; + } + protected function getCustomControlClass() { return 'aphront-form-control-typeahead'; } @@ -30,6 +36,7 @@ final class AphrontFormTypeaheadControl extends AphrontFormControl { 'type' => 'text', 'name' => $this->getName(), 'value' => $this->getValue(), + 'placeholder' => $this->placeholder, 'disabled' => $this->getDisabled() ? 'disabled' : null, 'autocomplete' => 'off', 'id' => $this->getID(), diff --git a/src/view/form/control/PHUIFormFileControl.php b/src/view/form/control/PHUIFormFileControl.php index 57f5d30bf5..9dff7e21ca 100644 --- a/src/view/form/control/PHUIFormFileControl.php +++ b/src/view/form/control/PHUIFormFileControl.php @@ -30,15 +30,47 @@ final class PHUIFormFileControl 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(), )); - return phutil_tag( - 'input', - array( - 'type' => 'file', - 'multiple' => $this->getAllowMultiple() ? 'multiple' : null, - 'name' => $this->getName().'.raw', - 'id' => $file_id, - 'disabled' => $this->getDisabled() ? 'disabled' : null, - )); + + // If the control has a value, add a hidden input which submits it as a + // default. This allows the file control to mean "don't change anything", + // instead of "remove the file", if the user submits the form without + // touching it. + + // This also allows the input to "hold" the value of an uploaded file if + // there is another error in the form: when you submit the form but are + // stopped because of an unrelated error, submitting it again will keep + // the value around (if you don't upload a new file) instead of requiring + // you to pick the file again. + + // TODO: This works alright, but is a bit of a hack, and the UI should + // provide the user better feedback about whether the state of the control + // is "keep the value the same" or "remove the value", and about whether + // or not the control is "holding" a value from a previous submission. + + $default_input = null; + $default_value = $this->getValue(); + if ($default_value !== null) { + $default_input = phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => $this->getName().'_default', + 'value' => $default_value, + )); + } + + return array( + phutil_tag( + 'input', + array( + 'type' => 'file', + 'multiple' => $this->getAllowMultiple() ? 'multiple' : null, + 'name' => $this->getName().'_raw', + 'id' => $file_id, + 'disabled' => $this->getDisabled() ? 'disabled' : null, + )), + $default_input, + ); } } diff --git a/src/view/form/control/PHUIFormIconSetControl.php b/src/view/form/control/PHUIFormIconSetControl.php index 459bff08fa..96b1ad99b8 100644 --- a/src/view/form/control/PHUIFormIconSetControl.php +++ b/src/view/form/control/PHUIFormIconSetControl.php @@ -30,7 +30,7 @@ final class PHUIFormIconSetControl $classes = array(); $classes[] = 'button'; - $classes[] = 'grey'; + $classes[] = 'button-grey'; if ($is_disabled) { $classes[] = 'disabled'; diff --git a/src/view/layout/AphrontListFilterView.php b/src/view/layout/AphrontListFilterView.php index 7a6be9c3d2..f764964934 100644 --- a/src/view/layout/AphrontListFilterView.php +++ b/src/view/layout/AphrontListFilterView.php @@ -44,7 +44,7 @@ final class AphrontListFilterView extends AphrontView { $hide_action = javelin_tag( 'a', array( - 'class' => 'button grey', + 'class' => 'button button-grey', 'sigil' => 'reveal-content', 'id' => $hide_action_id, 'href' => $this->showHideHref, @@ -65,7 +65,7 @@ final class AphrontListFilterView extends AphrontView { $show_action = javelin_tag( 'a', array( - 'class' => 'button grey', + 'class' => 'button button-grey', 'sigil' => 'reveal-content', 'style' => 'display: none;', 'href' => '#', diff --git a/src/view/layout/PHUICurtainView.php b/src/view/layout/PHUICurtainView.php index af02ceb932..49ff55384a 100644 --- a/src/view/layout/PHUICurtainView.php +++ b/src/view/layout/PHUICurtainView.php @@ -46,10 +46,17 @@ final class PHUICurtainView extends AphrontTagView { $panels = $this->renderPanels(); - return id(new PHUIObjectBoxView()) + $box = id(new PHUIObjectBoxView()) ->appendChild($action_list) ->appendChild($panels) ->addClass('phui-two-column-properties'); + + // We want to hide this UI on mobile if there are no child panels + if (!$panels) { + $box->addClass('curtain-no-panels'); + } + + return $box; } private function renderPanels() { diff --git a/src/view/layout/PhabricatorActionListView.php b/src/view/layout/PhabricatorActionListView.php index faf30555eb..134c336735 100644 --- a/src/view/layout/PhabricatorActionListView.php +++ b/src/view/layout/PhabricatorActionListView.php @@ -16,6 +16,10 @@ final class PhabricatorActionListView extends AphrontTagView { } protected function getTagName() { + if (!$this->actions) { + return null; + } + return 'ul'; } diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php index 85b9d3abfe..f6de8eca5b 100644 --- a/src/view/layout/PhabricatorActionView.php +++ b/src/view/layout/PhabricatorActionView.php @@ -119,6 +119,11 @@ final class PhabricatorActionView extends AphrontView { return $this->openInNewWindow; } + public function setID($id) { + $this->id = $id; + return $this; + } + public function getID() { if (!$this->id) { $this->id = celerity_generate_unique_node_id(); diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index ffa82ab1b9..d26cca945f 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -19,6 +19,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView private $showFooter = true; private $showDurableColumn = true; private $quicksandConfig = array(); + private $tabs; private $crumbs; private $navigation; @@ -159,6 +160,17 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView return $this->crumbs; } + public function setTabs(PHUIListView $tabs) { + $tabs->setType(PHUIListView::TABBAR_LIST); + $tabs->addClass('phabricator-standard-page-tabs'); + $this->tabs = $tabs; + return $this; + } + + public function getTabs() { + return $this->tabs; + } + public function setNavigation(AphrontSideNavFilterView $navigation) { $this->navigation = $navigation; return $this; @@ -268,7 +280,8 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView } $icon = id(new PHUIIconView()) - ->setIcon('fa-download'); + ->setIcon('fa-download') + ->addClass('phui-icon-circle-icon'); $lightbox_id = celerity_generate_unique_node_id(); $download_form = phabricator_form( $user, @@ -527,6 +540,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView $footer = $this->renderFooter(); $nav = $this->getNavigation(); + $tabs = $this->getTabs(); if ($nav) { $crumbs = $this->getCrumbs(); if ($crumbs) { @@ -540,9 +554,17 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView $crumbs = $this->getCrumbs(); if ($crumbs) { + if ($this->getTabs()) { + $crumbs->setBorder(true); + } $content[] = $crumbs; } + $tabs = $this->getTabs(); + if ($tabs) { + $content[] = $tabs; + } + $content[] = $body; $content[] = $footer; diff --git a/src/view/page/menu/PhabricatorMainMenuSearchView.php b/src/view/page/menu/PhabricatorMainMenuSearchView.php index d21d23d0fd..0b2ca8ab10 100644 --- a/src/view/page/menu/PhabricatorMainMenuSearchView.php +++ b/src/view/page/menu/PhabricatorMainMenuSearchView.php @@ -39,6 +39,9 @@ final class PhabricatorMainMenuSearchView extends AphrontView { 'name' => 'query', 'id' => $search_id, 'autocomplete' => 'off', + 'autocorrect' => 'off', + 'autocapitalize' => 'off', + 'spellcheck' => 'false', )); $target = javelin_tag( diff --git a/src/view/phui/PHUIActionPanelView.php b/src/view/phui/PHUIActionPanelView.php index f5f74b2807..8ced0a642b 100644 --- a/src/view/phui/PHUIActionPanelView.php +++ b/src/view/phui/PHUIActionPanelView.php @@ -4,6 +4,7 @@ final class PHUIActionPanelView extends AphrontTagView { private $href; private $fontIcon; + private $image; private $header; private $subHeader; private $bigText; @@ -29,6 +30,11 @@ final class PHUIActionPanelView extends AphrontTagView { return $this; } + public function setImage($image) { + $this->image = $image; + return $this; + } + public function setBigText($text) { $this->bigText = $text; return $this; @@ -89,6 +95,21 @@ final class PHUIActionPanelView extends AphrontTagView { $fonticon); } + if ($this->image) { + $image = phutil_tag( + 'img', + array( + 'class' => 'phui-action-panel-image', + 'src' => $this->image, + )); + $icon = phutil_tag( + 'span', + array( + 'class' => 'phui-action-panel-icon', + ), + $image); + } + $header = null; if ($this->header) { $header = phutil_tag( diff --git a/src/view/phui/PHUIBigInfoView.php b/src/view/phui/PHUIBigInfoView.php index 0f5bcecfbd..31a458ed60 100644 --- a/src/view/phui/PHUIBigInfoView.php +++ b/src/view/phui/PHUIBigInfoView.php @@ -5,6 +5,7 @@ final class PHUIBigInfoView extends AphrontTagView { private $icon; private $title; private $description; + private $image; private $actions = array(); public function setIcon($icon) { @@ -22,6 +23,11 @@ final class PHUIBigInfoView extends AphrontTagView { return $this; } + public function setImage($image) { + $this->image = $image; + return $this; + } + public function addAction(PHUIButtonView $button) { $this->actions[] = $button; return $this; @@ -43,16 +49,34 @@ final class PHUIBigInfoView extends AphrontTagView { protected function getTagContent() { require_celerity_resource('phui-big-info-view-css'); - $icon = id(new PHUIIconView()) - ->setIcon($this->icon) - ->addClass('phui-big-info-icon'); + $icon = null; + if ($this->icon) { + $icon = id(new PHUIIconView()) + ->setIcon($this->icon) + ->addClass('phui-big-info-icon'); - $icon = phutil_tag( - 'div', - array( - 'class' => 'phui-big-info-icon-container', - ), - $icon); + $icon = phutil_tag( + 'div', + array( + 'class' => 'phui-big-info-icon-container', + ), + $icon); + } + + if ($this->image) { + $image = phutil_tag( + 'img', + array( + 'class' => 'phui-big-info-image', + 'src' => $this->image, + )); + $icon = phutil_tag( + 'span', + array( + 'class' => 'phui-big-info-icon-container', + ), + $image); + } $title = phutil_tag( 'div', diff --git a/src/view/phui/PHUIButtonBarView.php b/src/view/phui/PHUIButtonBarView.php index add91bbc8f..78443929d8 100644 --- a/src/view/phui/PHUIButtonBarView.php +++ b/src/view/phui/PHUIButtonBarView.php @@ -29,7 +29,7 @@ final class PHUIButtonBarView extends AphrontTagView { } protected function getTagContent() { - require_celerity_resource('phui-button-css'); + require_celerity_resource('phui-button-bar-css'); $i = 1; $j = count($this->buttons); diff --git a/src/view/phui/PHUIButtonView.php b/src/view/phui/PHUIButtonView.php index c4ee7a190c..91c336eaaf 100644 --- a/src/view/phui/PHUIButtonView.php +++ b/src/view/phui/PHUIButtonView.php @@ -4,16 +4,16 @@ final class PHUIButtonView extends AphrontTagView { const GREEN = 'green'; const GREY = 'grey'; + const BLUE = 'blue'; + const RED = 'red'; const DISABLED = 'disabled'; - const SIMPLE = 'simple'; - const SIMPLE_YELLOW = 'simple simple-yellow'; - const SIMPLE_GREY = 'simple simple-grey'; - const SIMPLE_BLUE = 'simple simple-blue'; - const SMALL = 'small'; const BIG = 'big'; + const BUTTONTYPE_DEFAULT = 'buttontype.default'; + const BUTTONTYPE_SIMPLE = 'buttontype.simple'; + private $size; private $text; private $subtext; @@ -25,10 +25,12 @@ final class PHUIButtonView extends AphrontTagView { private $href = null; private $title = null; private $disabled; + private $selected; private $name; private $tooltip; private $noCSS; private $hasCaret; + private $buttonType = self::BUTTONTYPE_DEFAULT; public function setName($name) { $this->name = $name; @@ -64,11 +66,20 @@ final class PHUIButtonView extends AphrontTagView { return $this; } + public function getColor() { + return $this->color; + } + public function setDisabled($disabled) { $this->disabled = $disabled; return $this; } + public function setSelected($selected) { + $this->selected = $selected; + return $this; + } + public function setTag($tag) { $this->tag = $tag; return $this; @@ -103,6 +114,15 @@ final class PHUIButtonView extends AphrontTagView { return $this->hasCaret; } + public function setButtonType($button_type) { + $this->buttonType = $button_type; + return $this; + } + + public function getButtonType() { + return $this->buttonType; + } + public function setIcon($icon, $first = true) { if (!($icon instanceof PHUIIconView)) { $icon = id(new PHUIIconView()) @@ -121,6 +141,7 @@ final class PHUIButtonView extends AphrontTagView { Javelin::initBehavior('phui-dropdown-menu'); $this->addSigil('phui-dropdown-menu'); + $this->setDropdown(true); $this->setMetadata($actions->getDropdownMenuMetadata()); return $this; @@ -141,12 +162,13 @@ final class PHUIButtonView extends AphrontTagView { protected function getTagAttributes() { require_celerity_resource('phui-button-css'); + require_celerity_resource('phui-button-simple-css'); $classes = array(); $classes[] = 'button'; if ($this->color) { - $classes[] = $this->color; + $classes[] = 'button-'.$this->color; } if ($this->size) { @@ -161,6 +183,10 @@ final class PHUIButtonView extends AphrontTagView { $classes[] = 'has-icon'; } + if ($this->text !== null) { + $classes[] = 'has-text'; + } + if ($this->iconFirst == false) { $classes[] = 'icon-last'; } @@ -169,6 +195,19 @@ final class PHUIButtonView extends AphrontTagView { $classes[] = 'disabled'; } + if ($this->selected) { + $classes[] = 'selected'; + } + + switch ($this->getButtonType()) { + case self::BUTTONTYPE_DEFAULT: + $classes[] = 'phui-button-default'; + break; + case self::BUTTONTYPE_SIMPLE: + $classes[] = 'phui-button-simple'; + break; + } + $sigil = null; $meta = null; if ($this->tooltip) { @@ -196,18 +235,29 @@ final class PHUIButtonView extends AphrontTagView { protected function getTagContent() { - $icon = null; - $text = $this->text; - if ($this->icon) { - $icon = $this->icon; + $icon = $this->icon; + $text = null; + $subtext = null; - $subtext = null; - if ($this->subtext) { - $subtext = phutil_tag( - 'div', array('class' => 'phui-button-subtext'), $this->subtext); - } + if ($this->subtext) { + $subtext = phutil_tag( + 'div', + array( + 'class' => 'phui-button-subtext', + ), + $this->subtext); + } + + if ($this->text !== null) { $text = phutil_tag( - 'div', array('class' => 'phui-button-text'), array($text, $subtext)); + 'div', + array( + 'class' => 'phui-button-text', + ), + array( + $this->text, + $subtext, + )); } $caret = null; @@ -218,7 +268,7 @@ final class PHUIButtonView extends AphrontTagView { if ($this->iconFirst == true) { return array($icon, $text, $caret); } else { - return array($text, $icon); + return array($text, $icon, $caret); } } } diff --git a/src/view/phui/PHUIDocumentViewPro.php b/src/view/phui/PHUIDocumentViewPro.php index d60cd78d44..51ecc3526e 100644 --- a/src/view/phui/PHUIDocumentViewPro.php +++ b/src/view/phui/PHUIDocumentViewPro.php @@ -79,7 +79,7 @@ final class PHUIDocumentViewPro extends AphrontTagView { $toc[] = id(new PHUIButtonView()) ->setTag('a') ->setIcon('fa-align-left') - ->setColor(PHUIButtonView::SIMPLE) + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) ->addClass('phui-document-toc') ->addSigil('jx-toggle-class') ->setMetaData(array( diff --git a/src/view/phui/PHUIFeedStoryView.php b/src/view/phui/PHUIFeedStoryView.php index fbba4a3f46..f0acd48201 100644 --- a/src/view/phui/PHUIFeedStoryView.php +++ b/src/view/phui/PHUIFeedStoryView.php @@ -150,13 +150,25 @@ final class PHUIFeedStoryView extends AphrontView { if ($this->getShowTimestamp()) { if ($this->epoch) { if ($user) { - $foot = phabricator_datetime($this->epoch, $user); + $marker = id(new PHUIIconView()) + ->setIcon('fa-circle') + ->addClass('phabricator-notification-status'); + $date = phabricator_datetime($this->epoch, $user); $foot = phutil_tag( 'span', array( 'class' => 'phabricator-notification-date', ), - $foot); + $date); + $foot = phutil_tag( + 'div', + array( + 'class' => 'phabricator-notification-foot', + ), + array( + $marker, + $date, + )); } else { $foot = null; } diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index 0ae2c558d6..dc836f3d3a 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -21,7 +21,6 @@ final class PHUIHeaderView extends AphrontTagView { private $policyObject; private $epoch; private $actionItems = array(); - private $badges = array(); private $href; private $actionList; private $actionListID; @@ -46,11 +45,6 @@ final class PHUIHeaderView extends AphrontTagView { return $this; } - public function addBadge(PHUIBadgeMiniView $badge) { - $this->badges[] = $badge; - return $this; - } - public function setImage($uri) { $this->image = $uri; return $this; @@ -131,7 +125,7 @@ final class PHUIHeaderView extends AphrontTagView { $tag = id(new PHUITagView()) ->setName($name) ->setIcon($icon) - ->setShade($color) + ->setColor($color) ->setType(PHUITagView::TYPE_SHADE); return $this->addProperty(self::PROPERTY_STATUS, $tag); @@ -267,7 +261,9 @@ final class PHUIHeaderView extends AphrontTagView { if ($this->actionLinks) { $actions = array(); foreach ($this->actionLinks as $button) { - $button->setColor(PHUIButtonView::GREY); + if (!$button->getColor()) { + $button->setColor(PHUIButtonView::GREY); + } $button->addClass(PHUI::MARGIN_SMALL_LEFT); $button->addClass('phui-header-action-link'); $actions[] = $button; @@ -339,21 +335,13 @@ final class PHUIHeaderView extends AphrontTagView { $header_content, )); - if ($this->subheader || $this->badges) { - $badges = null; - if ($this->badges) { - $badges = new PHUIBadgeBoxView(); - $badges->addItems($this->badges); - $badges->setCollapsed(true); - } - + if ($this->subheader) { $left[] = phutil_tag( 'div', array( 'class' => 'phui-header-subheader', ), array( - $badges, $this->subheader, )); } diff --git a/src/view/phui/PHUIHovercardView.php b/src/view/phui/PHUIHovercardView.php index b99fc8153e..f6cfc45a35 100644 --- a/src/view/phui/PHUIHovercardView.php +++ b/src/view/phui/PHUIHovercardView.php @@ -173,7 +173,7 @@ final class PHUIHovercardView extends AphrontTagView { foreach ($this->actions as $action) { $options = array( - 'class' => 'button grey', + 'class' => 'button button-grey', 'href' => $action['uri'], ); diff --git a/src/view/phui/PHUIIconCircleView.php b/src/view/phui/PHUIIconCircleView.php index 083818969f..11e2a2ac3b 100644 --- a/src/view/phui/PHUIIconCircleView.php +++ b/src/view/phui/PHUIIconCircleView.php @@ -6,10 +6,22 @@ final class PHUIIconCircleView extends AphrontTagView { private $icon; private $color; private $size; + private $state; const SMALL = 'circle-small'; const MEDIUM = 'circle-medium'; + const STATE_FAIL = 'fa-times-circle'; + const STATE_INFO = 'fa-info-circle'; + const STATE_STOP = 'fa-stop-circle'; + const STATE_START = 'fa-play-circle'; + const STATE_PAUSE = 'fa-pause-circle'; + const STATE_SUCCESS = 'fa-check-circle'; + const STATE_WARNING = 'fa-exclamation-circle'; + const STATE_PLUS = 'fa-plus-circle'; + const STATE_MINUS = 'fa-minus-circle'; + const STATE_UNKNOWN = 'fa-question-circle'; + public function setHref($href) { $this->href = $href; return $this; @@ -30,6 +42,11 @@ final class PHUIIconCircleView extends AphrontTagView { return $this; } + public function setState($state) { + $this->state = $state; + return $this; + } + protected function getTagName() { $tag = 'span'; if ($this->href) { @@ -54,6 +71,10 @@ final class PHUIIconCircleView extends AphrontTagView { $classes[] = $this->size; } + if ($this->state) { + $classes[] = 'phui-icon-circle-state'; + } + return array( 'href' => $this->href, 'class' => $classes, @@ -61,8 +82,32 @@ final class PHUIIconCircleView extends AphrontTagView { } protected function getTagContent() { + $state = null; + if ($this->state) { + $state = id(new PHUIIconView()) + ->setIcon($this->state.' '.$this->color) + ->addClass('phui-icon-circle-state-icon'); + } + return id(new PHUIIconView()) - ->setIcon($this->icon); + ->setIcon($this->icon) + ->addClass('phui-icon-circle-icon') + ->appendChild($state); + } + + public static function getStateMap() { + return array( + self::STATE_FAIL => pht('Failure'), + self::STATE_INFO => pht('Information'), + self::STATE_STOP => pht('Stop'), + self::STATE_START => pht('Start'), + self::STATE_PAUSE => pht('Pause'), + self::STATE_SUCCESS => pht('Success'), + self::STATE_WARNING => pht('Warning'), + self::STATE_PLUS => pht('Plus'), + self::STATE_MINUS => pht('Minus'), + self::STATE_UNKNOWN => pht('Unknown'), + ); } } diff --git a/src/view/phui/PHUIIconView.php b/src/view/phui/PHUIIconView.php index d6b1ad0ccd..8cc61ba2eb 100644 --- a/src/view/phui/PHUIIconView.php +++ b/src/view/phui/PHUIIconView.php @@ -18,6 +18,7 @@ final class PHUIIconView extends AphrontTagView { private $iconFont; private $iconColor; private $iconBackground; + private $tooltip; public function setHref($href) { $this->href = $href; @@ -60,6 +61,11 @@ final class PHUIIconView extends AphrontTagView { return $this; } + public function setTooltip($text) { + $this->tooltip = $text; + return $this; + } + protected function getTagName() { $tag = 'span'; if ($this->href) { @@ -100,11 +106,24 @@ final class PHUIIconView extends AphrontTagView { $this->appendChild($this->text); } + $sigil = null; + $meta = array(); + if ($this->tooltip) { + Javelin::initBehavior('phabricator-tooltips'); + require_celerity_resource('aphront-tooltip-css'); + $sigil = 'has-tooltip'; + $meta = array( + 'tip' => $this->tooltip, + ); + } + return array( 'href' => $this->href, 'style' => $style, 'aural' => false, 'class' => $classes, + 'sigil' => $sigil, + 'meta' => $meta, ); } diff --git a/src/view/phui/PHUIInfoPanelView.php b/src/view/phui/PHUIInfoPanelView.php deleted file mode 100644 index 91f9d2b8e4..0000000000 --- a/src/view/phui/PHUIInfoPanelView.php +++ /dev/null @@ -1,120 +0,0 @@ -header = $header; - return $this; - } - - public function setProgress($progress) { - $this->progress = $progress; - return $this; - } - - public function setColumns($columns) { - $this->columns = $columns; - return $this; - } - - public function addInfoblock($num, $text) { - $this->infoblock[] = array($num, $text); - return $this; - } - - public function render() { - require_celerity_resource('phui-info-panel-css'); - - $trs = array(); - $rows = ceil(count($this->infoblock) / $this->columns); - for ($i = 0; $i < $rows; $i++) { - $tds = array(); - $ii = 1; - foreach ($this->infoblock as $key => $cell) { - $tds[] = $this->renderCell($cell); - unset($this->infoblock[$key]); - $ii++; - if ($ii > $this->columns) { - break; - } - } - $trs[] = phutil_tag( - 'tr', - array( - 'class' => 'phui-info-panel-table-row', - ), - $tds); - } - - $table = phutil_tag( - 'table', - array( - 'class' => 'phui-info-panel-table', - ), - $trs); - - $table = id(new PHUIBoxView()) - ->addPadding(PHUI::PADDING_MEDIUM) - ->appendChild($table); - - $progress = null; - if ($this->progress) { - $progress = phutil_tag( - 'div', - array( - 'class' => 'phui-info-panel-progress', - 'style' => 'width: '.(int)$this->progress.'%;', - ), - null); - } - - $box = id(new PHUIObjectBoxView()) - ->setHeader($this->header) - ->appendChild($table) - ->appendChild($progress); - - return phutil_tag( - 'div', - array( - 'class' => 'phui-info-panel', - ), - $box); - } - - private function renderCell($cell) { - $number = phutil_tag( - 'div', - array( - 'class' => 'phui-info-panel-number', - ), - $cell[0]); - - $text = phutil_tag( - 'div', - array( - 'class' => 'phui-info-panel-text', - ), - $cell[1]); - - return phutil_tag( - 'td', - array( - 'class' => 'phui-info-panel-table-cell', - 'align' => 'center', - 'width' => floor(100 / $this->columns).'%', - ), - array( - $number, - $text, - )); - } -} diff --git a/src/view/form/PHUIInfoView.php b/src/view/phui/PHUIInfoView.php similarity index 93% rename from src/view/form/PHUIInfoView.php rename to src/view/phui/PHUIInfoView.php index ca01ff294b..161e49108b 100644 --- a/src/view/form/PHUIInfoView.php +++ b/src/view/phui/PHUIInfoView.php @@ -7,6 +7,7 @@ final class PHUIInfoView extends AphrontTagView { const SEVERITY_NOTICE = 'notice'; const SEVERITY_NODATA = 'nodata'; const SEVERITY_SUCCESS = 'success'; + const SEVERITY_PLAIN = 'plain'; private $title; private $errors; @@ -52,8 +53,15 @@ final class PHUIInfoView extends AphrontTagView { return $this; } - public function setIcon(PHUIIconView $icon) { - $this->icon = $icon; + public function setIcon($icon) { + if ($icon instanceof PHUIIconView) { + $this->icon = $icon; + } else { + $icon = id(new PHUIIconView()) + ->setIcon($icon); + $this->icon = $icon; + } + return $this; } @@ -72,6 +80,7 @@ final class PHUIInfoView extends AphrontTagView { case self::SEVERITY_NOTICE: $icon = 'fa-info-circle'; break; + case self::SEVERITY_PLAIN: case self::SEVERITY_NODATA: return null; break; @@ -87,7 +96,6 @@ final class PHUIInfoView extends AphrontTagView { } public function addButton(PHUIButtonView $button) { - $button->setColor(PHUIButtonView::GREY); $this->buttons[] = $button; return $this; } diff --git a/src/view/phui/PHUILeftRightView.php b/src/view/phui/PHUILeftRightView.php new file mode 100644 index 0000000000..c3873c4899 --- /dev/null +++ b/src/view/phui/PHUILeftRightView.php @@ -0,0 +1,51 @@ +left = $left; + return $this; + } + + public function setRight($right) { + $this->right = $right; + return $this; + } + + public function setVerticalAlign($align) { + $this->verticalAlign = $align; + return $this; + } + + protected function getTagAttributes() { + require_celerity_resource('phui-left-right-css'); + + $classes = array(); + $classes[] = 'phui-left-right-view'; + + if ($this->verticalAlign) { + $classes[] = 'phui-lr-view-'.$this->verticalAlign; + } + + return array('class' => implode(' ', $classes)); + } + + protected function getTagName() { + return 'div'; + } + + protected function getTagContent() { + $left = phutil_tag_div('phui-left-view', $this->left); + $right = phutil_tag_div('phui-right-view', $this->right); + + return phutil_tag_div('phui-lr-container', array($left, $right)); + } +} diff --git a/src/view/phui/PHUIObjectBoxView.php b/src/view/phui/PHUIObjectBoxView.php index 26044ef3a0..f5fdbfffc4 100644 --- a/src/view/phui/PHUIObjectBoxView.php +++ b/src/view/phui/PHUIObjectBoxView.php @@ -35,6 +35,7 @@ final class PHUIObjectBoxView extends AphrontTagView { const BLUE = 'phui-box-blue'; const BLUE_PROPERTY = 'phui-box-blue-property'; + const WHITE_CONFIG = 'phui-box-white-config'; const GREY = 'phui-box-grey'; public function addPropertyList(PHUIPropertyListView $property_list) { @@ -302,7 +303,9 @@ final class PHUIObjectBoxView extends AphrontTagView { $pager = null; if ($this->pager) { - $pager = phutil_tag_div('phui-object-box-pager', $this->pager); + if ($this->pager->willShowPagingControls()) { + $pager = phutil_tag_div('phui-object-box-pager', $this->pager); + } } $content = array( diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index 6d193081d2..070d436cf6 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -19,13 +19,15 @@ final class PHUIObjectItemView extends AphrontTagView { private $headIcons = array(); private $disabled; private $imageURI; + private $imageHref; private $imageIcon; private $titleText; private $badge; private $countdownNum; private $countdownNoun; - private $launchButton; + private $sideColumn; private $coverImage; + private $description; public function setDisabled($disabled) { $this->disabled = $disabled; @@ -126,6 +128,11 @@ final class PHUIObjectItemView extends AphrontTagView { return $this; } + public function setImageHref($image_href) { + $this->imageHref = $image_href; + return $this; + } + public function getImageURI() { return $this->imageURI; } @@ -148,6 +155,11 @@ final class PHUIObjectItemView extends AphrontTagView { return $this; } + public function setDescription($description) { + $this->description = $description; + return $this; + } + public function setEpoch($epoch) { $date = phabricator_datetime($epoch, $this->getUser()); $this->addIcon('none', $date); @@ -217,9 +229,8 @@ final class PHUIObjectItemView extends AphrontTagView { return $this; } - public function setLaunchButton(PHUIButtonView $button) { - $button->setSize(PHUIButtonView::SMALL); - $this->launchButton = $button; + public function setSideColumn($column) { + $this->sideColumn = $column; return $this; } @@ -334,6 +345,23 @@ final class PHUIObjectItemView extends AphrontTagView { ), $this->header); + $description_tag = null; + if ($this->description) { + $decription_id = celerity_generate_unique_node_id(); + $description_tag = id(new PHUITagView()) + ->setIcon('fa-ellipsis-h') + ->addClass('phui-oi-description-tag') + ->setType(PHUITagView::TYPE_SHADE) + ->setColor(PHUITagView::COLOR_GREY) + ->addSigil('jx-toggle-class') + ->setSlimShady(true) + ->setMetaData(array( + 'map' => array( + $decription_id => 'phui-oi-description-reveal', + ), + )); + } + // Wrap the header content in a with the "slippery" sigil. This // prevents us from beginning a drag if you click the text (like "T123"), // but not if you click the white space after the header. @@ -351,6 +379,7 @@ final class PHUIObjectItemView extends AphrontTagView { $this->headIcons, $header_name, $header_link, + $description_tag, ))); $icons = array(); @@ -453,6 +482,16 @@ final class PHUIObjectItemView extends AphrontTagView { $this->subhead); } + if ($this->description) { + $subhead = phutil_tag( + 'div', + array( + 'class' => 'phui-oi-subhead phui-oi-description', + 'id' => $decription_id, + ), + $this->description); + } + if ($icons) { $icons = phutil_tag( 'div', @@ -541,11 +580,12 @@ final class PHUIObjectItemView extends AphrontTagView { $this->getImageIcon()); } - if ($image && $this->href) { + if ($image && (strlen($this->href) || strlen($this->imageHref))) { + $image_href = ($this->imageHref) ? $this->imageHref : $this->href; $image = phutil_tag( 'a', array( - 'href' => $this->href, + 'href' => $image_href, ), $image); } @@ -611,14 +651,15 @@ final class PHUIObjectItemView extends AphrontTagView { )); } - if ($this->launchButton) { + /* Fixed width, right column container. */ + if ($this->sideColumn) { $column2 = phutil_tag( 'div', array( - 'class' => 'phui-oi-col2 phui-oi-launch-button', + 'class' => 'phui-oi-col2 phui-oi-side-column', ), array( - $this->launchButton, + $this->sideColumn, )); } diff --git a/src/view/phui/PHUITagView.php b/src/view/phui/PHUITagView.php index ae80fc1731..292246c4a7 100644 --- a/src/view/phui/PHUITagView.php +++ b/src/view/phui/PHUITagView.php @@ -6,6 +6,7 @@ final class PHUITagView extends AphrontTagView { const TYPE_OBJECT = 'object'; const TYPE_STATE = 'state'; const TYPE_SHADE = 'shade'; + const TYPE_OUTLINE = 'outline'; const COLOR_RED = 'red'; const COLOR_ORANGE = 'orange'; @@ -15,6 +16,8 @@ final class PHUITagView extends AphrontTagView { const COLOR_VIOLET = 'violet'; const COLOR_GREEN = 'green'; const COLOR_BLACK = 'black'; + const COLOR_SKY = 'sky'; + const COLOR_FIRE = 'fire'; const COLOR_GREY = 'grey'; const COLOR_WHITE = 'white'; const COLOR_PINK = 'pink'; @@ -25,10 +28,13 @@ final class PHUITagView extends AphrontTagView { const COLOR_OBJECT = 'object'; const COLOR_PERSON = 'person'; + const BORDER_NONE = 'border-none'; + private $type; private $href; private $name; private $phid; + private $color; private $backgroundColor; private $dotColor; private $closed; @@ -36,11 +42,13 @@ final class PHUITagView extends AphrontTagView { private $icon; private $shade; private $slimShady; + private $border; public function setType($type) { $this->type = $type; switch ($type) { case self::TYPE_SHADE: + case self::TYPE_OUTLINE: break; case self::TYPE_OBJECT: $this->setBackgroundColor(self::COLOR_OBJECT); @@ -52,8 +60,20 @@ final class PHUITagView extends AphrontTagView { return $this; } + /** + * This method has been deprecated, use @{method:setColor} instead. + * + * @deprecated + */ public function setShade($shade) { - $this->shade = $shade; + phlog( + pht('Deprecated call to setShade(), use setColor() instead.')); + $this->color = $shade; + return $this; + } + + public function setColor($color) { + $this->color = $color; return $this; } @@ -87,6 +107,11 @@ final class PHUITagView extends AphrontTagView { return $this; } + public function setBorder($border) { + $this->border = $border; + return $this; + } + public function setIcon($icon) { $this->icon = $icon; return $this; @@ -109,18 +134,26 @@ final class PHUITagView extends AphrontTagView { 'phui-tag-type-'.$this->type, ); - if ($this->shade) { + if ($this->color) { + $classes[] = 'phui-tag-'.$this->color; + } + + if ($this->slimShady) { + $classes[] = 'phui-tag-slim'; + } + + if ($this->type == self::TYPE_SHADE) { $classes[] = 'phui-tag-shade'; - $classes[] = 'phui-tag-shade-'.$this->shade; - if ($this->slimShady) { - $classes[] = 'phui-tag-shade-slim'; - } } if ($this->icon) { $classes[] = 'phui-tag-icon-view'; } + if ($this->border) { + $classes[] = 'phui-tag-'.$this->border; + } + if ($this->phid) { Javelin::initBehavior('phui-hovercards'); @@ -240,6 +273,32 @@ final class PHUITagView extends AphrontTagView { return idx(self::getShadeMap(), $shade, $shade); } + public static function getOutlines() { + return array_keys(self::getOutlineMap()); + } + + public static function getOutlineMap() { + return array( + self::COLOR_RED => pht('Red'), + self::COLOR_ORANGE => pht('Orange'), + self::COLOR_YELLOW => pht('Yellow'), + self::COLOR_BLUE => pht('Blue'), + self::COLOR_INDIGO => pht('Indigo'), + self::COLOR_VIOLET => pht('Violet'), + self::COLOR_GREEN => pht('Green'), + self::COLOR_GREY => pht('Grey'), + self::COLOR_PINK => pht('Pink'), + self::COLOR_SKY => pht('Sky'), + self::COLOR_FIRE => pht('Fire'), + self::COLOR_BLACK => pht('Black'), + self::COLOR_DISABLED => pht('Disabled'), + ); + } + + public static function getOutlineName($outline) { + return idx(self::getOutlineMap(), $outline, $outline); + } + public function setExternal($external) { $this->external = $external; diff --git a/src/view/phui/PHUITimelineEventView.php b/src/view/phui/PHUITimelineEventView.php index 142ea1e87d..51a0ea5aae 100644 --- a/src/view/phui/PHUITimelineEventView.php +++ b/src/view/phui/PHUITimelineEventView.php @@ -28,6 +28,7 @@ final class PHUITimelineEventView extends AphrontView { private $hideCommentOptions = false; private $authorPHID; private $badges = array(); + private $pinboardItems = array(); public function setAuthorPHID($author_phid) { $this->authorPHID = $author_phid; @@ -190,6 +191,11 @@ final class PHUITimelineEventView extends AphrontView { return $this->hideCommentOptions; } + public function addPinboardItem(PHUIPinboardItemView $item) { + $this->pinboardItems[] = $item; + return $this; + } + public function setToken($token, $removed = false) { $this->token = $token; $this->tokenRemoved = $removed; @@ -441,12 +447,21 @@ final class PHUITimelineEventView extends AphrontView { ), $content); + // Image Events + $pinboard = null; + if ($this->pinboardItems) { + $pinboard = new PHUIPinboardView(); + foreach ($this->pinboardItems as $item) { + $pinboard->addItem($item); + } + } + $content = phutil_tag( 'div', array( 'class' => implode(' ', $content_classes), ), - array($image, $badges, $wedge, $content)); + array($image, $badges, $wedge, $content, $pinboard)); $outer_classes = $this->classes; $outer_classes[] = 'phui-timeline-shell'; diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php index 1c56886f7a..9240887f4b 100644 --- a/src/view/phui/PHUITwoColumnView.php +++ b/src/view/phui/PHUITwoColumnView.php @@ -6,10 +6,11 @@ final class PHUITwoColumnView extends AphrontTagView { private $sideColumn = null; private $navigation; private $display; - private $fluid; + private $fixed; private $header; private $subheader; private $footer; + private $tabs; private $propertySection = array(); private $curtain; @@ -42,6 +43,12 @@ final class PHUITwoColumnView extends AphrontTagView { return $this; } + public function setTabs(PHUIListView $tabs) { + $tabs->setType(PHUIListView::TABBAR_LIST); + $this->tabs = $tabs; + return $this; + } + public function setFooter($footer) { $this->footer = $footer; return $this; @@ -64,8 +71,8 @@ final class PHUITwoColumnView extends AphrontTagView { return $this->curtain; } - public function setFluid($fluid) { - $this->fluid = $fluid; + public function setFixed($fixed) { + $this->fixed = $fixed; return $this; } @@ -87,14 +94,22 @@ final class PHUITwoColumnView extends AphrontTagView { $classes[] = 'phui-two-column-view'; $classes[] = $this->getDisplay(); - if ($this->fluid) { - $classes[] = 'phui-two-column-fluid'; + if ($this->fixed) { + $classes[] = 'phui-two-column-fixed'; + } + + if ($this->tabs) { + $classes[] = 'with-tabs'; } if ($this->subheader) { $classes[] = 'with-subheader'; } + if (!$this->header) { + $classes[] = 'without-header'; + } + return array( 'class' => implode(' ', $classes), ); @@ -124,6 +139,12 @@ final class PHUITwoColumnView extends AphrontTagView { 'phui-two-column-header', $this->header); } + $tabs = null; + if ($this->tabs) { + $tabs = phutil_tag_div( + 'phui-two-column-tabs', $this->tabs); + } + $subheader = null; if ($this->subheader) { $subheader = phutil_tag_div( @@ -137,6 +158,7 @@ final class PHUITwoColumnView extends AphrontTagView { ), array( $header, + $tabs, $subheader, $table, $footer, diff --git a/support/aphlict/server/lib/AphlictClientServer.js b/support/aphlict/server/lib/AphlictClientServer.js index 0d23d4f4f1..989b3f1db1 100644 --- a/support/aphlict/server/lib/AphlictClientServer.js +++ b/support/aphlict/server/lib/AphlictClientServer.js @@ -92,8 +92,14 @@ JX.install('AphlictClientServer', { var server = this._server.listen.apply(this._server, arguments); var wss = new WebSocket.Server({server: server}); - wss.on('connection', function(ws) { - var path = url.parse(ws.upgradeReq.url).pathname; + // This function checks for upgradeReq which is only available in + // ws2 by default, not ws3. See T12755 for more information. + wss.on('connection', function(ws, request) { + if ('upgradeReq' in ws) { + request = ws.upgradeReq; + } + + var path = url.parse(request.url).pathname; var instance = self._parseInstanceFromPath(path); var listener = self.getListenerList(instance).addListener(ws); diff --git a/webroot/rsrc/css/aphront/dark-console.css b/webroot/rsrc/css/aphront/dark-console.css index e1ddc5a16d..81e888ba46 100644 --- a/webroot/rsrc/css/aphront/dark-console.css +++ b/webroot/rsrc/css/aphront/dark-console.css @@ -3,12 +3,12 @@ */ .dark-console { - background: #444444; - color: #eeeeee; - width: 100%; - font-family: "Verdana"; - font-size: 11px; - position: relative; + background: #444444; + color: #eeeeee; + width: 100%; + font-family: "Verdana"; + font-size: 11px; + position: relative; } .dark-console a { @@ -74,6 +74,7 @@ .dark-console-requests a.dark-console-request:hover, .dark-console-tabs a.dark-console-tab:hover { background: #1188cc; + text-decoration: none; } .dark-console-tabs a.dark-console-tab { @@ -107,6 +108,12 @@ .dark-console .aphront-table-view td { font-size: 11px; + color: #eee; +} + +.dark-console .aphront-table-view tr:hover, +.dark-console .aphront-table-view tr.alt:hover { + background: #333; } .dark-console .aphront-table-view td.header { diff --git a/webroot/rsrc/css/aphront/dialog-view.css b/webroot/rsrc/css/aphront/dialog-view.css index 0e6f6d4cfc..153685548e 100644 --- a/webroot/rsrc/css/aphront/dialog-view.css +++ b/webroot/rsrc/css/aphront/dialog-view.css @@ -3,11 +3,11 @@ */ .aphront-dialog-view { - width: 560px; + width: 580px; margin: 32px auto 16px; border: 1px solid {$lightblueborder}; border-radius: 3px; - background-color: #fff; + background-color: {$page.content}; } .jx-client-dialog .aphront-dialog-view { @@ -32,7 +32,7 @@ } .aphront-dialog-view-width-form { - width: 640px; + width: 820px; } .aphront-dialog-view-width-full { @@ -41,7 +41,7 @@ } .aphront-dialog-body { - background: #fff; + background: {$page.content}; padding: 16px; border: none; } diff --git a/webroot/rsrc/css/aphront/list-filter-view.css b/webroot/rsrc/css/aphront/list-filter-view.css index 50d43f2383..d16ad72eb4 100644 --- a/webroot/rsrc/css/aphront/list-filter-view.css +++ b/webroot/rsrc/css/aphront/list-filter-view.css @@ -5,7 +5,7 @@ .aphront-list-filter-wrap { border: 1px solid {$lightblueborder}; margin: 0 16px; - background: #fff; + background: {$page.content}; border-radius: 3px; } diff --git a/webroot/rsrc/css/aphront/notification.css b/webroot/rsrc/css/aphront/notification.css index ee835bba76..306075b582 100644 --- a/webroot/rsrc/css/aphront/notification.css +++ b/webroot/rsrc/css/aphront/notification.css @@ -11,7 +11,7 @@ .jx-notification { width: 240px; - padding: 8px 16px; + padding: 8px 12px; font-size: 11px; overflow: hidden; diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index 2de08095dd..1a7d2eb215 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -4,12 +4,13 @@ .aphront-table-wrap { overflow-x: auto; + -webkit-overflow-scrolling: touch; } .aphront-table-view { width: 100%; border-collapse: collapse; - background: #fff; + background: {$page.content}; border: 1px solid {$lightblueborder}; border-bottom: 1px solid {$blueborder}; } @@ -24,7 +25,6 @@ .aphront-table-notice { padding: 12px 16px; - font-size: {$normalfontsize}; color: {$darkbluetext}; border-bottom: 1px solid {$thinblueborder}; } @@ -37,9 +37,16 @@ background: {$lightgreybackground}; } +.device-desktop .aphront-table-view tr:hover { + background: {$bluebackground}; +} + +.device-desktop .aphront-table-view tr.no-data:hover { + background: inherit; +} + .aphront-table-view th { font-weight: bold; - font-size: {$normalfontsize}; white-space: nowrap; color: {$bluetext}; text-shadow: 0 1px 0 white; @@ -78,6 +85,7 @@ th.aphront-table-view-sortable-selected { .aphront-table-view td { white-space: nowrap; vertical-align: middle; + color: {$darkbluetext}; } .aphront-table-down-sort { @@ -115,35 +123,17 @@ th.aphront-table-view-sortable-selected { .aphront-table-view th { padding: 8px 10px; - font-size: {$normalfontsize}; } .aphront-table-view td { padding: 8px 10px; - font-size: {$smallerfontsize}; -} - -.device-tablet .aphront-table-view td, -.device-phone .aphront-table-view td { - padding: 6px; -} - -.device-tablet .aphront-table-view td + td, -.device-phone .aphront-table-view td + td { - padding-left: 0px; } .device-tablet .aphront-table-view th, .device-phone .aphront-table-view th { - padding: 6px; overflow: hidden; } -.device-tablet .aphront-table-view th + th, -.device-phone .aphront-table-view th + th { - padding-left: 0px; -} - .aphront-table-view td.sorted-column { background: {$lightbluebackground}; } @@ -196,7 +186,7 @@ th.aphront-table-view-sortable-selected { } .aphront-table-view td.wrap { - white-space: normal; + white-space: normal; } .aphront-table-view td.prewrap { @@ -282,7 +272,6 @@ span.single-display-line-content { text-align: center; color: {$lightgreytext}; font-style: italic; - font-size: {$normalfontsize}; } .aphront-table-view td.thumb img { diff --git a/webroot/rsrc/css/aphront/tokenizer.css b/webroot/rsrc/css/aphront/tokenizer.css index 7116ce3ace..7cfc9e391b 100644 --- a/webroot/rsrc/css/aphront/tokenizer.css +++ b/webroot/rsrc/css/aphront/tokenizer.css @@ -88,11 +88,11 @@ a.jx-tokenizer-token:hover { a.jx-tokenizer-token-function { border-color: {$sh-lightgreyborder}; - background: #fff; + background: {$page.content}; } a.jx-tokenizer-token-function:hover { - background: #fff; + background: {$page.content}; } a.jx-tokenizer-token-disabled { @@ -181,4 +181,5 @@ a.jx-tokenizer-token-invalid:hover { .button.tokenizer-browse-button .phui-icon-view { top: 7px; left: 9px; + position: absolute; } diff --git a/webroot/rsrc/css/aphront/typeahead-browse.css b/webroot/rsrc/css/aphront/typeahead-browse.css index 625354bb71..8746d9db4e 100644 --- a/webroot/rsrc/css/aphront/typeahead-browse.css +++ b/webroot/rsrc/css/aphront/typeahead-browse.css @@ -11,7 +11,7 @@ } .typeahead-browse-more { - background: {$lightblue}; + background: {$lightbluebackground}; border: 1px solid {$lightblueborder}; color: {$blue}; } @@ -64,19 +64,3 @@ input.typeahead-browse-input { margin-top: 1px; margin-left: 6px; } - -.typeahead-browse-item .phabricator-main-search-typeahead-result { - margin: 2px 0; - padding: 0 8px; -} - -.typeahead-browse-item .phabricator-main-search-typeahead-result.has-image { - padding-left: 48px; -} - -.typeahead-browse-item - .phabricator-main-search-typeahead-result.result-closed - .result-name { - text-decoration: line-through; - color: {$lightgreytext}; -} diff --git a/webroot/rsrc/css/aphront/typeahead.css b/webroot/rsrc/css/aphront/typeahead.css index bdda2703e0..05fec1e70c 100644 --- a/webroot/rsrc/css/aphront/typeahead.css +++ b/webroot/rsrc/css/aphront/typeahead.css @@ -9,10 +9,10 @@ div.jx-typeahead-hardpoint { div.jx-typeahead-results { position: absolute; - border: 1px solid {$hoverborder}; + border: 1px solid {$thinblueborder}; border-top: 0px; padding: 0; - background: #fefefe; + background: {$page.content}; width: 98%; box-shadow: 0px 1px 2px rgba({$alphablack}, 0.2); margin: -1px 1% 0; @@ -28,10 +28,13 @@ div.jx-typeahead-results a.jx-result { color: {$darkgreytext}; display: block; font-size: {$normalfontsize}; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } div.jx-typeahead-results a.jx-result + a.jx-result { - border-top: 1px solid {$hoverborder}; + border-top: 1px solid {$thinblueborder}; } div.jx-typeahead-results a.jx-result:hover, @@ -68,7 +71,8 @@ div.jx-typeahead-results a.diffusion-locate-file { } .diffusion-locate-file strong { - color: {$blue}; + color: {$fire}; + text-decoration: underline; } .diffusion-locate-file .phui-icon-view { diff --git a/webroot/rsrc/css/application/base/main-menu-view.css b/webroot/rsrc/css/application/base/main-menu-view.css index 63ac3719fa..9a4fe28d7a 100644 --- a/webroot/rsrc/css/application/base/main-menu-view.css +++ b/webroot/rsrc/css/application/base/main-menu-view.css @@ -164,9 +164,9 @@ position: absolute; font-size: {$normalfontsize}; border: none; - background-color: #fff; + background-color: {$page.content}; height: 28px; - padding: 3px 28px 3px 52px; + padding: 3px 28px 3px 48px; float: left; width: 280px; } @@ -180,7 +180,7 @@ } .phabricator-main-menu .phabricator-main-menu-search input:focus { - background: #fff; + background: {$page.content}; opacity: 1; color: {$darkbluetext}; box-shadow: none; @@ -212,7 +212,7 @@ position: absolute; right: auto; left: 12px; - width: 46px; + width: 40px; background: {$greybackground}; z-index: 1; } @@ -247,11 +247,12 @@ a.phabricator-core-user-menu .caret:before { font-size: 15px; top: 4px; left: 8px; + position: absolute; } .phabricator-main-menu-search-dropdown .caret { position: absolute; - right: 18px; + right: 20px; top: 2px; border: none; margin-top: 1px; @@ -269,7 +270,7 @@ a.phabricator-core-user-menu .caret:before { } .phabricator-main-menu-search-target div.jx-typeahead-results { - background: #fff; + background: {$page.content}; word-wrap: break-word; overflow-y: auto; box-shadow: {$dropshadow}; @@ -309,6 +310,22 @@ a.phabricator-core-user-menu .caret:before { color: {$darkgreytext}; } +.phabricator-main-search-typeahead-result.result-closed { + opacity: .8; + -webkit-filter: grayscale(100%); + filter: grayscale(100%); +} + +.phabricator-main-search-typeahead-result.result-closed + .result-name { + text-decoration: line-through; + color: {$lightgreytext}; +} + +.phabricator-main-search-typeahead-result.has-image { + padding-left: 48px; +} + .phabricator-main-search-typeahead-result .result-type { color: {$lightgreytext}; font-size: {$smallestfontsize}; diff --git a/webroot/rsrc/css/application/base/notification-menu.css b/webroot/rsrc/css/application/base/notification-menu.css index 02a3d5a170..8e2391d57c 100644 --- a/webroot/rsrc/css/application/base/notification-menu.css +++ b/webroot/rsrc/css/application/base/notification-menu.css @@ -3,8 +3,9 @@ */ .phabricator-notification-menu { - background: #fff; - font-size: {$smallestfontsize}; + background: {$page.content}; + font-size: {$smallerfontsize}; + line-height: 18px; word-wrap: break-word; overflow-y: auto; box-shadow: {$dropshadow}; @@ -12,9 +13,8 @@ border-radius: 3px; } -.phabricator-notification .phabricator-notification-date { - margin-left: 8px; - color: {$lightgreytext}; +.phabricator-notification { + padding: 8px 12px; } .phabricator-notification-menu-loading { @@ -44,11 +44,7 @@ } .phabricator-notification-list .phabricator-notification { - padding: 10px 4px; -} - -.phabricator-notification { - padding: 6px 8px; + padding: 8px; } .phabricator-notification-menu .phabricator-notification { @@ -56,7 +52,7 @@ } .device-desktop .phabricator-notification-menu .phabricator-notification:hover { - background: {$hoverblue}; + background: {$lightgreybackground}; } .device-desktop .phabricator-notification-menu @@ -65,7 +61,7 @@ } .phabricator-notification + .phabricator-notification { - border-top: 1px solid {$hoverborder}; + border-top: 1px solid {$thinblueborder}; } .no-notifications { @@ -81,9 +77,34 @@ color: {$lightgreytext}; } +.phabricator-notification-foot { + color: {$lightgreytext}; + font-size: {$smallestfontsize}; + line-height: 18px; + position: relative; +} + +.phabricator-notification-unread .phabricator-notification-foot { + padding-left: 10px; +} + +.phabricator-notification-foot .phabricator-notification-status { + display: none; +} + +.phabricator-notification-unread .phabricator-notification-foot + .phabricator-notification-status { + font-size: 7px; + color: {$lightgreytext}; + position: absolute; + display: inline-block; + top: 6px; + left: 0; +} + .phabricator-notification-header { font-weight: bold; - padding: 10px 8px; + padding: 10px 12px; font-size: {$smallerfontsize}; border-bottom: 1px solid {$thinblueborder}; } diff --git a/webroot/rsrc/css/application/base/standard-page-view.css b/webroot/rsrc/css/application/base/standard-page-view.css index 1205dc93e7..755ed1d59c 100644 --- a/webroot/rsrc/css/application/base/standard-page-view.css +++ b/webroot/rsrc/css/application/base/standard-page-view.css @@ -18,7 +18,7 @@ } body.white-background { - background: #fff; + background: {$page.content}; } .phabricator-standard-page-footer { @@ -176,3 +176,29 @@ a.handle-status-closed:hover { position: absolute; left: -50px; } + +.phabricator-standard-page-tabs { + padding: 0 32px; + margin-bottom: 32px; + background: {$page.content}; + box-shadow: 0 0 3px 0 rgba(0,0,0,0.2); +} + +.device .phabricator-standard-page-tabs { + margin-bottom: 20px; + padding: 0 12px; +} + +.device-phone .phabricator-standard-page-tabs { + text-align: center; +} + +.device-phone + .phabricator-standard-page-tabs.phui-list-view.phui-list-tabbar > li { + display: inline-block; + float: none; +} + +.phabricator-standard-page-tabs.phui-list-tabbar .phui-list-item-href { + padding: 12px 24px; +} diff --git a/webroot/rsrc/css/application/config/config-options.css b/webroot/rsrc/css/application/config/config-options.css index 67c005094c..0c80e31b5e 100644 --- a/webroot/rsrc/css/application/config/config-options.css +++ b/webroot/rsrc/css/application/config/config-options.css @@ -5,20 +5,20 @@ .config-option-table { width: 100%; border-collapse: collapse; - border: 1px solid {$lightgreyborder}; - background: #fff; + border: none; + background: {$page.content}; } .config-option-table th, .config-option-table td { - padding: 4px 12px; - border: 1px solid {$lightgreyborder}; + padding: 8px 12px; + border-bottom: 1px solid {$thinblueborder}; } .config-option-table th { background: {$lightgreybackground}; color: {$bluetext}; - text-align: right; + text-align: left; white-space: nowrap; } @@ -37,20 +37,22 @@ .config-option-table .column-labels th { font-weight: bold; color: {$bluetext}; - text-align: center; - background: {$greybackground}; + background: {$lightgreybackground}; + border-right: 1px solid {$thinblueborder}; } .config-options-current-value { - padding: 0 8px 6px; - white-space: pre-wrap; + white-space: nowrap; + width: 200px; + text-overflow: ellipsis; + overflow: hidden; } -.config-options-current-value span { - color: {$greytext}; +.config-options-current-value.violet { + color: {$violet}; } .config-options-effective-value, .config-option-table .config-options-effective-value th { - background: {$lightyellow}; + background: {$gentle.highlight}; } diff --git a/webroot/rsrc/css/application/config/config-page.css b/webroot/rsrc/css/application/config/config-page.css deleted file mode 100644 index 82cb9642d2..0000000000 --- a/webroot/rsrc/css/application/config/config-page.css +++ /dev/null @@ -1,77 +0,0 @@ -/** - * @provides config-page-css - */ - -.config-page-header { - margin: 28px 24px 0; - padding-bottom: 28px; - border-bottom: 1px solid {$thinblueborder}; -} - -.device-phone .config-page-header { - margin: 4px 12px 0; - padding-bottom: 4px; -} - -.config-page-header .phui-profile-header { - padding: 0; -} - -.device-phone .config-page-header .phui-profile-header { - padding-left: 4px; - padding-right: 4px; -} - -.config-page-header .phui-profile-header.phui-header-shell .phui-header-header { - font-size: 20px; -} - -.device-phone .config-page-header .phui-profile-header.phui-header-shell - .phui-header-header { - font-size: 16px; -} - -.config-page-content { - margin: 0 24px; -} - -.device-phone .config-page-content { - margin: 0 4px; -} - -.device-desktop .config-page-content .phui-oi-list-view { - padding-left: 0; - padding-right: 0; -} - -.device-desktop .config-page-content .phui-document-fluid .phui-document-view { - padding: 16px 0; - margin: 0; -} - -.config-page-content .aphront-table-view { - border: none; -} - -.config-page-property { - padding-top: 4px; - border-bottom: 1px solid {$thinblueborder}; -} - -.config-page-content .aphront-table-notice { - padding: 0; -} - -.config-page-content .aphront-table-notice .phui-info-view { - margin-left: 0; - margin-right: 0; -} - -.config-page-content .aphront-table-wrap + .aphront-table-wrap { - margin-top: 20px; - border-top: 1px solid {$thinblueborder}; -} - -.config-page-content .phui-box.phui-box-blue-property { - margin-top: 16px; -} diff --git a/webroot/rsrc/css/application/config/setup-issue.css b/webroot/rsrc/css/application/config/setup-issue.css index ae54394b4c..7ba033d9c2 100644 --- a/webroot/rsrc/css/application/config/setup-issue.css +++ b/webroot/rsrc/css/application/config/setup-issue.css @@ -6,15 +6,15 @@ } .setup-issue-shell { - max-width: 760px; + max-width: 880px; margin: 16px auto; } .setup-issue { background: #fff; - border: 1px solid #BFCFDA; - padding: 8px; - border-radius: 3px; + border: 1px solid #e4e5e6; + padding: 0; + border-radius: 5px; } .setup-issue p { @@ -24,29 +24,30 @@ .setup-issue table { width: 100%; border-collapse: collapse; - border: 1px solid #BFCFDA; + border: 1px solid #e2e2e2; } .setup-issue table th { text-align: right; width: 30%; background: #F8F9FC; - border: 1px solid #BFCFDA; + border: 1px solid #e2e2e2; padding: 8px; } .setup-issue table td { - border: 1px solid #BFCFDA; + border: 1px solid #e2e2e2; padding: 8px; word-break: break-word; } .setup-issue pre { width: auto; - border: 1px solid #BFCFDA; - padding: 10px 2%; + border: 1px solid #e2e2e2; + padding: 12px; background: #F8F9FC; overflow-x: auto; + border-radius: 3px; } .setup-issue tt { @@ -60,51 +61,51 @@ } .setup-issue-instructions { - font-size: 14px; - padding: 12px 0; + font-size: 13px; + padding: 16px; line-height: 20px; - background: #fff; - border-bottom: 1px solid #BFCFDA; - margin: 0 12px; + background: rgba(71,87,120,0.08); + border-radius: 3px; } .setup-fatal .setup-issue-instructions { - color: #c0392b; + color: #af1404; background: #f4dddb; - padding: 12px; - margin: 0 0 12px; - border-bottom: 1px solid #c0392b; } .setup-issue-name { - color: #464C5C; - padding: 4px 8px 12px; - border-bottom: 1px solid #BFCFDA; font-size: 15px; font-weight: bold; + color: #6B748C; + border-bottom: 1px solid #e2e2e2; + overflow: hidden; + padding: 16px; + line-height: 24px; } -.setup-issue-tail { - margin-top: 12px; +.setup-issue-body { + padding: 16px 16px 0 16px; } .setup-issue-status { - margin: 12px 4px 0; + margin: 0 0 16px 0; padding: 12px; background: #FDF5D4; - color: #bc7837; - border: 1px solid #bc7837; + color: #ab8206; border-radius: 3px; } .setup-issue-actions { - padding: 8px 12px; - border-top: 1px solid #dfdfdf; - background-color: #f7f7f7; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; + padding: 12px; + background-color: #f5f5f5; + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; overflow: hidden; - margin: 0 -8px -8px -8px; + text-align: right; +} + +.setup-issue-actions .button { + margin-left: 8px; } .setup-issue-next { @@ -112,14 +113,17 @@ border: 1px solid #BFCFDA; background: #daeaf3; text-align: center; - font-size: 16px; margin: 12px 0; color: #2980b9; border-radius: 3px; } .setup-issue-config { - padding: 8px 12px; + padding: 12px 0; +} + +.setup-issue-config + .setup-issue-config { + padding-top: 0; } .setup-issue ul { @@ -134,3 +138,12 @@ text-align: right; color: #74777D; } + +.phui-two-column-view .setup-issue-background { + padding: 0; +} + +.phui-two-column-view .setup-issue-shell { + width: auto; + margin: 0; +} diff --git a/webroot/rsrc/css/application/conpherence/durable-column.css b/webroot/rsrc/css/application/conpherence/durable-column.css index fdbdbe848f..41645717f3 100644 --- a/webroot/rsrc/css/application/conpherence/durable-column.css +++ b/webroot/rsrc/css/application/conpherence/durable-column.css @@ -12,7 +12,7 @@ right: 16px; width: 400px; height: 360px; - background: #fff; + background: {$page.content}; border-top-right-radius: 3px; border-top-left-radius: 3px; box-shadow: 0px 1px 8px rgba(55,55,55, .3); diff --git a/webroot/rsrc/css/application/conpherence/header-pane.css b/webroot/rsrc/css/application/conpherence/header-pane.css index 54f58682da..3f5965def5 100644 --- a/webroot/rsrc/css/application/conpherence/header-pane.css +++ b/webroot/rsrc/css/application/conpherence/header-pane.css @@ -9,7 +9,7 @@ .conpherence-header-pane .phui-header-header { font-size: 16px; - color: #000; + color: {$blacktext}; display: block; } diff --git a/webroot/rsrc/css/application/conpherence/menu.css b/webroot/rsrc/css/application/conpherence/menu.css index b2702d94fd..feb17156d7 100644 --- a/webroot/rsrc/css/application/conpherence/menu.css +++ b/webroot/rsrc/css/application/conpherence/menu.css @@ -8,7 +8,7 @@ left: 0; right: 0; top: 44px; - background: #fff; + background: {$page.content}; } .conpherence-menu-pane { @@ -137,7 +137,6 @@ font-weight: bold; font-size: {$normalfontsize}; color: {$darkbluetext}; - text-shadow: 0px 1px 1px #fff; overflow: hidden; width: 165px; text-overflow: ellipsis; diff --git a/webroot/rsrc/css/application/conpherence/message-pane.css b/webroot/rsrc/css/application/conpherence/message-pane.css index ab3c55ba22..0dc33789f3 100644 --- a/webroot/rsrc/css/application/conpherence/message-pane.css +++ b/webroot/rsrc/css/application/conpherence/message-pane.css @@ -25,7 +25,7 @@ .conpherence-layout .conpherence-content-pane .conpherence-no-threads { top: 44px; right: 0; - background: #fff; + background: {$page.content}; z-index: 26; } @@ -411,7 +411,7 @@ body .conpherence-message-pane .aphront-form-control { bottom: 0; transition: all 0.3s; position: fixed; - background-color: #fff; + background-color: {$page.content}; } .conpherence-layout.loading .conpherence-loading-mask { @@ -445,7 +445,7 @@ body .conpherence-message-pane .aphront-form-control { .show-searchbar .conpherence-search-form-view { display: block; height: 54px; - background: #fff; + background: {$page.content}; position: absolute; top: 0; left: 0; @@ -461,7 +461,7 @@ body .conpherence-message-pane .aphront-form-control { .conpherence-search-results { position: absolute; - background: #fff; + background: {$page.content}; top: 54px; left: 0; right: 0; diff --git a/webroot/rsrc/css/application/conpherence/participant-pane.css b/webroot/rsrc/css/application/conpherence/participant-pane.css index f219707e7b..479bf95637 100644 --- a/webroot/rsrc/css/application/conpherence/participant-pane.css +++ b/webroot/rsrc/css/application/conpherence/participant-pane.css @@ -65,7 +65,7 @@ } .conpherence-participant-pane .person-entry:hover a { - color: #000; + color: {$blacktext}; } .conpherence-participant-pane .person-entry a img { @@ -99,7 +99,7 @@ } .conpherence-participant-pane .person-entry .remove:hover .close-icon { - color: #000; + color: {$blacktext}; } /****** Hide Widgets **********************************************************/ diff --git a/webroot/rsrc/css/application/conpherence/transaction.css b/webroot/rsrc/css/application/conpherence/transaction.css index 6e52a8c30c..42f39bac74 100644 --- a/webroot/rsrc/css/application/conpherence/transaction.css +++ b/webroot/rsrc/css/application/conpherence/transaction.css @@ -13,7 +13,7 @@ .conpherence-transaction-header .phui-link-person { font-weight: bold; font-size: {$biggerfontsize}; - color: #000; + color: {$blacktext}; } .conpherence-transaction-view.date-marker { @@ -23,7 +23,7 @@ .conpherence-transaction-view.date-marker .date { position: relative; top: -11px; - background-color: #fff; + background-color: {$page.content}; color: {$sh-violettext}; font-weight: bold; } diff --git a/webroot/rsrc/css/application/dashboard/dashboard.css b/webroot/rsrc/css/application/dashboard/dashboard.css index 3def453670..aed87b7673 100644 --- a/webroot/rsrc/css/application/dashboard/dashboard.css +++ b/webroot/rsrc/css/application/dashboard/dashboard.css @@ -20,7 +20,7 @@ } .dashboard-box .phui-header-header { - color: #000; + color: {$blacktext}; } .dashboard-view .phui-oi-empty .phui-info-view { diff --git a/webroot/rsrc/css/application/diff/inline-comment-summary.css b/webroot/rsrc/css/application/diff/inline-comment-summary.css index 572441191d..554fcdd518 100644 --- a/webroot/rsrc/css/application/diff/inline-comment-summary.css +++ b/webroot/rsrc/css/application/diff/inline-comment-summary.css @@ -16,8 +16,7 @@ .phabricator-inline-summary-table .inline-comment-summary-table-header { font-weight: bold; padding: 16px 1px 8px; - background: #fff; - color: #000; + color: {$blacktext}; border-bottom: 1px solid {$thinblueborder}; } @@ -25,14 +24,14 @@ padding: 4px 8px; white-space: nowrap; color: {$darkbluetext}; - background: white; + background: {$page.content}; } .phabricator-inline-summary-table td.inline-line-number { padding: 0; width: 100px; white-space: nowrap; - background: #F8F9FC; + background: {$lightgreybackground}; font-family: "Menlo", "Consolas", monospace; font-size: {$smallestfontsize}; color: {$bluetext}; diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 1bd7d48635..194561aa7a 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -20,7 +20,7 @@ } .differential-diff { - background: #fff; + background: {$diff.background}; width: 100%; border-top: 1px solid {$lightblueborder}; border-bottom: 1px solid {$lightblueborder}; @@ -67,10 +67,6 @@ padding: 1px 4px; } -.device .differential-diff .inline > td { - padding: 4px; -} - .differential-diff td .zwsp { position: absolute; width: 0; @@ -113,22 +109,10 @@ color: {$darkgreytext}; } -.differential-diff th.selected { - background-color: {$sh-yellowbackground}; -} - .differential-changeset-immutable .differential-diff th { cursor: auto; } -.differential-diff th.old { - border-right-color: {$old-bright}; -} - -.differential-diff th.new { - border-right-color: {$new-bright}; -} - .differential-diff td.old { background: {$old-background}; } @@ -282,9 +266,9 @@ td.cov-I { } .differential-meta-notice { - border-top: 1px solid {$sh-lightyellowborder}; - border-bottom: 1px solid {$sh-lightyellowborder}; - background-color: {$sh-yellowbackground}; + border-top: 1px solid {$gentle.highlight.border}; + border-bottom: 1px solid {$gentle.highlight.border}; + background-color: {$gentle.highlight}; padding: 12px; } @@ -296,7 +280,7 @@ td.cov-I { font-size: {$biggestfontsize}; padding: 2px 0 20px 12px; line-height: 20px; - color: #000; + color: {$blacktext}; } .device-phone .differential-changeset h1 { @@ -312,16 +296,17 @@ td.cov-I { top: 0; left: 0; box-sizing: border-box; + pointer-events: none; } .differential-diff .inline > td { - padding: 8px 12px; + padding: 0; } .differential-loading { - border-top: 1px solid {$yellow}; - border-bottom: 1px solid {$yellow}; - background-color: {$lightyellow}; + border-top: 1px solid {$gentle.highlight.border}; + border-bottom: 1px solid {$gentle.highlight.border}; + background-color: {$gentle.highlight}; padding: 12px; text-align: center; } @@ -332,6 +317,7 @@ td.cov-I { border: 1px solid {$blue}; text-align: center; background-color: {$lightblue}; + margin: 8px; } .differential-collapse-undo a { @@ -392,3 +378,36 @@ tr.differential-inline-loading { .differential-review-stage { position: relative; } + +.diff-banner { + position: fixed; + top: 0; + left: 0; + right: 0; + background: {$page.content}; + box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1); + border-bottom: 1px solid {$lightgreyborder}; + padding: 8px 18px; + vertical-align: middle; + font-weight: bold; + font-size: {$biggerfontsize}; + line-height: 28px; +} + +.diff-banner-path { + color: {$greytext}; +} + +.diff-banner-buttons .button { + margin-left: 8px; +} + +.diff-banner-has-unsaved, +.diff-banner-has-unsubmitted, +.diff-banner-has-draft-done { + background: {$gentle.highlight}; +} + +.diff-banner-buttons { + float: right; +} diff --git a/webroot/rsrc/css/application/differential/phui-inline-comment.css b/webroot/rsrc/css/application/differential/phui-inline-comment.css index f93b2c8d6c..2738a64749 100644 --- a/webroot/rsrc/css/application/differential/phui-inline-comment.css +++ b/webroot/rsrc/css/application/differential/phui-inline-comment.css @@ -25,17 +25,20 @@ .differential-inline-comment, .differential-inline-comment-edit { - background: #fff; - border: 1px solid {$sh-yellowborder}; + background: {$page.content}; + border: 1px solid {$gentle.highlight.border}; font: {$basefont}; - margin: 0; - width: 100%; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; overflow: hidden; white-space: normal; border-radius: 3px; + margin: 8px 12px; +} + +.device .differential-inline-comment { + margin: 4px; } .inline-state-is-draft { @@ -45,9 +48,9 @@ .differential-inline-comment-head { font-weight: bold; color: {$darkbluetext}; - border-bottom: 1px solid {$sh-lightyellowborder}; + border-bottom: 1px solid {$gentle.highlight.border}; padding: 4px 5px 4px 8px; - background-color: {$sh-yellowbackground}; + background-color: {$gentle.highlight}; } .differential-inline-comment-content { @@ -61,7 +64,7 @@ /* Tighten up spacing on replies */ .differential-inline-comment.inline-comment-is-reply { - margin-top: -12px; + margin-top: 0; } .differential-inline-comment .inline-head-right { @@ -206,8 +209,8 @@ */ .differential-inline-comment .differential-inline-done-label { - border-color: {$sh-yellowborder}; - color: {$sh-yellowicon}; + border-color: {$gentle.highlight.border}; + color: {$bluetext}; } .differential-inline-comment.inline-state-is-draft @@ -241,7 +244,7 @@ .differential-inline-comment.inline-is-done .differential-inline-done-label { - background-color: #fff; + background-color: {$page.content}; border-color: {$lightblueborder}; color: {$sky}; opacity: 1; @@ -249,7 +252,7 @@ .device-desktop .differential-inline-comment.inline-is-done .differential-inline-done-label:hover { - background-color: #fff; + background-color: {$page.content}; color: {$sky}; } @@ -270,7 +273,7 @@ } .differential-inline-comment.inline-is-done - .differential-inline-comment-head { + .differential-inline-comment-head { background-color: {$lightgreybackground}; border-bottom-color: {$lightgreyborder}; } @@ -284,7 +287,7 @@ .differential-inline-comment.inline-is-done .differential-inline-comment-head .differential-inline-done-label { color: {$sky}; - background-color: #fff; + background-color: {$page.content}; border-color: {$sky}; } @@ -315,10 +318,10 @@ .differential-inline-undo { padding: 8px; + margin: 4px 12px; text-align: center; background: {$sh-yellowbackground}; border: 1px solid {$sh-yellowborder}; - margin: 4px 0; color: {$darkgreytext}; font: {$basefont}; font-size: {$normalfontsize}; @@ -374,17 +377,45 @@ /* - Hiding Inlines ------------------------------------------------------------ */ -.reveal-inlines { - float: left; - margin-left: 4px; +.reveal-inline { + color: {$lightbluetext}; + margin: 4px 0; + display: none; +} + +.inline-hidden .reveal-inline { + display: block; +} + +.inline-hidden .differential-inline-comment { + display: none; +} + +.differential-inline-summary { + background: {$lightgreybackground}; + padding: 2px 16px; + color: {$lightgreytext}; + display: none; + font: {$basefont}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.device .differential-inline-summary { + padding-left: 4px; + padding-right: 4px; +} + +.inline-hidden .differential-inline-summary { + display: block; +} + +.reveal-inline span.phui-icon-view { color: {$lightbluetext}; } -.reveal-inlines span.phui-icon-view { - color: {$lightbluetext}; -} - -.reveal-inlines:hover span.phui-icon-view { +.reveal-inline:hover span.phui-icon-view { color: {$darkbluetext}; } diff --git a/webroot/rsrc/css/application/diffusion/diffusion-icons.css b/webroot/rsrc/css/application/diffusion/diffusion-icons.css index 8edc034975..072db01660 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-icons.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-icons.css @@ -20,6 +20,7 @@ input.diffusion-clone-uri { .diffusion-browse-name { margin-left: 8px; + letter-spacing: 0.02em; } .diffusion-link-icon + .diffusion-link-icon { diff --git a/webroot/rsrc/css/application/diffusion/diffusion-readme.css b/webroot/rsrc/css/application/diffusion/diffusion-readme.css index b8e20af5cb..0dd5cdd858 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-readme.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-readme.css @@ -14,6 +14,10 @@ padding: 24px 32px; } +.device .diffusion-readme-view .phui-document-container { + padding: 8px 16px; +} + .diffusion-readme-view .phabricator-remarkup-toc { display: none; } diff --git a/webroot/rsrc/css/application/diffusion/diffusion-repository.css b/webroot/rsrc/css/application/diffusion/diffusion-repository.css new file mode 100644 index 0000000000..99ddd72940 --- /dev/null +++ b/webroot/rsrc/css/application/diffusion/diffusion-repository.css @@ -0,0 +1,13 @@ +/** + * @provides diffusion-repository-css + */ + +.diffusion-page-header-view a.phui-header-action-link { + display: block; + float: none; +} + +.phui-box.phui-object-box.phui-box-blue-property + .diffusion-panel-header-view.phui-header-shell { + padding: 8px 4px 8px 16px; +} diff --git a/webroot/rsrc/css/application/diffusion/diffusion-source.css b/webroot/rsrc/css/application/diffusion/diffusion-source.css index 1af068fe1a..3b8b9a8a16 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-source.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-source.css @@ -4,42 +4,57 @@ .diffusion-source { width: 100%; - background: #fff; + background: {$page.content}; + overflow: hidden; } -.diffusion-source tr.phabricator-source-highlight { - background: {$sh-yellowbackground}; +.device-phone .diffusion-source-wrap { + overflow: scroll; + -webkit-overflow-scrolling: touch; +} + +.diffusion-source tr.phabricator-source-highlight th, +.diffusion-source tr.phabricator-source-highlight td { + background: {$gentle.highlight}; } .diffusion-source th { text-align: right; vertical-align: top; - background: {$lightgreybackground}; - color: {$bluetext}; + color: {$darkbluetext}; border-right: 1px solid {$thinblueborder}; } .diffusion-source td { vertical-align: top; white-space: pre-wrap; - padding-top: 1px; - padding-bottom: 1px; - padding-left: 8px; + padding: 3px 12px; width: 100%; word-break: break-all; } +.device .diffusion-source td { + word-break: normal; + white-space: nowrap; +} + .diffusion-browse-type-form { float: right; } .diffusion-blame-link, -.diffusion-rev-link { +.diffusion-rev-link, +.diffusion-blame-date { white-space: nowrap; } -.diffusion-blame-link { - min-width: 28px; +.diffusion-blame-date, +.diffusion-blame-link, +.diffusion-blame-revision, +.diffusion-rev-link { + background: {$lightgreybackground}; + font: {$basefont}; + font-size: {$smallerfontsize}; } .diffusion-source th.diffusion-rev-link { @@ -47,16 +62,23 @@ min-width: 130px; } -.diffusion-blame-link a, -.diffusion-rev-link a, -.diffusion-line-link a { +.diffusion-source a { color: {$darkbluetext}; } -.diffusion-rev-link a, -.diffusion-rev-link span { - margin: 2px 8px 0; - display: inline-block; +.diffusion-rev-link a { + max-width: 300px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin: 3px 8px; + display: block; +} + +.diffusion-blame-date a, +.diffusion-blame-revision a { + float: right; + margin: 3px 8px; } .diffusion-rev-link span { @@ -69,7 +91,19 @@ .diffusion-line-link a { /* Give the user a larger click target. */ display: block; - padding: 2px 8px; + padding: 4px 8px 3px; +} + +.diffusion-line-link a { + color: {$lightgreytext}; +} + +.diffusion-blame-link a .phui-icon-view { + color: {$bluetext}; +} + +.diffusion-blame-link a:hover .phui-icon-view { + color: {$sky}; } .diffusion-line-link { diff --git a/webroot/rsrc/css/application/diffusion/diffusion.css b/webroot/rsrc/css/application/diffusion/diffusion.css new file mode 100644 index 0000000000..20f269dd3d --- /dev/null +++ b/webroot/rsrc/css/application/diffusion/diffusion.css @@ -0,0 +1,239 @@ +/** + * @provides diffusion-css + */ + +/* - Home Styles ------------------------------------------------------------*/ + +.diffusion-profile-header.phui-profile-header .phui-header-col3 { + vertical-align: middle; +} + +.diffusion-profile-header .phui-header-action-links a.button { + display: block; +} + +.device-phone .diffusion-profile-header .phui-header-col1 { + display: none; +} + +.diffusion-action-bar { + margin-bottom: 16px; +} + +.device-phone .diffusion-action-bar { + display: block; +} + +.device-phone .diffusion-action-bar .phui-lr-container { + display: block; +} + +.device-phone .diffusion-action-bar .phui-lr-container .phui-left-view { + display: block; +} + +.device-phone .diffusion-action-bar .phui-lr-container .phui-right-view { + padding-top: 12px; + display: block; +} + +.device-phone .diffusion-action-bar .button .phui-button-text { + visibility: hidden; + width: 0; + margin-left: 8px; +} + +.device-phone .full-mobile-buttons.diffusion-action-bar .phui-lr-container + .phui-left-view { + display: inline-block; +} + +.device-phone .full-mobile-buttons.diffusion-action-bar .phui-lr-container + .phui-right-view { + display: inline-block; +} + +.diffusion-profile-locate .phui-form-view { + margin: 0; + padding: 0; +} + +.diffusion-profile-locate .phui-form-view .aphront-form-control { + padding: 0; +} + +.diffusion-profile-locate .phui-form-view .aphront-form-input { + margin: 0; + width: 480px; +} + +.device .diffusion-profile-locate .phui-form-view .aphront-form-input { + margin: 0; + width: 100%; +} + +.diffusion-profile-description.phui-object-box { + padding: 0; +} + +.device-phone .diffusion-profile-description.phui-object-box { + padding: 0; +} + +.diffusion-view-browse-header { + display: block; + padding: 2px 0; + font-size: {$biggestfontsize}; +} + +/* - List Styles ------------------------------------------------------------*/ + +.diffusion-history-list .phui-oi-link { + color: {$blacktext}; + font-size: {$biggerfontsize}; +} + +.diffusion-history-message { + background-color: {$bluebackground}; + padding: 16px; + margin: 4px 0; + border-radius: 5px; + color: {$darkbluetext}; +} + +.diffusion-history-list .phui-oi-attribute { + font-size: {$smallerfontsize}; + letter-spacing: 0.01em; +} + +.diffusion-history-author-name a { + color: {$darkbluetext}; +} + +.diffusion-history-list .diffusion-differential-tag { + margin-left: 4px; +} + +/* - Branch Styles ----------------------------------------------------------*/ + +.diffusion-branch-list .phui-oi-attribute a { + color: {$darkbluetext}; +} + +.diffusion-branch-list .phui-oi-attribute-spacer { + visibility: hidden; +} + +.diffusion-branch-list .phui-oi-subhead { + color: {$bluetext}; +} + +.diffusion-branch-list .phui-oi-subhead .phui-tag-view { + margin-right: 4px; +} + +.diffusion-header-branch-tag.phui-tag-view.phui-tag-type-outline + .phui-tag-core { + padding: 3px 12px 2px; + font-size: {$normalfontsize}; +} + +/* - Browse Styles ----------------------------------------------------------*/ + +.diffusion-browse-table .commit-detail { + padding-left: 32px; +} + +.diffusion-browse-table .commit-detail a { + color: {$darkbluetext}; +} + +.diffusion-search-result-header.phui-header-shell { + border: none; + padding-bottom: 24px; +} + +/* - Search Input ------------------------------------------------------------*/ + +.diffusion-search-form-view { + width: 240px; +} + +.diffusion-search-form-view .diffusion-search-input { + width: 240px; + border-radius: 20px; + padding-left: 12px; +} + +.device-phone .diffusion-browse-header .diffusion-search-form-view, +.device-phone .diffusion-profile-header .diffusion-search-form-view { + display: none; +} + +.diffusion-mobile-search-form { + display: none; +} + +.device-phone .diffusion-mobile-search-form { + display: block; +} + +.device-phone .diffusion-search-form-view { + width: 100%; + margin-bottom: 20px; +} + +.device-phone .diffusion-search-form-view .diffusion-search-input { + width: 100%; +} + +/* - Create Repository -------------------------------------------------------*/ + +.diffusion-create-repo { + margin-top: 32px; + margin-bottom: 20px; +} + +.device .diffusion-create-repo { + margin-top: 0; + margin-bottom: 0; +} + + +/* - Phone Style ------------------------------------------------------------*/ + +.device-phone.diffusion-history-view .phui-two-column-view + .phui-two-column-footer .phui-object-box { + border-color: {$thinblueborder}; +} + +.device-phone.diffusion-history-view .phui-oi-attribute-spacer { + display: none; +} + +.device-phone.diffusion-history-view .phui-oi-attribute { + display: block; + margin: 0 0 4px 0; +} + +.device-phone.diffusion-history-view .phui-oi-image { + height: 36px; + width: 36px; + margin-top: 10px; +} + +.device-phone.diffusion-history-view .phui-oi-with-image .phui-oi-content-box { + margin-left: 44px; +} + +.device-phone.diffusion-history-view .phui-oi-col2.phui-oi-side-column { + padding-bottom: 10px; +} + +.device-phone .phui-two-column-view .phui-two-column-content + .phui-object-box.diffusion-mobile-view { + margin: 0 -12px 20px; + border-left: none; + border-right: none; + border-color: {$thinblueborder}; +} diff --git a/webroot/rsrc/css/application/files/global-drag-and-drop.css b/webroot/rsrc/css/application/files/global-drag-and-drop.css index e338b987b0..f9981d0492 100644 --- a/webroot/rsrc/css/application/files/global-drag-and-drop.css +++ b/webroot/rsrc/css/application/files/global-drag-and-drop.css @@ -14,7 +14,7 @@ top: 30%; padding: 18px 0; - color: #ffffff; + color: {$page.content}; background: rgba({$alphablack}, 0.8); border-radius: 18px; diff --git a/webroot/rsrc/css/application/objectselector/object-selector.css b/webroot/rsrc/css/application/objectselector/object-selector.css index 927b805155..fc53cc3282 100644 --- a/webroot/rsrc/css/application/objectselector/object-selector.css +++ b/webroot/rsrc/css/application/objectselector/object-selector.css @@ -32,7 +32,7 @@ td.phabricator-object-selector-search-text { .phabricator-object-selector-row:hover a { text-decoration: none; - color: #000; + color: {$blacktext}; } .phabricator-object-selector-search-text input { diff --git a/webroot/rsrc/css/application/paste/paste.css b/webroot/rsrc/css/application/paste/paste.css index b9b2fe2dd2..22138df452 100644 --- a/webroot/rsrc/css/application/paste/paste.css +++ b/webroot/rsrc/css/application/paste/paste.css @@ -3,8 +3,8 @@ */ .paste-embed { - background: {$sh-yellowbackground}; - border: 1px solid {$sh-lightyellowborder}; + background: {$paste.content}; + border: 1px solid {$paste.border}; border-radius: 3px; } @@ -13,7 +13,8 @@ } .paste-embed-head { - border-bottom: 1px solid {$sh-lightyellowborder}; + border-bottom: 1px solid {$paste.border}; + background: {$paste.highlight}; padding: 8px 12px; } @@ -25,3 +26,8 @@ .paste-embed-body { overflow-y: auto; } + +.paste-embed-body .phabricator-source-code-container { + border-top-right-radius: 0; + border-top-left-radius: 0; +} diff --git a/webroot/rsrc/css/application/people/people-picture-menu-item.css b/webroot/rsrc/css/application/people/people-picture-menu-item.css index 8f5da76166..745f9df3cd 100644 --- a/webroot/rsrc/css/application/people/people-picture-menu-item.css +++ b/webroot/rsrc/css/application/people/people-picture-menu-item.css @@ -8,7 +8,7 @@ } .people-menu-image-container { - background: #fff; + background: {$page.content}; padding: 5px; border-radius: 5px; border: 1px solid rgba({$alphablue},.2); diff --git a/webroot/rsrc/css/application/phame/phame.css b/webroot/rsrc/css/application/phame/phame.css index 080f5ca344..4478e607e9 100644 --- a/webroot/rsrc/css/application/phame/phame.css +++ b/webroot/rsrc/css/application/phame/phame.css @@ -29,6 +29,7 @@ position: absolute; } + .phame-blog-description + .phui-document-view-pro-box { border-top: 1px solid rgba({$alphablue}, 0.20); } @@ -38,11 +39,11 @@ } .phame-home-view .phui-side-column { - background-color: #fff; + background-color: {$page.content}; } .phame-home-view { - background-color: #fff; + background-color: {$page.content}; border-bottom: 1px solid rgba({$alphagrey},.1); } @@ -145,7 +146,7 @@ width: 360px; float: right; text-align: right; - color: #000; + color: {$blacktext}; position: relative; } @@ -179,7 +180,7 @@ width: 360px; float: left; text-align: left; - color: #000; + color: {$blacktext}; position: relative; } @@ -258,7 +259,7 @@ /* Hero Image */ .phame-header-hero { - background-color: #fff; + background-color: {$page.content}; margin-top: 16px; } @@ -281,7 +282,7 @@ .phame-mega-header { margin: 0 auto; text-align: center; - background: #fff; + background: {$page.content}; padding: 16px 0 24px; } @@ -290,7 +291,7 @@ } .phame-mega-header .phame-header-title { - color: #000; + color: {$blacktext}; font-size: 28px; font-weight: bold; padding-top: 24px; diff --git a/webroot/rsrc/css/application/pholio/pholio.css b/webroot/rsrc/css/application/pholio/pholio.css index 52ff4ca5bd..598a5e0abc 100644 --- a/webroot/rsrc/css/application/pholio/pholio.css +++ b/webroot/rsrc/css/application/pholio/pholio.css @@ -181,7 +181,7 @@ } .mock-image-description { - background: #fff; + background: {$page.content}; border-top: 1px solid {$thinblueborder}; text-align: left; } diff --git a/webroot/rsrc/css/application/project/project-card-view.css b/webroot/rsrc/css/application/project/project-card-view.css index 0d45de9485..b960d55cef 100644 --- a/webroot/rsrc/css/application/project/project-card-view.css +++ b/webroot/rsrc/css/application/project/project-card-view.css @@ -5,7 +5,7 @@ .project-card-view { margin: 0 12px 16px 0; text-align: left; - background: #fff; + background: {$page.content}; border: 1px solid {$lightblueborder}; border-radius: 3px; box-shadow: {$dropshadow}; @@ -21,9 +21,9 @@ } .project-card-view .phui-header-shell .phui-header-image { - border: 3px solid #fff; + border: 3px solid {$page.content}; border-radius: 3px; - background-color: #fff; + background-color: {$page.content}; } .project-card-view .phui-header-shell .phui-header-header { @@ -71,7 +71,7 @@ .project-card-header .project-card-name { font-size: 20px; font-weight: bold; - color: #000; + color: {$blacktext}; margin-bottom: 2px; text-overflow: ellipsis; white-space: nowrap; @@ -129,85 +129,48 @@ /* Colors */ -.project-card-view.project-card-red { - border-color: {$sh-redborder}; -} - .project-card-view.project-card-red .phui-header-shell { background: linear-gradient(to bottom, - {$sh-redbackground} 42px, #fff 42px); -} - -.project-card-view.project-card-orange { - border-color: {$sh-orangeborder}; + {$sh-redbackground} 42px, {$page.content} 42px); } .project-card-view.project-card-orange .phui-header-shell { background: linear-gradient(to bottom, - {$sh-orangebackground} 42px, #fff 42px); -} - -.project-card-view.project-card-yellow { - border-color: {$sh-yellowborder}; + {$sh-orangebackground} 42px, {$page.content} 42px); } .project-card-view.project-card-yellow .phui-header-shell { background: linear-gradient(to bottom, - {$sh-yellowbackground} 42px, #fff 42px); -} - -.project-card-view.project-card-green { - border-color: {$sh-greenborder}; + {$sh-yellowbackground} 42px, {$page.content} 42px); } .project-card-view.project-card-green .phui-header-shell { background: linear-gradient(to bottom, - {$sh-greenbackground} 42px, #fff 42px); -} - -.project-card-view.project-card-blue { - border-color: {$sh-blueborder}; + {$sh-greenbackground} 42px, {$page.content} 42px); } .project-card-view.project-card-blue .phui-header-shell { background: linear-gradient(to bottom, - {$sh-bluebackground} 42px, #fff 42px); -} - -.project-card-view.project-card-indigo { - border-color: {$sh-indigoborder}; + {$sh-bluebackground} 42px, {$page.content} 42px); } .project-card-view.project-card-indigo .phui-header-shell { background: linear-gradient(to bottom, - {$sh-indigobackground} 42px, #fff 42px); -} - -.project-card-view.project-card-violet { - border-color: {$sh-violetborder}; + {$sh-indigobackground} 42px, {$page.content} 42px); } .project-card-view.project-card-violet .phui-header-shell { background: linear-gradient(to bottom, - {$sh-violetbackground} 42px, #fff 42px); -} - -.project-card-view.project-card-pink { - border-color: {$sh-pinkborder}; + {$sh-violetbackground} 42px, {$page.content} 42px); } .project-card-view.project-card-pink .phui-header-shell { background: linear-gradient(to bottom, - {$sh-pinkbackground} 42px, #fff 42px); -} - -.project-card-view.project-card-grey, -.project-card-view.project-card-checkered { - border-color: {$sh-greyborder}; + {$sh-pinkbackground} 42px, {$page.content} 42px); } .project-card-view.project-card-grey .phui-header-shell, .project-card-view.project-card-checkered .phui-header-shell { background: linear-gradient(to bottom, - {$sh-greybackground} 42px, #fff 42px); + {$sh-greybackground} 42px, {$page.content} 42px); } diff --git a/webroot/rsrc/css/application/search/application-search-view.css b/webroot/rsrc/css/application/search/application-search-view.css index 1964d010f0..afc59a7ba8 100644 --- a/webroot/rsrc/css/application/search/application-search-view.css +++ b/webroot/rsrc/css/application/search/application-search-view.css @@ -3,11 +3,11 @@ */ .application-search-view { - background-color: #fff; + background-color: {$page.content}; } .application-search-view .phui-crumbs-view { - background-color: #fff; + background-color: {$page.content}; } .application-search-view .application-search-results.phui-object-box { @@ -40,7 +40,7 @@ padding: 12px 0; } -.device-phone .application-search-results +.device-phone.application-search-view .application-search-results .phui-profile-header.phui-header-shell { padding: 12px 0 12px 4px; } diff --git a/webroot/rsrc/css/application/search/search-results.css b/webroot/rsrc/css/application/search/search-results.css index a1837fa391..86a0cc075a 100644 --- a/webroot/rsrc/css/application/search/search-results.css +++ b/webroot/rsrc/css/application/search/search-results.css @@ -12,9 +12,10 @@ } .phui-source-fragment strong { - background-color: {$lightyellow}; - font-weight: normal; - color: #000; + background-color: {$gentle.highlight}; + font-weight: 600; + color: {$blacktext}; + letter-spacing: 0.02em; } .phui-fulltext-tokens { @@ -26,6 +27,6 @@ margin: 0 2px; } -.phui-fulltext-tokens .phui-tag-view.phui-tag-shade-grey { +.phui-fulltext-tokens .phui-tag-view.phui-tag-grey { opacity: 0.5; } diff --git a/webroot/rsrc/css/core/core.css b/webroot/rsrc/css/core/core.css index 01c4414454..9d471477ed 100644 --- a/webroot/rsrc/css/core/core.css +++ b/webroot/rsrc/css/core/core.css @@ -48,6 +48,7 @@ body { text-align: left; unicode-bidi: embed; background: {$page.background}; + color: {$blacktext}; /* By default, the iPhone zooms all text on the page by some percentage when you rotate from portrait mode to landscape mode. Disable this, since it @@ -173,3 +174,16 @@ hr { height: 2px; background: {$sky}; } + +.clipboard-copy { + visibility: hidden; +} + +.supports-clipboard .clipboard-copy { + visibility: visible; +} + +.clipboard-buffer { + position: absolute; + left: -9999px; +} diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index a986409227..8750598780 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -3,7 +3,7 @@ */ .phabricator-remarkup { - line-height: 1.51em; + line-height: 1.7em; word-break: break-word; } @@ -47,7 +47,7 @@ .phabricator-remarkup .remarkup-code-block pre { background: rgba({$alphablue},0.08); display: block; - color: #000; + color: {$blacktext}; overflow: auto; padding: 12px; border-radius: 3px; @@ -80,7 +80,7 @@ } .phabricator-remarkup tt.remarkup-monospaced { - color: #000; + color: {$blacktext}; background: rgba({$alphablue},0.1); padding: 1px 4px; border-radius: 3px; @@ -122,15 +122,38 @@ .phabricator-remarkup .remarkup-list-with-checkmarks .remarkup-checked-item, .phabricator-remarkup .remarkup-list-with-checkmarks .remarkup-unchecked-item { list-style: none; - margin-left: -18px; + margin-left: -20px; + position: relative; + padding-left: 22px; } .phabricator-remarkup .remarkup-list-with-checkmarks input { - margin-right: 2px; - opacity: 1; + visibility: hidden; + width: 0; +} + +.phabricator-remarkup .remarkup-list-with-checkmarks + .remarkup-list-item::before { + height: 16px; + width: 16px; + background-size: 16px; + display: inline-block; + content: ''; + position: absolute; + top: 3px; + left: 0; +} + +.remarkup-list-with-checkmarks .remarkup-checked-item::before { + background-image: url(rsrc/image/controls/checkbox-checked.png); +} + +.remarkup-list-with-checkmarks .remarkup-unchecked-item::before { + background-image: url(rsrc/image/controls/checkbox-unchecked.png); } .phabricator-remarkup .remarkup-list-with-checkmarks .remarkup-checked-item { + color: {$lightgreytext}; text-decoration: line-through; } @@ -319,7 +342,7 @@ video.phabricator-media { .phabricator-remarkup-toc { float: right; border-left: 1px solid {$lightblueborder}; - background: #fff; + background: {$page.content}; width: 160px; padding-left: 8px; margin: 0 0 4px 8px; @@ -379,7 +402,7 @@ video.phabricator-media { -webkit-font-smoothing: antialiased; border: 1px solid {$lightblueborder}; border-radius: 3px; - color: #000; + color: {$blacktext}; min-width: 256px; position: relative; /*height: 22px;*/ @@ -486,7 +509,7 @@ video.phabricator-media { } .phabricator-remarkup table.remarkup-table td { - background: #ffffff; + background: {$page.content}; padding: 3px 6px; } @@ -656,7 +679,7 @@ var.remarkup-assist-textarea { position: absolute; width: 300px; box-shadow: {$dropshadow}; - background: #ffffff; + background: {$page.content}; border: 1px solid {$lightgreyborder}; border-radius: 3px; } @@ -698,13 +721,13 @@ var.remarkup-assist-textarea { .phuix-autocomplete-list a.jx-result:hover { text-decoration: none; background: {$sh-bluebackground}; - color: #000; + color: {$blacktext}; } .phuix-autocomplete-list a.jx-result.focused, .phuix-autocomplete-list a.jx-result.focused:hover { background: {$sh-bluebackground}; - color: #000; + color: {$blacktext}; } @@ -712,7 +735,7 @@ var.remarkup-assist-textarea { .phui-box.phui-object-box.phui-comment-form-view.remarkup-assist-pinned { position: fixed; - background-color: #ffffff; + background-color: {$page.content}; border-top: 1px solid {$lightblueborder}; box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); @@ -736,7 +759,7 @@ var.remarkup-assist-textarea { .remarkup-inline-preview { display: block; position: relative; - background: #fff; + background: {$page.content}; overflow-y: auto; box-sizing: border-box; width: 100%; @@ -760,7 +783,7 @@ var.remarkup-assist-textarea { } .remarkup-assist-button.preview-active .phui-icon-view { - color: #fff; + color: {$page.content}; } .remarkup-assist-button.preview-active:hover { @@ -768,7 +791,7 @@ var.remarkup-assist-textarea { } .remarkup-assist-button.preview-active:hover .phui-icon-view { - color: #fff; + color: {$page.content}; } .remarkup-preview-active .remarkup-assist, @@ -844,7 +867,7 @@ var.remarkup-assist-textarea { border-width: 1px 0 0 0; outline: none; resize: none; - background: #fff !important; + background: {$page.content} !important; } .remarkup-control-fullscreen-mode textarea.remarkup-assist-textarea:focus { diff --git a/webroot/rsrc/css/core/z-index.css b/webroot/rsrc/css/core/z-index.css index bdab2e97ed..75cd394988 100644 --- a/webroot/rsrc/css/core/z-index.css +++ b/webroot/rsrc/css/core/z-index.css @@ -93,6 +93,10 @@ div.phui-calendar-day-event { z-index: 6; } +.diff-banner { + z-index: 6; +} + .conpherence-durable-column { z-index: 7; } diff --git a/webroot/rsrc/css/diviner/diviner-shared.css b/webroot/rsrc/css/diviner/diviner-shared.css index 56dc6ddf30..a2eb6c9c72 100644 --- a/webroot/rsrc/css/diviner/diviner-shared.css +++ b/webroot/rsrc/css/diviner/diviner-shared.css @@ -66,7 +66,7 @@ } .phui-header-shell.diviner-section-header .phui-header-header { - color: #000; + color: {$blacktext}; font-size: 20px; } diff --git a/webroot/rsrc/css/layout/phabricator-source-code-view.css b/webroot/rsrc/css/layout/phabricator-source-code-view.css index 7e956b580a..a855e01447 100644 --- a/webroot/rsrc/css/layout/phabricator-source-code-view.css +++ b/webroot/rsrc/css/layout/phabricator-source-code-view.css @@ -5,9 +5,9 @@ .phabricator-source-code-container { overflow-x: auto; overflow-y: hidden; - border: 1px solid {$sh-lightyellowborder}; + border: 1px solid {$paste.border}; border-radius: 3px; - background-color: #FFFEF5; + background-color: {$paste.content}; } .phui-oi .phabricator-source-code-container { @@ -29,10 +29,10 @@ } .phabricator-source-line { - background-color: {$sh-yellowbackground}; + background-color: {$paste.highlight}; text-align: right; padding: 2px 6px 1px 12px; - border-right: 1px solid {$sh-lightyellowborder}; + border-right: 1px solid {$paste.border}; color: {$sh-yellowtext}; /* When the user selects rows of source, don't visibly select the line @@ -51,12 +51,12 @@ th.phabricator-source-line a { } th.phabricator-source-line:hover { - background: {$sh-lightyellowborder}; + background: {$paste.border}; cursor: pointer; } .phabricator-source-highlight { - background: {$sh-yellowbackground}; + background: {$paste.highlight}; } .phabricator-source-code-summary { diff --git a/webroot/rsrc/css/phui/button/phui-button-bar.css b/webroot/rsrc/css/phui/button/phui-button-bar.css new file mode 100644 index 0000000000..87369e4ab6 --- /dev/null +++ b/webroot/rsrc/css/phui/button/phui-button-bar.css @@ -0,0 +1,61 @@ +/** + * @provides phui-button-bar-css + * @requires phui-button-css + * @requires phui-button-simple-css + */ + +.phui-button-bar-borderless .button { + border: 0; + background-color: transparent; + background-image: none; + padding-left: 10px; + padding-right: 10px; +} + +.phui-button-bar-borderless .button .phui-icon-view { + font-size: 15px; + color: {$bluetext}; +} + +.phui-button-bar-borderless .button:hover { + background-color: transparent; + background-image: none; + border-radius: 3px; +} + +.phui-button-bar-borderless .button:hover .phui-icon-view { + color: {$sky}; +} + +.phui-button-bar-borderless .button { + border: 0; +} + +.phui-button-bar .phui-button-bar-first { + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; +} + +.phui-button-bar .phui-button-bar-middle { + border-radius: 0; + border-left: none; +} + +.phui-button-bar .phui-button-bar-last { + border-left: none; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; +} + +.phui-button-bar .button.simple:hover { + border-color: {$lightblueborder}; + background-color: #fff; + background-image: none; + color: {$sky}; +} + +.phui-button-bar .button.simple:hover .phui-icon-view { + border-color: {$lightblueborder}; + color: {$sky}; + background-image: none; +} diff --git a/webroot/rsrc/css/phui/button/phui-button-simple.css b/webroot/rsrc/css/phui/button/phui-button-simple.css new file mode 100644 index 0000000000..1ebfea47d5 --- /dev/null +++ b/webroot/rsrc/css/phui/button/phui-button-simple.css @@ -0,0 +1,131 @@ +/** + * @provides phui-button-simple-css + * @requires phui-button-css + */ + + +/* - Basic -------------------------------------------------------------------*/ + +button.phui-button-simple, +input[type="submit"].phui-button-simple, +a.phui-button-simple, +a.phui-button-simple:visited { + background: {$page.content}; + color: {$bluetext}; + border: 1px solid {$lightblueborder}; +} + +button.phui-button-simple .phui-icon-view, +input[type="submit"].phui-button-simple .phui-icon-view, +a.phui-button-simple .phui-icon-view, +a.phui-button-simple:visited .phui-icon-view { + color: {$lightbluetext}; +} + +a.button.phui-button-simple:hover, +button.phui-button-simple:hover { + border-color: {$blueborder}; + background-image: none; + background-color: {$page.content}; + transition: 0s; +} + +a.phui-button-simple.current { + background: {$lightblue}; +} + + +/* - Red --------------------------------------------------------------------*/ + +button.phui-button-simple.button-red, +input[type="submit"].phui-button-simple.button-red, +a.phui-button-simple.button-red, +a.phui-button-simple.button-red:visited { + background: {$sh-redbackground}; + color: {$redtext}; + border: 1px solid {$sh-redborder}; +} + +button.phui-button-simple.button-red .phui-icon-view, +input[type="submit"].phui-button-simple.button-red .phui-icon-view, +a.phui-button-simple.button-red .phui-icon-view, +a.phui-button-simple.button-red:visited .phui-icon-view { + color: {$redtext}; +} + +a.button.phui-button-simple.button-red:hover, +button.phui-button-simple.button-red:hover { + border-color: {$sh-redtext}; + background-image: none; + background-color: {$sh-redbackground}; + transition: 0s; +} + +/* - Green ------------------------------------------------------------------*/ + +button.phui-button-simple.button-green, +input[type="submit"].phui-button-simple.button-green, +a.phui-button-simple.button-green, +a.phui-button-simple.button-green:visited { + background: {$sh-greenbackground}; + color: {$greentext}; + border: 1px solid {$sh-greenborder}; +} + +button.phui-button-simple.button-green .phui-icon-view, +input[type="submit"].phui-button-simple.button-green .phui-icon-view, +a.phui-button-simple.button-green .phui-icon-view, +a.phui-button-simple.button-green:visited .phui-icon-view { + color: {$greentext}; +} + +a.button.phui-button-simple.button-green:hover, +button.phui-button-simple.button-green:hover { + border-color: {$sh-greentext}; + background-image: none; + background-color: {$sh-greenbackground}; + transition: 0s; +} + +/* - Yellow -----------------------------------------------------------------*/ + +button.phui-button-simple.button-yellow, +input[type="submit"].phui-button-simple.button-yellow, +a.phui-button-simple.button-yellow, +a.phui-button-simple.button-yellow:visited { + background-color: {$sh-yellowbackground}; + color: {$sh-yellowtext}; + border: 1px solid {$sh-yellowborder}; +} + +button.phui-button-simple.button-yellow .phui-icon-view, +input[type="submit"].phui-button-simple.button-yellow .phui-icon-view, +a.phui-button-simple.button-yellow .phui-icon-view, +a.phui-button-simple.button-yellow:visited .phui-icon-view { + color: {$sh-yellowicon}; +} + +a.button.phui-button-simple.button-yellow:hover, +button.phui-button-simple.button-yellow:hover { + border-color: {$sh-yellowtext}; + background-image: none; + background-color: {$sh-yellowbackground}; + transition: 0s; +} + + +/* - Misc -------------------------------------------------------------------*/ + +a.button.phui-button-simple .phui-icon-view { + border: none; +} + +a.button.phui-button-simple.phuix-dropdown-open { + background-color: #fff; + color: {$blue}; + box-shadow: none; +} + +a.button.phui-button-simple.phuix-dropdown-open:hover .phui-icon-view { + color: {$blue}; +} diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/button/phui-button.css similarity index 58% rename from webroot/rsrc/css/phui/phui-button.css rename to webroot/rsrc/css/phui/button/phui-button.css index 09f2233a6c..14bbdcf46b 100644 --- a/webroot/rsrc/css/phui/phui-button.css +++ b/webroot/rsrc/css/phui/button/phui-button.css @@ -33,15 +33,15 @@ button, a.button, a.button:visited, input[type="submit"] { - background-color: #2980b9; - border: 1px solid #2980b9; - background-image: linear-gradient(to bottom, #3498db, #2980b9); + background-color: {$blue.button.color}; + border: 1px solid {$blue.button.color}; + background-image: {$blue.button.gradient}; color: white; cursor: pointer; font-weight: bold; font-size: {$normalfontsize}; display: inline-block; - padding: 4px 16px 5px; + padding: 4px 14px 5px; text-align: center; white-space: nowrap; border-radius: 3px; @@ -49,13 +49,15 @@ input[type="submit"] { button .phui-icon-view, a.button .phui-icon-view, -button.green .phui-icon-view, -a.button.green .phui-icon-view { +button.button-green .phui-icon-view, +a.button.button-green .phui-icon-view, +button.button-red .phui-icon-view, +a.button.button-red .phui-icon-view { color: white; } -button.grey .phui-icon-view, -a.button.grey .phui-icon-view { +button.button-grey .phui-icon-view, +a.button.button-grey .phui-icon-view { color: {$darkbluetext}; } @@ -68,44 +70,32 @@ a.icon:visited { text-indent: 29px; } -button.green, -a.green, -a.green:visited { - background-color: {$green}; - border-color: {$green}; - background-image: linear-gradient(to bottom, #23BB5B, #139543); +button.button-green, +a.button-green.button, +a.button-green.button:visited { + background-color: {$green.button.color}; + border-color: {$green.button.color}; + background-image: {$green.button.gradient}; } -button.grey, -input[type="submit"].grey, -a.grey, -a.grey:visited { - background-color: #F7F7F9; - background-image: linear-gradient(to bottom, #ffffff, #f1f0f1); +button.button-red, +a.button-red.button, +a.button-red.button:visited { + background-color: {$red.button.color}; + border-color: {$red.button.color}; + background-image: {$red.button.gradient}; +} + +button.button-grey, +input[type="submit"].button-grey, +a.button-grey, +a.button-grey:visited { + background-color: {$grey.button.color}; + background-image: {$grey.button.gradient}; border: 1px solid rgba({$alphablue}, 0.3); color: {$darkgreytext}; } -button.simple, -input[type="submit"].simple, -a.simple, -a.simple:visited { - background: #fff; - color: {$blue}; - border: 1px solid {$blue}; -} - -a.simple.current { - background: {$lightblue}; -} - -button.simple .phui-icon-view, -input[type="submit"].simple .phui-icon-view, -a.simple .phui-icon-view, -a.simple:visited .phui-icon-view { - color: {$blue}; -} - a.disabled, button.disabled, button[disabled] { @@ -115,6 +105,19 @@ button[disabled] { opacity: 0.5; } +button.button-grey.selected, +a.button.button-grey.selected, +button.button-grey.selected:hover, +a.button.button-grey.selected:hover { + border-color: {$sh-orangetext}; + color: {$sh-orangetext}; +} + +button.button-grey.selected .phui-icon-view, +a.button-grey.selected .phui-icon-view { + color: {$sh-orangetext}; +} + a.phuix-dropdown-open { color: {$greytext}; } @@ -123,54 +126,34 @@ a.button:hover, button:hover { text-decoration: none; background-color: #2980b9; - background-image: linear-gradient(to bottom, #3498db, #1b6ba0); + background-image: {$blue.button.hover}; border-color: #115988; transition: 0.1s; } -a.button.grey:hover, -button.grey:hover { - background-image: linear-gradient(to bottom, #ffffff, #eeebec); +a.button.button-grey:hover, +button.button-grey:hover { + background-image: {$grey.button.hover}; border-color: rgba({$alphablue}, 0.4); transition: 0.1s; } -a.button.green:hover, -button.green:hover { +a.button.button-green:hover, +button.button-green:hover { border-color: #127336; background-color: #0DAD48; - background-image: linear-gradient(to bottom, #23BB5B, #178841); + background-image: {$green.button.hover}; transition: 0.1s; } -a.button.simple:hover, -button.simple:hover { - background-color: {$blue}; - background-image: linear-gradient(to bottom, {$blue}, {$blue}); - color: #fff; +a.button.button-red:hover, +button.button-red:hover { + border-color: #79150b; + background-color: #0DAD48; + background-image: {$red.button.hover}; transition: 0.1s; } -a.button.simple:hover .phui-icon-view, -button.simple:hover .phui-icon-view { - color: #fff; - transition: 0.1s; -} - -a.button.simple .phui-icon-view { - border: none; -} - -a.button.simple.phuix-dropdown-open { - background-color: #fff; - color: {$blue}; - box-shadow: none; -} - -a.button.simple.phuix-dropdown-open:hover .phui-icon-view { - color: {$blue}; -} - body a.button.disabled:hover, body button.disabled:hover, body a.button.disabled:active, @@ -211,7 +194,7 @@ button.link:hover { .phuix-dropdown-menu { position: absolute; width: 200px; - background: #fff; + background: {$page.content}; margin-top: -1px; padding: 12px; box-shadow: {$dropshadow}; @@ -253,7 +236,7 @@ a.policy-control .phui-button-text { width: 0; height: 0; vertical-align: top; - border-top: 5px solid #fff; + border-top: 5px solid {$page.content}; border-right: 5px solid transparent; border-left: 5px solid transparent; content: ""; @@ -292,8 +275,8 @@ a.policy-control .phui-button-text { margin-top: 6px; } -.grey.dropdown .caret { - border-top-color: #000; +.button-grey.dropdown .caret { + border-top-color: {$blacktext}; } /* Icons */ @@ -301,11 +284,17 @@ a.policy-control .phui-button-text { position: relative; } -.button .phui-icon-view { +.button.has-icon.dropdown .phui-icon-view { + margin-right: 8px; + margin-left: -2px; +} + +.button.has-text .phui-icon-view { display: inline-block; position: absolute; top: 7px; left: 12px; + margin: 0; } .button.icon-last .phui-icon-view { @@ -313,8 +302,12 @@ a.policy-control .phui-button-text { right: 10px; } -.phui-button-bar .button .phui-icon-view { - left: 14px; +.phui-button-text { + display: inline-block; +} + +.dropdown .phui-button-text { + margin-right: 8px; } .button.has-icon .phui-button-text { @@ -345,66 +338,3 @@ a.policy-control .phui-button-text { font-weight: normal; } -/* PHUI Button Bar */ - -.phui-button-bar-borderless .button { - border: 0; - background-color: transparent; - background-image: none; - padding-left: 10px; - padding-right: 10px; -} - -.phui-button-bar-borderless .button .phui-icon-view { - font-size: 15px; - color: rgba({$alphagrey},.4); -} - -.phui-button-bar-borderless .button:hover { - background-color: transparent; - background-image: none; - border-radius: 3px; -} - -.phui-button-bar-borderless .button:hover .phui-icon-view { - color: rgba({$alphagrey},.9); -} - -.phui-button-bar-borderless .button { - border: 0; -} - -.phui-button-bar a.button.has-icon { - display: inline-block; - height: 18px; - width: 6px; -} - -.phui-button-bar .phui-button-bar-first { - border-top-right-radius: 0px; - border-bottom-right-radius: 0px; -} - -.phui-button-bar .phui-button-bar-middle { - border-radius: 0; - border-left: none; -} - -.phui-button-bar .phui-button-bar-last { - border-left: none; - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; -} - -.phui-button-bar .button.simple:hover { - border-color: {$lightblueborder}; - background-color: #fff; - background-image: none; - color: {$sky}; -} - -.phui-button-bar .button.simple:hover .phui-icon-view { - border-color: {$lightblueborder}; - color: {$sky}; - background-image: none; -} diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-list.css b/webroot/rsrc/css/phui/calendar/phui-calendar-list.css index c7b844a7eb..5fea904911 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-list.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-list.css @@ -15,7 +15,7 @@ .project-view-home .phui-object-box .phui-calendar-list-container .phui-header-shell { padding: 8px 0; - background: #fff; + background: {$page.content}; } .phui-calendar-list { diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css index c649021f0d..49b792023d 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-month.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-month.css @@ -5,7 +5,7 @@ .phui-calendar-view { width: 100%; border-collapse: collapse; - background: #fff; + background: {$page.content}; } tr.phui-calendar-day-of-week-header th { @@ -33,7 +33,7 @@ tr.phui-calendar-day-of-week-header th .short-weekday-name { } table.phui-calendar-view td { - border: solid #dfdfdf; + border: solid {$lightblueborder}; border-width: 1px 0 0; width: 14.2857%; /* This is one seventh, approximately. */ } @@ -220,7 +220,7 @@ td.phui-calendar-month-number { .device-desktop td.phui-calendar-month-day.calendar-hover, .device-desktop td.phui-calendar-month-number.calendar-hover { - background: {$lightblue}; + background: {$hoverblue}; } .phui-calendar-month-adjacent { diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar.css b/webroot/rsrc/css/phui/calendar/phui-calendar.css index ab59886fd8..0d8dcfc56e 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar.css @@ -5,7 +5,7 @@ .phui-calendar-list { /* When hovering over a day, this allows the hover color to peek through the event name, but for event names to mostly remain readable. */ - background: rgba(255, 255, 255, 0.75); + } .application-search-view div.phui-calendar-box { diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css b/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css index 7fcc6d4ef7..67ce566733 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css @@ -17,7 +17,7 @@ } .phui-oi-list-big a.phui-oi-link { - color: #000; + color: {$blacktext}; font-size: {$biggestfontsize}; } @@ -31,7 +31,7 @@ } .device-desktop .phui-oi-list-big .phui-oi { - margin-bottom: 8px; + margin-bottom: 4px; } .phui-oi-list-big .phui-oi-col0 { @@ -46,3 +46,16 @@ .phui-oi-list-big .phui-oi-visited a.phui-oi-link { color: {$violet}; } + +.phui-box-white-config .phui-oi-list-big.phui-oi-list-view { + padding: 8px 8px 4px; +} + +.phui-box-white-config .phui-oi-frame { + padding: 4px 8px 0; +} + +.device-desktop .phui-box-white-config .phui-oi:hover .phui-oi-frame { + background-color: {$hoverblue}; + border-radius: 3px; +} diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-drag-ui.css b/webroot/rsrc/css/phui/object-item/phui-oi-drag-ui.css index caf9ff8dd0..5f24e2b983 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-drag-ui.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-drag-ui.css @@ -23,6 +23,10 @@ margin-top: 4px; } +.phui-oi-drag .phui-oi-name { + padding-left: 0; +} + .phui-oi-drag.phui-oi-with-image-icon .phui-oi-frame, .phui-oi-drag.phui-oi-with-image .phui-oi-frame, .phui-oi-drag .phui-oi-frame { @@ -57,3 +61,7 @@ .phui-oi-list-drag .drag-ghost { margin-top: 4px; } + +.phui-oi-list-drag .phui-object-icon-pane { + padding-right: 8px; +} diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index 2f29001f9a..e356396451 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -37,7 +37,7 @@ ul.phui-oi-list-view { border-color: {$lightgreyborder}; margin: 5px 0; overflow: hidden; - background: #fff; + background: {$page.content}; margin-bottom: 4px; } @@ -105,7 +105,7 @@ ul.phui-oi-list-view { } .phui-oi-objname { - color: #000; + color: {$blacktext}; cursor: text; font-weight: bold; } @@ -182,6 +182,10 @@ ul.phui-oi-list-view { vertical-align: top; } +.phui-oi-col2.phui-oi-side-column { + width: 200px; +} + .device-phone .phui-oi-col1, .device-phone .phui-oi-col2 { display: block; @@ -285,6 +289,31 @@ ul.phui-oi-list-view { padding: 0 8px 6px; } +.phui-oi-description { + display: none; +} + +.phui-oi-description.phui-oi-description-reveal { + display: block; +} + +.phui-oi-description-tag { + margin-left: 4px; +} + +.phui-oi-description-tag:hover .phui-tag-core { + cursor: pointer; + background: {$darkgreybackground}; +} + +.phui-oi-description-tag .phui-tag-core { + border: none; +} + +.phui-oi-description-tag.phui-tag-view .phui-icon-view { + margin: 2px; +} + /* - Attribute List ------------------------------------------------------------ @@ -390,6 +419,17 @@ ul.phui-oi-icons { text-decoration: line-through; } +.phui-oi.phui-oi-disabled .phui-oi-image { + opacity: .8; + -webkit-filter: grayscale(100%); + filter: grayscale(100%); +} + +.phui-oi.phui-oi-disabled .phui-oi-attribute, +.phui-oi.phui-oi-disabled .phui-oi-attribute > .phui-icon-view { + color: {$lightgreytext}; +} + /* - Effects ------------------------------------------------------------------- @@ -614,13 +654,13 @@ ul.phui-oi-list-view .phui-oi-selected /* - Launcher Button -------------------------------------------------------- */ -.phui-oi-col2.phui-oi-launch-button { +.phui-oi-col2.phui-oi-side-column { text-align: right; vertical-align: middle; padding-right: 4px; } -.device-phone .phui-oi-col2.phui-oi-launch-button { +.device-phone .phui-oi-col2.phui-oi-side-column { padding: 0 8px 8px; text-align: left; } diff --git a/webroot/rsrc/css/phui/phui-action-list.css b/webroot/rsrc/css/phui/phui-action-list.css index 4569438d5a..a7ad662d33 100644 --- a/webroot/rsrc/css/phui/phui-action-list.css +++ b/webroot/rsrc/css/phui/phui-action-list.css @@ -112,12 +112,6 @@ -webkit-font-smoothing: antialiased; } -.device-desktop li.phabricator-action-view-label:hover - .phabricator-action-view-item { - background-color: #fff; - color: {$bluetext}; -} - .phabricator-action-view + .phabricator-action-view-label { padding-top: 8px; } @@ -157,7 +151,6 @@ .phabricator-action-view-icon, .device-desktop .phabricator-action-view-disabled:hover button.phabricator-action-view-icon { - background-color: {$greybackground}; color: {$lightgreytext}; } diff --git a/webroot/rsrc/css/phui/phui-action-panel.css b/webroot/rsrc/css/phui/phui-action-panel.css index 8cfd69a94f..63bd351807 100644 --- a/webroot/rsrc/css/phui/phui-action-panel.css +++ b/webroot/rsrc/css/phui/phui-action-panel.css @@ -4,7 +4,7 @@ .phui-action-panel { position: relative; - background-color: #fff; + background-color: {$page.content}; border: 1px solid {$lightblueborder}; border-radius: 3px; margin: 0 8px; @@ -47,6 +47,12 @@ display: table-cell; } +.phui-action-panel-image { + width: 48px; + height: 48px; + margin: 0 auto; +} + .phui-action-panel-icon a { display: block; } diff --git a/webroot/rsrc/css/phui/phui-basic-nav-view.css b/webroot/rsrc/css/phui/phui-basic-nav-view.css index 03b1deb917..a545ad35ce 100644 --- a/webroot/rsrc/css/phui/phui-basic-nav-view.css +++ b/webroot/rsrc/css/phui/phui-basic-nav-view.css @@ -48,7 +48,7 @@ } .phui-two-column-view .phui-basic-nav .phabricator-side-menu { - background-color: #fff; + background-color: {$page.content}; } .phui-basic-nav .phabricator-side-menu { @@ -64,7 +64,7 @@ .phui-basic-nav .phabricator-side-menu .phui-list-item-href { display: block; - padding: 6px 8px 6px 20px; + padding: 6px 8px 6px 12px; color: {$darkbluetext}; border-top-right-radius: 3px; border-bottom-right-radius: 3px; @@ -72,13 +72,8 @@ text-overflow: ellipsis } -.phui-basic-nav .phabricator-side-menu .phui-list-item-has-icon - .phui-list-item-href { - padding-left: 12px; - } - .phui-basic-nav .phabricator-side-menu .phui-list-item-icon { - margin-left: -4px; + margin-left: -8px; text-align: center; width: 30px; } @@ -100,7 +95,6 @@ .phui-basic-nav .phabricator-side-menu .phui-list-item-selected { background-color: rgba({$alphablack},.05); - border-left: 4px solid {$sky}; border-top-right-radius: 3px; border-bottom-right-radius: 3px; font-weight: bold; @@ -109,12 +103,7 @@ .device-desktop .phui-basic-nav .phabricator-side-menu .phui-list-item-selected a.phui-list-item-href:hover { - background-color: rgba({$alphablack},.05); -} - -.phui-basic-nav .phabricator-side-menu .phui-list-item-selected - .phui-list-item-href { - margin-left: -4px; + background-color: rgba({$alphablack},.05); } .phui-basic-nav .phabricator-side-menu .phui-list-item-type-label { @@ -124,6 +113,7 @@ font-size: 12px; font-weight: bold; border-style: solid; + letter-spacing: 0.02em; } .device-desktop .phui-basic-nav .phabricator-side-menu diff --git a/webroot/rsrc/css/phui/phui-big-info-view.css b/webroot/rsrc/css/phui/phui-big-info-view.css index 3c92025d79..b8fbab55ce 100644 --- a/webroot/rsrc/css/phui/phui-big-info-view.css +++ b/webroot/rsrc/css/phui/phui-big-info-view.css @@ -5,7 +5,7 @@ .phui-big-info-view { padding: 64px 32px; margin: 16px 4px; - background-color: {$sh-greybackground}; + background-color: {$page.sidenav}; text-align: center; } @@ -35,3 +35,10 @@ .phui-big-info-button + .phui-big-info-button { margin-left: 12px; } + +.phui-big-info-view .phui-big-info-image { + height: 64px; + width: 64px; + margin: 0 auto; + padding-bottom: 12px; +} diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css index 9dc99c6f8c..278f1365e8 100644 --- a/webroot/rsrc/css/phui/phui-box.css +++ b/webroot/rsrc/css/phui/phui-box.css @@ -4,7 +4,7 @@ .phui-box-border { border: 1px solid {$lightblueborder}; - background-color: #fff; + background-color: {$page.content}; border-radius: 3px; } @@ -13,7 +13,7 @@ } .phui-box-grey { - background-color: #F7F7F9; + background-color: {$lightgreybackground}; border-radius: 3px; border-color: rgba({$alphagrey},.2); } @@ -36,7 +36,7 @@ .phui-box-blue .phui-oi-list-view, .phui-box-grey .phui-oi-list-view { - background-color: #fff; + background-color: {$page.content}; } .phui-box-blue .phui-header-shell { @@ -49,7 +49,7 @@ .phui-object-box.phui-box-blue div.phui-info-severity-nodata, .phui-object-box.phui-box-grey div.phui-info-severity-nodata { - background: #fff; + background: {$page.content}; padding: 32px 0; text-align: center; border: none; @@ -81,7 +81,7 @@ } .phui-box.phui-object-box.phui-box-blue-property .phui-header-shell { - background-color: #eff3fc; + background-color: {$bluepropertybackground}; border-top-right-radius: 3px; border-top-left-radius: 3px; padding: 6px 16px; @@ -113,5 +113,31 @@ body .phui-box-blue-property .phui-header-shell + .phui-object-box { .phui-box-blue-property .phui-header-shell + .phui-object-box .phui-header-shell { - background: #fff; + background: {$page.content}; +} + +/* Config Boxes */ + +.phui-box-white-config.phui-box-border { + border-color: #e2e2e2; + border-radius: 5px; +} + +.phui-box-white-config.phui-object-box { + padding: 16px 0 0 0; +} + +.phui-box-white-config .phui-header-shell { + border-bottom: 1px solid #e2e2e2; + overflow: hidden; + padding: 0 16px 16px; +} + +.phui-box-white-config .phui-header-header { + color: {$bluetext}; +} + +.phui-box-white-config .phui-header-action-links .button { + margin-top: 0; + margin-bottom: 0; } diff --git a/webroot/rsrc/css/phui/phui-comment-form.css b/webroot/rsrc/css/phui/phui-comment-form.css index 860dc9dcaf..dcf45edb76 100644 --- a/webroot/rsrc/css/phui/phui-comment-form.css +++ b/webroot/rsrc/css/phui/phui-comment-form.css @@ -3,7 +3,7 @@ */ body .phui-box.phui-object-box.phui-comment-form-view { - background-color: #fff; + background-color: {$page.content}; margin-left: 62px; position: relative; } @@ -67,7 +67,7 @@ body.device .phui-box.phui-object-box.phui-comment-form-view { } .phui-comment-form-view .aphront-form-input .remarkup-assist-textarea:focus { - background-color: #fff; + background-color: {$page.content}; } .device-phone .phui-comment-form-view .aphront-form-input @@ -90,7 +90,7 @@ body.device .phui-box.phui-object-box.phui-comment-form-view { } .phui-comment-action { - background-color: rgba(239, 243, 252, .75); + background-color: rgba({$alphablue}, .1); border-radius: 3px; margin: 0px 16px 8px; padding: 6px; @@ -106,7 +106,7 @@ body.device .phui-box.phui-object-box.phui-comment-form-view { .phui-comment-form-view .phui-comment-action-bar { border-bottom: 1px solid {$thinblueborder}; - background-color: rgba(239, 243, 252, .75); + background-color: {$bluepropertybackground}; padding: 4px 12px 4px 12px; margin-bottom: 16px; } diff --git a/webroot/rsrc/css/phui/phui-curtain-view.css b/webroot/rsrc/css/phui/phui-curtain-view.css index dbc706994a..8bd8a331c4 100644 --- a/webroot/rsrc/css/phui/phui-curtain-view.css +++ b/webroot/rsrc/css/phui/phui-curtain-view.css @@ -7,12 +7,17 @@ margin: 0 4px; } +.phui-two-column-properties > .phui-curtain-panel:first-child { + padding-top: 6px; +} + .device .phui-curtain-panel { padding: 8px 0; margin: 0; } -.device-desktop .phui-curtain-panel { +.device-desktop .phui-curtain-panel + .phui-curtain-panel, +.device-desktop .phabricator-action-list-view + .phui-curtain-panel { border-top: 1px solid {$greybackground}; } @@ -41,3 +46,7 @@ .phui-side-column .phui-curtain-panel-body .phui-tag-view { white-space: pre-wrap; } + +.device .curtain-no-panels { + display: none; +} diff --git a/webroot/rsrc/css/phui/phui-document-pro.css b/webroot/rsrc/css/phui/phui-document-pro.css index 0aef43ed90..35c843f81f 100644 --- a/webroot/rsrc/css/phui/phui-document-pro.css +++ b/webroot/rsrc/css/phui/phui-document-pro.css @@ -9,7 +9,7 @@ } .phui-document-container { - background-color: #fff; + background-color: {$page.content}; position: relative; border-bottom: 1px solid #dedee1; } @@ -64,29 +64,22 @@ body.printable { .phui-document-view-pro .phui-document-toc { position: absolute; top: 34px; - left: -36px; + left: -44px; } .printable .phui-document-view-pro a.phui-document-toc { display: none; } -a.button.phui-document-toc { - display: inline-block; - height: 16px; - width: 20px; - padding: 3px 8px 4px 8px; -} - .phui-document-view-pro .phui-document-toc-list { margin: 8px; border: 1px solid {$lightgreyborder}; border-radius: 3px; box-shadow: {$dropshadow}; - width: 200px; + width: 260px; position: absolute; z-index: 30; - background-color: #fff; + background-color: {$page.content}; top: 52px; left: -40px; } @@ -104,25 +97,42 @@ a.button.phui-document-toc { } .phui-document-toc-open .phui-document-toc { - background-color: {$blue}; -} - -.phui-document-toc-open .phui-document-toc .phui-icon-view { - color: #fff; + border-color: {$blueborder}; } .phui-document-view-pro .phui-document-toc-content { - margin: 4px 12px; + margin: 8px 16px; } .phui-document-view-pro .phui-document-toc-header { font-weight: bold; color: {$bluetext}; margin-bottom: 8px; + text-transform: uppercase; + font-size: {$smallerfontsize}; } .phui-document-view-pro .phui-document-toc-content li { - margin: 4px 8px; + margin: 4px 8px 4px 0; +} + +.phui-document-view-pro .phui-document-toc-content a { + padding: 2px 0; + display: block; + text-decoration: none; + color: {$darkbluetext}; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.phui-document-view-pro .phui-document-toc-content a:hover { + color: {$anchor}; + text-decoration: underline; +} + +.phui-document-view-pro .phui-document-toc-content li + ul { + margin: 4px 0 4px 8px; } .phui-document-view-pro .phui-document-content .phabricator-remarkup { @@ -153,7 +163,7 @@ a.button.phui-document-toc { .phui-header-header { font-size: 24px; line-height: 30px; - color: #000; + color: {$blacktext}; } .device-phone .phui-document-view.phui-document-view-pro .phui-header-tall @@ -218,7 +228,7 @@ a.button.phui-document-toc { .phui-document-view-pro-box .phui-timeline-title { border-top-right-radius: 3px; border-top-left-radius: 3px; - background-color: #fff; + background-color: {$page.content}; border-bottom: 1px solid #F1F1F4; } diff --git a/webroot/rsrc/css/phui/phui-document-summary.css b/webroot/rsrc/css/phui/phui-document-summary.css index 736efe9d20..322096f481 100644 --- a/webroot/rsrc/css/phui/phui-document-summary.css +++ b/webroot/rsrc/css/phui/phui-document-summary.css @@ -17,7 +17,7 @@ body .phui-document-view .phui-document-summary-view h2.remarkup-header { } .phui-document-summary-view h2.remarkup-header a { - color: #000; + color: {$blacktext}; } .phui-document-summary-view h2.remarkup-header a:hover { diff --git a/webroot/rsrc/css/phui/phui-document.css b/webroot/rsrc/css/phui/phui-document.css index 87cdc60ddd..e5985f8889 100644 --- a/webroot/rsrc/css/phui/phui-document.css +++ b/webroot/rsrc/css/phui/phui-document.css @@ -55,7 +55,7 @@ } .phui-document-content { - background: #fff; + background: {$page.content}; } .phui-document-content .phabricator-remarkup { diff --git a/webroot/rsrc/css/phui/phui-fontkit.css b/webroot/rsrc/css/phui/phui-fontkit.css index 875ac41981..43162ea618 100644 --- a/webroot/rsrc/css/phui/phui-fontkit.css +++ b/webroot/rsrc/css/phui/phui-fontkit.css @@ -3,7 +3,7 @@ */ .diviner-document-section .phui-header-header { - color: #000; + color: {$blacktext}; } .phui-document-view .phabricator-remarkup .remarkup-header { diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css index b89d3ee6f9..f23511f255 100644 --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -196,6 +196,7 @@ .aphront-form-control-markup .aphront-form-input { font-size: {$normalfontsize}; + padding: 3px 0; } .aphront-form-control-static .aphront-form-input { diff --git a/webroot/rsrc/css/phui/phui-form.css b/webroot/rsrc/css/phui/phui-form.css index 8a6f705ec9..1c587b5d3d 100644 --- a/webroot/rsrc/css/phui/phui-form.css +++ b/webroot/rsrc/css/phui/phui-form.css @@ -22,7 +22,7 @@ div.jx-tokenizer-container { display: inline-block; height: 30px; line-height: 18px; - color: #333; + color: inherit; vertical-align: middle; font: {$basefont}; -webkit-font-smoothing: antialiased; @@ -45,7 +45,7 @@ input[type="tel"], input[type="color"], div.jx-tokenizer-container { padding: 4px 6px; - background-color: #ffffff; + background-color: {$page.content}; border: 1px solid {$greyborder}; border-radius: 3px; @@ -105,10 +105,9 @@ select { -moz-appearance: none; appearance: none; - background: #fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAMAAACzvE1FAAAADFBMVEUzMzMzMzMzMzMzMzMKAG/3AAAAA3RSTlMAf4C/aSLHAAAAPElEQVR42q3NMQ4AIAgEQTn//2cLdRKppSGzBYwzVXvznNWs8C58CiussPJj8h6NwgorrKRdTvuV9v16Afn0AYFOB7aYAAAAAElFTkSuQmCC") no-repeat right 8px center; + background: {$page.content} url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAMAAACzvE1FAAAADFBMVEUzMzMzMzMzMzMzMzMKAG/3AAAAA3RSTlMAf4C/aSLHAAAAPElEQVR42q3NMQ4AIAgEQTn//2cLdRKppSGzBYwzVXvznNWs8C58CiussPJj8h6NwgorrKRdTvuV9v16Afn0AYFOB7aYAAAAAElFTkSuQmCC") no-repeat right 8px center; background-size: 8px 10px; border-radius: 3px; - color: {$darkbluetext}; border: 1px solid {$greyborder}; height: 30px; padding: 0 24px 0 8px; diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index 5c24d5575e..18b1464e53 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -34,10 +34,6 @@ vertical-align: middle; } -.device-phone .phui-header-col3 { - vertical-align: top; -} - body .phui-header-shell.phui-header-no-backgound { background-color: transparent; border: none; @@ -120,7 +116,7 @@ body .phui-header-shell.phui-bleed-header .device-phone .phui-header-action-link .phui-button-text { visibility: hidden; width: 0; - margin-left: 4px; + margin-left: 8px; } .device-phone .phui-header-action-link.button .phui-icon-view { @@ -187,8 +183,8 @@ body .phui-header-shell.phui-bleed-header margin-right: 4px; } -.phui-header-subheader .phui-tag-view .phui-icon-view, -.phui-header-subheader .policy-header-callout .phui-icon-view { +.phui-header-subheader .phui-tag-view span.phui-icon-view, +.phui-header-subheader .policy-header-callout span.phui-icon-view { display: inline-block; margin: -2px 4px -2px 0; font-size: 15px; @@ -307,14 +303,6 @@ body .phui-header-shell.phui-bleed-header text-decoration: underline; } -.phui-header-subheader .phui-badge-flex-view { - display: inline; - margin-right: 4px; -} - -.phui-header-subheader .phui-badge-flex-view:after { - display: inline; -} /*** Profile Header ***********************************************************/ @@ -346,14 +334,14 @@ body .phui-header-shell.phui-bleed-header .phui-profile-header.phui-header-shell .phui-header-header { font-size: 24px; - color: #000; + color: {$blacktext}; } -.phui-profile-header .phui-header-col3 { - vertical-align: top; +.phui-profile-header.phui-header-shell .phui-header-header a { + color: {$blacktext}; } -.phui-header-view .phui-tag-shade-indigo a { +.phui-header-view .phui-tag-indigo a { color: {$sh-indigotext}; } diff --git a/webroot/rsrc/css/phui/phui-hovercard.css b/webroot/rsrc/css/phui/phui-hovercard.css index 0daa904ade..1f01362b6f 100644 --- a/webroot/rsrc/css/phui/phui-hovercard.css +++ b/webroot/rsrc/css/phui/phui-hovercard.css @@ -19,7 +19,7 @@ box-shadow: {$dropshadow}; border: 1px solid {$lightblueborder}; border-radius: 3px; - background-color: #fff; + background-color: {$page.content}; } .phui-hovercard-head .phui-header-shell { diff --git a/webroot/rsrc/css/phui/phui-icon.css b/webroot/rsrc/css/phui/phui-icon.css index 22b33bca11..acc7818765 100644 --- a/webroot/rsrc/css/phui/phui-icon.css +++ b/webroot/rsrc/css/phui/phui-icon.css @@ -45,6 +45,10 @@ img.phui-image-disabled { filter: grayscale(100%); } +.phui-icon-view.bluetext { + color: {$bluetext}; +} + /* - Icon in a Circle ------------------------------------------------------- */ .phui-icon-circle { @@ -57,6 +61,7 @@ img.phui-image-disabled { cursor: pointer; background: transparent; padding: 0; + position: relative; } .phui-icon-circle.circle-medium { @@ -65,7 +70,21 @@ img.phui-image-disabled { border-radius: 36px; } -.phui-icon-circle .phui-icon-view { +.phui-icon-circle.phui-icon-circle-state { + border-color: transparent; + background-color: {$bluebackground}; +} + +.phui-icon-circle.phui-icon-circle-state .phui-icon-circle-icon { + color: {$bluetext}; + font-size: 16px; +} + +a.phui-icon-circle.phui-icon-circle-state:hover { + border-color: transparent !important; +} + +.phui-icon-circle .phui-icon-circle-icon { height: 24px; width: 24px; font-size: 11px; @@ -74,7 +93,7 @@ img.phui-image-disabled { cursor: pointer; } -.phui-icon-circle.circle-medium .phui-icon-view { +.phui-icon-circle.circle-medium .phui-icon-circle-icon { font-size: 18px; line-height: 36px; } @@ -129,6 +148,21 @@ a.phui-icon-circle.hover-red:hover .phui-icon-view { color: {$red}; } +.phui-icon-circle .phui-icon-view.phui-icon-circle-state-icon { + position: absolute; + width: 14px; + height: 14px; + display: inline-block; + font-size: 12px; + right: -3px; + top: -4px; + text-shadow: + -1px -1px 0 #fff, + 1px -1px 0 #fff, + -1px 1px 0 #fff, + 1px 1px 0 #fff; +} + /* - Icon in a Square ------------------------------------------------------- */ .phui-icon-view.phui-icon-square { diff --git a/webroot/rsrc/css/phui/phui-info-panel.css b/webroot/rsrc/css/phui/phui-info-panel.css deleted file mode 100644 index a182292a89..0000000000 --- a/webroot/rsrc/css/phui/phui-info-panel.css +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @provides phui-info-panel-css - */ - -.phui-info-panel .phui-object-box .phui-header-has-image { - padding: 2px 0 0 2px; -} - -.phui-info-panel .phui-object-box .phui-header-image { - margin: 0 8px 0 0; -} - -.phui-info-panel-table { - border-collapse: collapse; - border-style: hidden; - width: 100%; -} - -.phui-info-panel-table td, -.phui-info-panel-table th { - border: 1px solid {$thinblueborder}; -} - -.phui-info-panel-table-cell { - padding: 8px; -} - -.phui-info-panel-number, -.phui-info-panel-number a { - font-size: 30px; - font-weight: bold; - color: {$lightgreytext}; - -webkit-font-smoothing: antialiased; -} - -.phui-info-panel-text, -.phui-info-panel-text a { - color: {$lightgreytext}; -} - -.phui-info-panel-number a:hover, -.phui-info-panel-text a:hover { - color: {$greytext}; - text-decoration: none; -} - -.phui-info-panel-progress { - background: {$green}; - height: 6px; -} diff --git a/webroot/rsrc/css/phui/phui-info-view.css b/webroot/rsrc/css/phui/phui-info-view.css index 1642822b77..55400956e4 100644 --- a/webroot/rsrc/css/phui/phui-info-view.css +++ b/webroot/rsrc/css/phui/phui-info-view.css @@ -5,12 +5,20 @@ .phui-info-view { border-style: solid; border-width: 1px; - background: #fff; + background: {$page.content}; margin: 16px; padding: 12px; border-radius: 3px; } +div.phui-info-view.phui-info-severity-plain { + background: {$lightgreybackground}; + color: {$bluetext}; + border: none; + padding: 8px 12px; + margin-bottom: 4px !important; +} + .phui-info-view.phui-info-view-flush { margin: 0 0 20px 0; } @@ -30,6 +38,7 @@ .phui-info-view-body { line-height: 1.6em; + color: {$blacktext}; } .phui-info-view.phui-info-has-icon .phui-info-view-body { @@ -37,8 +46,11 @@ } .phui-info-view-body tt { - padding: 0 2px; - background-color: rgba({$alphagrey},.1); + color: {$blacktext}; + background: rgba({$alphablue},0.1); + padding: 1px 4px; + border-radius: 3px; + white-space: pre-wrap; } .phui-info-view-actions { @@ -127,3 +139,7 @@ h1.phui-info-view-head { div.phui-object-box .phui-header-shell + .phui-info-view { margin: 16px 0 8px; } + +div.phui-object-box.phui-box-white-config .phui-header-shell + .phui-info-view { + margin: 20px 16px 8px; +} diff --git a/webroot/rsrc/css/phui/phui-invisible-character-view.css b/webroot/rsrc/css/phui/phui-invisible-character-view.css index b8a848fa9a..a6a90da536 100644 --- a/webroot/rsrc/css/phui/phui-invisible-character-view.css +++ b/webroot/rsrc/css/phui/phui-invisible-character-view.css @@ -4,7 +4,7 @@ .invisible-special { font-family: monospace; - color: #000; + color: {$blacktext}; background: rgba({$alphablue},0.1); padding: 1px 4px; border-radius: 3px; diff --git a/webroot/rsrc/css/phui/phui-left-right.css b/webroot/rsrc/css/phui/phui-left-right.css new file mode 100644 index 0000000000..ac092cd226 --- /dev/null +++ b/webroot/rsrc/css/phui/phui-left-right.css @@ -0,0 +1,40 @@ +/** + * @provides phui-left-right-css + */ + +.phui-left-right-view { + display: table; + width: 100%; +} + +.phui-lr-container { + display: table-row; +} + +.phui-left-view { + display: table-cell; + text-align: left; +} + +.phui-right-view { + display: table-cell; + text-align: right; +} + +.phui-left-view .button { + margin-right: 8px; +} + +.phui-right-view .button { + margin-left: 8px; +} + +.phui-lr-view-top .phui-left-view, +.phui-lr-view-top .phui-right-view { + vertical-align: top; +} + +.phui-lr-view-bottom .phui-left-view, +.phui-lr-view-bottom .phui-right-view { + vertical-align: bottom; +} diff --git a/webroot/rsrc/css/phui/phui-lightbox.css b/webroot/rsrc/css/phui/phui-lightbox.css index 910d902a3b..611b9c97a4 100644 --- a/webroot/rsrc/css/phui/phui-lightbox.css +++ b/webroot/rsrc/css/phui/phui-lightbox.css @@ -80,7 +80,7 @@ right: 0; width: 360px; overflow-y: auto; - background: #fff; + background: {$page.content}; opacity: 1; } @@ -100,7 +100,7 @@ } .lightbox-attachment .lightbox-status { - background: #fff; + background: {$page.content}; position: fixed; top: 0; left: 0; @@ -121,7 +121,7 @@ } .lightbox-attachment .lightbox-status-txt a { - color: #000; + color: {$blacktext}; margin-right: 12px; font-size: {$biggerfontsize}; } diff --git a/webroot/rsrc/css/phui/phui-list.css b/webroot/rsrc/css/phui/phui-list.css index e571e228d8..5fab89bcd8 100644 --- a/webroot/rsrc/css/phui/phui-list.css +++ b/webroot/rsrc/css/phui/phui-list.css @@ -88,7 +88,7 @@ } .device-desktop .phui-list-sidenav .phui-list-item-href:hover .phui-icon-view { - color: #fff; + color: {$page.content}; } /* - Top, Full Width Navigations ----------------------------------------------- @@ -144,6 +144,76 @@ border: none; } +/* - Two Column View, Responsive Navigations ----------------------------------- + + Sets a two column page with a responsive, top navbar + +*/ + +.phui-list-view.phui-list-tabbar { + list-style: none; + overflow: hidden; +} + +.phui-list-view.phui-list-tabbar > li { + list-style: none; + float: left; + display: block; +} + +.phui-list-view.phui-list-tabbar > li > * { + display: block; +} + +.phui-list-tabbar .phui-list-item-href { + color: {$bluetext}; + padding: 8px 24px; + line-height: 24px; + font-weight: bold; + font-size: {$biggerfontsize}; + border-top: 4px solid transparent; +} + +.phui-list-tabbar .phui-list-item-selected .phui-list-item-href { + color: {$sky}; + border-bottom: 4px solid {$sky}; +} + +.phui-list-tabbar .phui-list-item-selected .phui-list-item-href + .phui-icon-view { + color: {$sky}; +} + +.device-desktop .phui-list-tabbar .phui-list-item-href:hover { + color: {$sky}; + text-decoration: none; +} + +.phui-list-tabbar .phui-list-item-icon { + height: 20px; + width: 20px; + display: none; + font-size: 20px; + text-align: center; +} + +.device-phone .phui-list-tabbar .phui-list-item-icon { + display: inline-block; +} + +.device-phone .phui-list-tabbar .phui-list-item-name { + display: none; +} + +.device-phone .phui-list-tabbar .phui-list-item-href { + padding: 8px 16px; +} + +.device-phone .phui-list-view.phui-list-navbar > li { + float: none; + border: none; +} + /* - Status Colors ------------------------------------------------------------- Colors for navbars diff --git a/webroot/rsrc/css/phui/phui-pager.css b/webroot/rsrc/css/phui/phui-pager.css index 590b8514ce..462ef2ad22 100644 --- a/webroot/rsrc/css/phui/phui-pager.css +++ b/webroot/rsrc/css/phui/phui-pager.css @@ -6,3 +6,9 @@ clear: both; text-align: center; } + +.phui-pager-view a.button.current, +.phui-pager-view a.button.current:hover { + border-color: {$sh-orangetext}; + color: {$sh-orangetext}; +} diff --git a/webroot/rsrc/css/phui/phui-pinboard-view.css b/webroot/rsrc/css/phui/phui-pinboard-view.css index d632f13795..1fb4e95792 100644 --- a/webroot/rsrc/css/phui/phui-pinboard-view.css +++ b/webroot/rsrc/css/phui/phui-pinboard-view.css @@ -16,7 +16,7 @@ margin: 0 12px 16px 0; text-align: left; width: 280px; - background: #fff; + background: {$page.content}; border: 1px solid {$lightblueborder}; border-radius: 3px; } @@ -95,7 +95,7 @@ .dashboard-panel .phui-pinboard-view { margin: 0; padding: 16px 12px 0 12px; - background: #fff; + background: {$page.content}; border-left: 1px solid {$lightblueborder}; border-right: 1px solid {$lightblueborder}; border-bottom: 1px solid {$blueborder}; diff --git a/webroot/rsrc/css/phui/phui-tag-view.css b/webroot/rsrc/css/phui/phui-tag-view.css index d5b2710d7b..73675a44d6 100644 --- a/webroot/rsrc/css/phui/phui-tag-view.css +++ b/webroot/rsrc/css/phui/phui-tag-view.css @@ -63,12 +63,12 @@ a.phui-tag-view:hover { a.phui-tag-type-object, a.phui-tag-type-object:link, .phui-tag-core-closed .phui-tag-color-object { - color: #000; + color: {$blacktext}; } .phui-tag-type-person { white-space: nowrap; - color: #19558d; + color: {$anchor}; } .phui-tag-color-red { @@ -107,8 +107,8 @@ a.phui-tag-type-object:link, } .phui-tag-color-black { - background-color: #333333; - border-color: #333333; + background-color: {$darkgreybackground}; + border-color: {$darkgreybackground}; } .phui-tag-color-grey { @@ -117,28 +117,28 @@ a.phui-tag-type-object:link, } .phui-tag-color-white { - background-color: #f7f7f7; - border-color: #f7f7f7; + background-color: {$lightgreybackground}; + border-color: {$lightgreybackground}; } .phui-tag-color-object { - background-color: #e7e7e7; - border-color: #e7e7e7; + background-color: {$greybackground}; + border-color: {$lightgreyborder}; } .phui-tag-color-person { - background-color: #f1f7ff; - border-color: #f1f7ff; + background-color: {$bluebackground}; + border-color: {$thinblueborder}; } a.phui-tag-view:hover .phui-tag-core.phui-tag-color-person { - border-color: #d9ebfd; + border-color: {$lightblueborder}; } a.phui-tag-view:hover .phui-tag-core.phui-tag-color-object { - border-color: #d7d7d7; + border-color: {$greyborder}; } .phabricator-handle-tag-list-item + .phabricator-handle-tag-list-item { @@ -154,6 +154,15 @@ a.phui-tag-view:hover margin: 0 4px 2px 0; } +.phui-tag-view.phui-tag-border-none .phui-tag-core { + border-color: transparent; +} + +a.phui-tag-view:hover.phui-tag-border-none .phui-tag-core { + border-color: transparent !important; + text-decoration: underline; +} + /* - Shaded Tags --------------------------------------------------------------- @@ -161,225 +170,233 @@ a.phui-tag-view:hover */ -.phui-tag-shade { +.phui-tag-view.phui-tag-type-shade { font-weight: normal; } -.phui-tag-shade .phui-icon-view { +.phui-tag-view.phui-tag-type-shade .phui-icon-view { font-size: 12px; } -.phui-tag-shade-slim .phui-icon-view { + +/* - Slim Tags ----------------------------------------------------------------- + + A thinner tag for object list, workboards. + +*/ + +.phui-tag-slim .phui-icon-view { font-size: 11px; } -.phui-tag-shade-slim .phui-tag-core { +.phui-tag-slim .phui-tag-core { font-size: {$smallerfontsize}; } + /* - Red -------------------------------------------------------------------- */ -.phui-tag-shade-red .phui-tag-core, +.phui-tag-red .phui-tag-core, .jx-tokenizer-token.red { background: {$sh-redbackground}; border-color: {$sh-lightredborder}; color: {$sh-redtext}; } -.phui-tag-shade-red .phui-icon-view, +.phui-tag-red .phui-icon-view, .jx-tokenizer-token.red .phui-icon-view, .jx-tokenizer-token.red .jx-tokenizer-x { color: {$sh-redicon}; } -a.phui-tag-view:hover.phui-tag-shade-red .phui-tag-core, +a.phui-tag-view:hover.phui-tag-red .phui-tag-core, .jx-tokenizer-token.red:hover { border-color: {$sh-redborder}; } /* - Orange ----------------------------------------------------------------- */ -.phui-tag-shade-orange .phui-tag-core, +.phui-tag-orange .phui-tag-core, .jx-tokenizer-token.orange { background: {$sh-orangebackground}; border-color: {$sh-lightorangeborder}; color: {$sh-orangetext}; } -.phui-tag-shade-orange .phui-icon-view, +.phui-tag-orange .phui-icon-view, .jx-tokenizer-token.orange .phui-icon-view, .jx-tokenizer-token.orange .jx-tokenizer-x { color: {$sh-orangeicon}; } -a.phui-tag-view:hover.phui-tag-shade-orange .phui-tag-core, +a.phui-tag-view:hover.phui-tag-orange .phui-tag-core, .jx-tokenizer-token.orange:hover { border-color: {$sh-orangeborder}; } /* - Yellow ----------------------------------------------------------------- */ -.phui-tag-shade-yellow .phui-tag-core, +.phui-tag-yellow .phui-tag-core, .jx-tokenizer-token.yellow { background: {$sh-yellowbackground}; border-color: {$sh-lightyellowborder}; color: {$sh-yellowtext}; } -.phui-tag-shade-yellow .phui-icon-view, +.phui-tag-yellow .phui-icon-view, .jx-tokenizer-token.yellow .phui-icon-view, .jx-tokenizer-token.yellow .jx-tokenizer-x { color: {$sh-yellowicon}; } -a.phui-tag-view:hover.phui-tag-shade-yellow .phui-tag-core, +a.phui-tag-view:hover.phui-tag-yellow .phui-tag-core, .jx-tokenizer-token.yellow:hover { border-color: {$sh-yellowborder}; } /* - Blue ------------------------------------------------------------------- */ -.phui-tag-shade-blue .phui-tag-core, +.phui-tag-blue .phui-tag-core, .jx-tokenizer-token.blue { background: {$sh-bluebackground}; border-color: {$sh-lightblueborder}; color: {$sh-bluetext}; } -.phui-tag-shade-blue .phui-icon-view, +.phui-tag-blue .phui-icon-view, .jx-tokenizer-token.blue .phui-icon-view, .jx-tokenizer-token.blue .jx-tokenizer-x { color: {$sh-blueicon}; } -a.phui-tag-view:hover.phui-tag-shade-blue .phui-tag-core, +a.phui-tag-view:hover.phui-tag-blue .phui-tag-core, .jx-tokenizer-token.blue:hover { border-color: {$sh-blueborder}; } /* - Sky ------------------------------------------------------------------- */ -.phui-tag-shade-sky .phui-tag-core, +.phui-tag-sky .phui-tag-core, .jx-tokenizer-token.sky { background: #E0F0FA; border-color: {$sh-lightblueborder}; color: {$sh-bluetext}; } -.phui-tag-shade-sky .phui-icon-view, +.phui-tag-sky .phui-icon-view, .jx-tokenizer-token.sky .phui-icon-view, .jx-tokenizer-token.sky .jx-tokenizer-x { color: {$sh-blueicon}; } -a.phui-tag-view:hover.phui-tag-shade-sky .phui-tag-core, +a.phui-tag-view:hover.phui-tag-sky .phui-tag-core, .jx-tokenizer-token.sky:hover { border-color: {$sh-blueborder}; } /* - Indigo ----------------------------------------------------------------- */ -.phui-tag-shade-indigo .phui-tag-core, +.phui-tag-indigo .phui-tag-core, .jx-tokenizer-token.indigo { background: {$sh-indigobackground}; border-color: {$sh-lightindigoborder}; color: {$sh-indigotext}; } -.phui-tag-shade-indigo .phui-icon-view, +.phui-tag-indigo .phui-icon-view, .jx-tokenizer-token.indigo .phui-icon-view, .jx-tokenizer-token.indigo .jx-tokenizer-x { color: {$sh-indigoicon}; } -a.phui-tag-view:hover.phui-tag-shade-indigo .phui-tag-core, +a.phui-tag-view:hover.phui-tag-indigo .phui-tag-core, .jx-tokenizer-token.indigo:hover { border-color: {$sh-indigoborder}; } /* - Green ------------------------------------------------------------------ */ -.phui-tag-shade-green .phui-tag-core, +.phui-tag-green .phui-tag-core, .jx-tokenizer-token.green { background: {$sh-greenbackground}; border-color: {$sh-lightgreenborder}; color: {$sh-greentext}; } -.phui-tag-shade-green .phui-icon-view, +.phui-tag-green .phui-icon-view, .jx-tokenizer-token.green .phui-icon-view, .jx-tokenizer-token.green .jx-tokenizer-x { color: {$sh-greenicon}; } -a.phui-tag-view:hover.phui-tag-shade-green .phui-tag-core, +a.phui-tag-view:hover.phui-tag-green .phui-tag-core, .jx-tokenizer-token.green:hover { border-color: {$sh-greenborder}; } /* - Violet ----------------------------------------------------------------- */ -.phui-tag-shade-violet .phui-tag-core, +.phui-tag-violet .phui-tag-core, .jx-tokenizer-token.violet { background: {$sh-violetbackground}; border-color: {$sh-lightvioletborder}; color: {$sh-violettext}; } -.phui-tag-shade-violet .phui-icon-view, +.phui-tag-violet .phui-icon-view, .jx-tokenizer-token.violet .phui-icon-view, .jx-tokenizer-token.violet .jx-tokenizer-x { color: {$sh-violeticon}; } -a.phui-tag-view:hover.phui-tag-shade-violet .phui-tag-core, +a.phui-tag-view:hover.phui-tag-violet .phui-tag-core, .jx-tokenizer-token.violet:hover { border-color: {$sh-violetborder}; } /* - Pink ------------------------------------------------------------------- */ -.phui-tag-shade-pink .phui-tag-core, +.phui-tag-pink .phui-tag-core, .jx-tokenizer-token.pink { background: {$sh-pinkbackground}; border-color: {$sh-lightpinkborder}; color: {$sh-pinktext}; } -.phui-tag-shade-pink .phui-icon-view, +.phui-tag-pink .phui-icon-view, .jx-tokenizer-token.pink .phui-icon-view, .jx-tokenizer-token.pink .jx-tokenizer-x { color: {$sh-pinkicon}; } -a.phui-tag-view:hover.phui-tag-shade-pink .phui-tag-core, +a.phui-tag-view:hover.phui-tag-pink .phui-tag-core, .jx-tokenizer-token.pink:hover { border-color: {$sh-pinkborder}; } /* - Grey ------------------------------------------------------------------- */ -.phui-tag-shade-grey .phui-tag-core, +.phui-tag-grey .phui-tag-core, .jx-tokenizer-token.grey { background: {$sh-greybackground}; border-color: {$sh-lightgreyborder}; color: {$sh-greytext}; } -.phui-tag-shade-grey .phui-icon-view, +.phui-tag-grey .phui-icon-view, .jx-tokenizer-token.grey .phui-icon-view, .jx-tokenizer-token.grey .jx-tokenizer-x { color: {$sh-greyicon}; } -a.phui-tag-view:hover.phui-tag-shade-grey .phui-tag-core, +a.phui-tag-view:hover.phui-tag-grey .phui-tag-core, .jx-tokenizer-token.grey:hover { border-color: {$sh-greyborder}; } /* - Checkered -------------------------------------------------------------- */ -.phui-tag-shade-checkered .phui-tag-core, +.phui-tag-checkered .phui-tag-core, .jx-tokenizer-token.checkered { background: url(/rsrc/image/checker_lighter.png); border-style: dashed; @@ -388,13 +405,13 @@ a.phui-tag-view:hover.phui-tag-shade-grey .phui-tag-core, text-shadow: 1px 1px #fff; } -.phui-tag-shade-checkered .phui-icon-view, +.phui-tag-checkered .phui-icon-view, .jx-tokenizer-token.checkered .phui-icon-view, .jx-tokenizer-token.checkered .jx-tokenizer-x { color: {$sh-greyicon}; } -a.phui-tag-view:hover.phui-tag-shade-checkered .phui-tag-core, +a.phui-tag-view:hover.phui-tag-checkered .phui-tag-core, .jx-tokenizer-token.checkered:hover { border-style: solid; border-color: {$sh-greyborder}; @@ -402,16 +419,101 @@ a.phui-tag-view:hover.phui-tag-shade-checkered .phui-tag-core, /* - Disabled --------------------------------------------------------------- */ -.phui-tag-shade-disabled .phui-tag-core { +.phui-tag-disabled .phui-tag-core { background-color: {$sh-disabledbackground}; border-color: {$sh-lightdisabledborder}; color: {$sh-disabledtext}; } -.phui-tag-shade-disabled .phui-icon-view { +.phui-tag-disabled .phui-icon-view { color: {$sh-disabledicon}; } -a.phui-tag-view:hover.phui-tag-shade-disabled .phui-tag-core { +a.phui-tag-view:hover.phui-tag-disabled .phui-tag-core { border-color: {$sh-disabledborder}; } + +/* - Outline Tags -------------------------------------------------------------- + + Basic Tag with a bold border and white background + +*/ + +.phui-tag-type-outline { + text-transform: uppercase; + font-weight: normal; +} + +.phui-tag-view.phui-tag-type-outline .phui-tag-core { + background: #fff; + padding: 0 6px 1px 6px; +} + +.phui-tag-slim.phui-tag-type-outline .phui-tag-core { + font-size: {$smallestfontsize}; +} + +.phui-tag-type-outline.phui-tag-red .phui-tag-core { + color: {$red}; + border-color: {$red}; +} + +.phui-tag-type-outline.phui-tag-orange .phui-tag-core { + color: {$orange}; + border-color: {$orange}; +} + +.phui-tag-type-outline.phui-tag-yellow .phui-tag-core { + color: {$yellow}; + border-color: {$yellow}; +} + +.phui-tag-type-outline.phui-tag-green .phui-tag-core { + color: {$green}; + border-color: {$green}; +} + +.phui-tag-type-outline.phui-tag-blue .phui-tag-core { + color: {$blue}; + border-color: {$blue}; +} + +.phui-tag-type-outline.phui-tag-indigo .phui-tag-core { + color: {$indigo}; + border-color: {$indigo}; +} + +.phui-tag-type-outline.phui-tag-violet .phui-tag-core { + color: {$violet}; + border-color: {$violet}; +} + +.phui-tag-type-outline.phui-tag-grey .phui-tag-core { + color: {$bluetext}; + border-color: {$bluetext}; +} + +.phui-tag-type-outline.phui-tag-disabled .phui-tag-core { + color: {$lightgreytext}; + border-color: {$lightgreytext}; +} + +.phui-tag-type-outline.phui-tag-pink .phui-tag-core { + color: {$pink}; + border-color: {$pink}; +} + +.phui-tag-type-outline.phui-tag-sky .phui-tag-core { + color: {$sky}; + border-color: {$sky}; +} + +.phui-tag-type-outline.phui-tag-fire .phui-tag-core { + color: {$fire}; + border-color: {$fire}; +} + +.phui-tag-type-outline.phui-tag-black .phui-tag-core { + color: {$blacktext}; + border-color: {$blacktext}; +} diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index c4f568411a..33e0f8ed0c 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -179,7 +179,7 @@ .phui-timeline-core-content { padding: 16px; line-height: 18px; - background: #fff; + background: {$page.content}; border-top: 1px solid rgba({$alphablue},.1); border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; @@ -221,7 +221,7 @@ } .phui-timeline-icon { - color: {$sh-blueicon}; + color: {$bluetext}; } .phui-icon-view.phui-timeline-icon { @@ -232,7 +232,7 @@ height: 26px; width: 26px; border-radius: 3px; - background-color: #E6E9F1; + background-color: {$timeline.icon.background}; } .phui-timeline-major-event .phui-timeline-icon-fill { @@ -260,7 +260,7 @@ .conpherence-transaction-content .phui-timeline-value, .phui-feed-story-head .phui-timeline-value { font-style: italic; - color: black; + color: {$blacktext}; } .device-desktop .phui-timeline-extra { @@ -280,7 +280,7 @@ } .phui-timeline-icon-fill.fill-has-color .phui-icon-view { - color: #fff; + color: {$page.content}; } .phui-timeline-icon-fill-red { @@ -344,8 +344,8 @@ } .phui-timeline-older-transactions-are-hidden { - background: {$sh-yellowbackground}; - border: 1px solid {$sh-yellowborder}; + background: {$gentle.highlight}; + border: 1px solid {$gentle.highlight.border}; text-align: center; padding: 12px; color: {$darkgreytext}; @@ -429,3 +429,9 @@ a.phui-timeline-menu .phui-icon-view { .phui-comment-preview-view { margin-bottom: 20px; } + +.phui-timeline-view .phui-pinboard-view { + margin: 8px 0 0 0; + padding: 0; + text-align: left; +} diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index d910cf71a0..2fb0eccee2 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -2,23 +2,33 @@ * @provides phui-two-column-view-css */ +.phui-two-column-fixed { + max-width: 1140px; + margin: 0 auto; +} + .phui-two-column-view .phui-two-column-header { - background-color: #fff; + background-color: {$page.content}; border-bottom: 1px solid rgba({$alphagrey}, .12); margin-bottom: 24px; } +.phui-two-column-view.without-header { + margin-top: 24px; +} + .device .phui-two-column-view .phui-two-column-header { margin-bottom: 12px; } +.phui-two-column-view.with-tabs .phui-two-column-header, .phui-two-column-view.with-subheader .phui-two-column-header { margin-bottom: 0; } .phui-two-column-header .phui-header-header { font-size: 20px; - color: #000; + color: {$blacktext}; } .device-phone .phui-two-column-header .phui-header-header { @@ -168,6 +178,26 @@ margin: 0 8px; } +.phui-two-column-tabs { + padding: 0 32px; + margin-bottom: 32px; + background: {$page.content}; + box-shadow: 0 1px 3px 0 rgba(0,0,0,0.2); +} + +.device-phone .phui-two-column-tabs { + padding: 0 12px; +} + +.device-phone .phui-two-column-tabs .phui-list-view.phui-list-tabbar { + text-align: center; +} + +.device-phone .phui-two-column-tabs .phui-list-view.phui-list-tabbar > li { + float: none; + display: inline-block; +} + /* Info View */ .phui-two-column-view .phui-info-view { @@ -218,3 +248,62 @@ .phui-document-view { margin: 0 0 20px 0; } + +/*- Fixed Styles with Navigation -------------------------------------------- */ + +.phui-two-column-fixed.phui-two-column-view .phui-two-column-header { + background: transparent; + border: none; + margin-bottom: 0; +} + +.phui-two-column-fixed.phui-two-column-view .phui-side-column + .phui-box-border { + background: transparent; + border: none; + padding: 0; + width: 180px; +} + +.device-desktop + .phui-two-column-fixed.phui-two-column-view.phui-side-column-left + .phui-side-column { + width: 200px; +} + +.device-desktop + .phui-two-column-fixed.phui-two-column-view.phui-side-column-left + .phui-main-column { + width: calc(100% - 200px) +} + +.phui-two-column-fixed.phui-two-column-view .phui-basic-nav + .phabricator-side-menu { + background: transparent; +} + +.phui-two-column-fixed.phui-two-column-view + .phui-basic-nav .phabricator-side-menu .phui-list-item-selected { + border-radius: 3px; + background-color: {$sky}; +} + +.phui-two-column-fixed.phui-two-column-view .phui-basic-nav + .phabricator-side-menu .phui-list-item-href { + border-radius: 3px; +} + +.phui-two-column-fixed.phui-two-column-view .phui-basic-nav + .phabricator-side-menu .phui-list-item-selected a { + color: #fff; +} + +.phui-two-column-fixed.phui-two-column-view .phui-basic-nav + .phabricator-side-menu .phui-list-item-selected a .phui-icon-view { + color: #fff; +} + +.phui-two-column-fixed.phui-two-column-view .phui-header-action-links + .phui-mobile-menu { + display: block; +} diff --git a/webroot/rsrc/css/phui/workboards/phui-workboard-color.css b/webroot/rsrc/css/phui/workboards/phui-workboard-color.css index e07bc6597f..9973d64979 100644 --- a/webroot/rsrc/css/phui/workboards/phui-workboard-color.css +++ b/webroot/rsrc/css/phui/workboards/phui-workboard-color.css @@ -7,7 +7,7 @@ } .phui-workboard-no-color { - background-color: #fff; + background-color: {$page.content}; } .phui-workboard-color .phui-crumbs-view { diff --git a/webroot/rsrc/css/phui/workboards/phui-workcard.css b/webroot/rsrc/css/phui/workboards/phui-workcard.css index 5af162b4bc..e137e962bc 100644 --- a/webroot/rsrc/css/phui/workboards/phui-workcard.css +++ b/webroot/rsrc/css/phui/workboards/phui-workcard.css @@ -3,7 +3,7 @@ */ .phui-workcard.phui-oi { - background-color: #fff; + background-color: {$page.content}; border-radius: 3px; margin-bottom: 8px; border-left-width: 4px; @@ -37,7 +37,7 @@ .phui-workcard .phui-oi-link { white-space: normal; font-weight: normal; - color: #000; + color: {$blacktext}; margin-left: 2px; } diff --git a/webroot/rsrc/css/sprite-login.css b/webroot/rsrc/css/sprite-login.css index 72638120f2..ec4e082031 100644 --- a/webroot/rsrc/css/sprite-login.css +++ b/webroot/rsrc/css/sprite-login.css @@ -14,7 +14,7 @@ only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) { .sprite-login { background-image: url(/rsrc/image/sprite-login-X2.png); - background-size: 145px 174px; + background-size: 145px 145px; } } @@ -35,28 +35,24 @@ only screen and (min-resolution: 1.5dppx) { background-position: -87px 0px; } -.login-Dropbox { +.login-Facebook { background-position: -116px 0px; } -.login-Facebook { +.login-Generic { background-position: 0px -29px; } -.login-Generic { +.login-Github { background-position: -29px -29px; } -.login-Github { +.login-Google { background-position: -58px -29px; } -.login-Google { - background-position: -87px -29px; -} - .login-HTTP { - background-position: -116px -29px; + background-position: -87px -29px; } .login-Jira { @@ -67,54 +63,42 @@ only screen and (min-resolution: 1.5dppx) { background-position: -29px -58px; } -.login-Linkedin { +.login-MediaWiki { background-position: -58px -58px; } -.login-MediaWiki { +.login-PayPal { background-position: -87px -58px; } -.login-Openid { - background-position: -116px -58px; -} - -.login-PayPal { +.login-Phabricator { background-position: 0px -87px; } -.login-Phabricator { - background-position: -58px -87px; -} - .login-Slack { - background-position: -87px -87px; + background-position: -29px -87px; } .login-Stripe { - background-position: -116px -87px; + background-position: -58px -87px; } .login-TestPayment { - background-position: 0px -116px; + background-position: -87px -87px; } .login-TwitchTV { - background-position: -29px -116px; + background-position: 0px -116px; } .login-Twitter { - background-position: -58px -116px; + background-position: -29px -116px; } .login-WePay { - background-position: -87px -116px; + background-position: -58px -116px; } .login-WordPressCOM { - background-position: -116px -116px; -} - -.login-Yahoo { - background-position: 0px -145px; + background-position: -87px -116px; } diff --git a/webroot/rsrc/externals/javelin/lib/DOM.js b/webroot/rsrc/externals/javelin/lib/DOM.js index 189d778ad5..e0e3d764e0 100644 --- a/webroot/rsrc/externals/javelin/lib/DOM.js +++ b/webroot/rsrc/externals/javelin/lib/DOM.js @@ -891,7 +891,7 @@ JX.install('DOM', { * it. * * @param Node Node to look above. - * @param string Tag name, like 'a' or 'textarea'. + * @param string Optional tag name, like 'a' or 'textarea'. * @param string Optionally, sigil which selected node must have. * @return Node Matching node. * @@ -911,7 +911,7 @@ JX.install('DOM', { if (!result) { break; } - if (JX.DOM.isType(result, tagname)) { + if (!tagname || JX.DOM.isType(result, tagname)) { if (!sigil || JX.Stratcom.hasSigil(result, sigil)) { break; } diff --git a/webroot/rsrc/externals/javelin/lib/Scrollbar.js b/webroot/rsrc/externals/javelin/lib/Scrollbar.js index 7596939581..cd69eb4def 100644 --- a/webroot/rsrc/externals/javelin/lib/Scrollbar.js +++ b/webroot/rsrc/externals/javelin/lib/Scrollbar.js @@ -34,7 +34,7 @@ JX.install('Scrollbar', { // width. If it doesn't, we're already in an environment with an aesthetic // scrollbar (like Safari on OSX with no mouse connected, or an iPhone) // and we don't need to do anything. - if (JX.Scrollbar._getScrollbarControlWidth() === 0) { + if (JX.Scrollbar.getScrollbarControlWidth() === 0) { return; } @@ -104,7 +104,7 @@ JX.install('Scrollbar', { /** * Compute the width of the browser's scrollbar control, in pixels. */ - _getScrollbarControlWidth: function() { + getScrollbarControlWidth: function() { var self = JX.Scrollbar; if (self._controlWidth === null) { @@ -140,7 +140,7 @@ JX.install('Scrollbar', { // If this browser and OS don't render a real scrollbar control, we // need to leave a margin. Generally, this is OSX with no mouse attached. - if (self._getScrollbarControlWidth() === 0) { + if (self.getScrollbarControlWidth() === 0) { return 12; } @@ -357,7 +357,7 @@ JX.install('Scrollbar', { */ _resizeViewport: function() { var fdim = JX.Vector.getDim(this._frame); - fdim.x += JX.Scrollbar._getScrollbarControlWidth(); + fdim.x += JX.Scrollbar.getScrollbarControlWidth(); fdim.setDim(this._viewport); }, diff --git a/webroot/rsrc/image/controls/checkbox-checked.png b/webroot/rsrc/image/controls/checkbox-checked.png new file mode 100644 index 0000000000..9e93e82a37 Binary files /dev/null and b/webroot/rsrc/image/controls/checkbox-checked.png differ diff --git a/webroot/rsrc/image/controls/checkbox-unchecked.png b/webroot/rsrc/image/controls/checkbox-unchecked.png new file mode 100644 index 0000000000..e60350d770 Binary files /dev/null and b/webroot/rsrc/image/controls/checkbox-unchecked.png differ diff --git a/webroot/rsrc/image/sprite-login-X2.png b/webroot/rsrc/image/sprite-login-X2.png index 78a044ee72..2d3c118745 100644 Binary files a/webroot/rsrc/image/sprite-login-X2.png and b/webroot/rsrc/image/sprite-login-X2.png differ diff --git a/webroot/rsrc/image/sprite-login.png b/webroot/rsrc/image/sprite-login.png index a877ad3754..2f79e7ad8a 100644 Binary files a/webroot/rsrc/image/sprite-login.png and b/webroot/rsrc/image/sprite-login.png differ diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js index 333e8daac1..2886aa0372 100644 --- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js +++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js @@ -82,6 +82,7 @@ JX.behavior('aphlict-listen', function(config) { new JX.Notification() .setContent(JX.$H(response.content)) .setDesktopReady(response.desktopReady) + .setWebReady(response.webReady) .setKey(response.primaryObjectPHID) .setTitle(response.title) .setBody(response.body) diff --git a/webroot/rsrc/js/application/aphlict/behavior-desktop-notifications-control.js b/webroot/rsrc/js/application/aphlict/behavior-desktop-notifications-control.js index 3a9b028ca3..9be9c510b0 100644 --- a/webroot/rsrc/js/application/aphlict/behavior-desktop-notifications-control.js +++ b/webroot/rsrc/js/application/aphlict/behavior-desktop-notifications-control.js @@ -84,12 +84,12 @@ JX.behavior('desktop-notifications-control', function(config, statics) { return; } var value = e.getTarget().value; - if (value == config.desktopMode) { - window.Notification.requestPermission( - function (permission) { - updateFormStatus(permission); - updateBrowserStatus(permission); - }); + if ((value == config.desktop) || (value == config.desktopOnly)) { + window.Notification.requestPermission( + function (permission) { + updateFormStatus(permission); + updateBrowserStatus(permission); + }); } else { var statusEl = JX.$(config.statusID); JX.DOM.hide(statusEl); diff --git a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js index c6c82bc3be..a521046d8e 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js +++ b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js @@ -177,7 +177,7 @@ JX.behavior('durable-column', function(config, statics) { var params = null; switch (action) { - case 'metadata': + case 'go_edit': threadManager.runUpdateWorkflowFromLink( link, { diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js new file mode 100644 index 0000000000..72eeae294a --- /dev/null +++ b/webroot/rsrc/js/application/diff/DiffChangeset.js @@ -0,0 +1,819 @@ +/** + * @provides phabricator-diff-changeset + * @requires javelin-dom + * javelin-util + * javelin-stratcom + * javelin-install + * javelin-workflow + * javelin-router + * javelin-behavior-device + * javelin-vector + * phabricator-diff-inline + * @javelin + */ + + +JX.install('DiffChangeset', { + + construct : function(node) { + this._node = node; + + var data = this._getNodeData(); + + this._renderURI = data.renderURI; + this._ref = data.ref; + this._whitespace = data.whitespace; + this._renderer = data.renderer; + this._highlight = data.highlight; + this._encoding = data.encoding; + this._loaded = data.loaded; + + this._leftID = data.left; + this._rightID = data.right; + + this._displayPath = JX.$H(data.displayPath); + this._icon = data.icon; + + this._inlines = []; + }, + + members: { + _node: null, + _loaded: false, + _sequence: 0, + _stabilize: false, + + _renderURI: null, + _ref: null, + _whitespace: null, + _renderer: null, + _highlight: null, + _encoding: null, + _undoTemplates: null, + + _leftID: null, + _rightID: null, + + _inlines: null, + _visible: true, + + _undoNode: null, + _displayPath: null, + + _changesetList: null, + _icon: null, + + getLeftChangesetID: function() { + return this._leftID; + }, + + getRightChangesetID: function() { + return this._rightID; + }, + + setChangesetList: function(list) { + this._changesetList = list; + return this; + }, + + getIcon: function() { + if (!this._visible) { + return 'fa-file-o'; + } + + return this._icon; + }, + + getColor: function() { + if (!this._visible) { + return 'grey'; + } + + return 'blue'; + }, + + getChangesetList: function() { + return this._changesetList; + }, + + /** + * Has the content of this changeset been loaded? + * + * This method returns `true` if a request has been fired, even if the + * response has not returned yet. + * + * @return bool True if the content has been loaded. + */ + isLoaded: function() { + return this._loaded; + }, + + + /** + * Configure stabilization of the document position on content load. + * + * When we dump the changeset into the document, we can try to stabilize + * the document scroll position so that the user doesn't feel like they + * are jumping around as things load in. This is generally useful when + * populating initial changes. + * + * However, if a user explicitly requests a content load by clicking a + * "Load" link or using the dropdown menu, this stabilization generally + * feels unnatural, so we don't use it in response to explicit user action. + * + * @param bool True to stabilize the next content fill. + * @return this + */ + setStabilize: function(stabilize) { + this._stabilize = stabilize; + return this; + }, + + + /** + * Should this changeset load immediately when the page loads? + * + * Normally, changes load immediately, but if a diff or commit is very + * large we stop doing this and have the user load files explicitly, or + * choose to load everything. + * + * @return bool True if the changeset should load automatically when the + * page loads. + */ + shouldAutoload: function() { + return this._getNodeData().autoload; + }, + + + /** + * Load this changeset, if it isn't already loading. + * + * This fires a request to fill the content of this changeset, provided + * there isn't already a request in flight. To force a reload, use + * @{method:reload}. + * + * @return this + */ + load: function() { + if (this._loaded) { + return this; + } + + return this.reload(); + }, + + + /** + * Reload the changeset content. + * + * This method always issues a request, even if the content is already + * loading. To load conditionally, use @{method:load}. + * + * @return this + */ + reload: function() { + this._loaded = true; + this._sequence++; + + var params = this._getViewParameters(); + var pht = this.getChangesetList().getTranslations(); + + var workflow = new JX.Workflow(this._renderURI, params) + .setHandler(JX.bind(this, this._onresponse, this._sequence)); + + this._startContentWorkflow(workflow); + + JX.DOM.setContent( + this._getContentFrame(), + JX.$N( + 'div', + {className: 'differential-loading'}, + pht('Loading...'))); + + return this; + }, + + /** + * Load missing context in a changeset. + * + * We do this when the user clicks "Show X Lines". We also expand all of + * the missing context when they "Show All Context". + * + * @param string Line range specification, like "0-40/0-20". + * @param node Row where the context should be rendered after loading. + * @param bool True if this is a bulk load of multiple context blocks. + * @return this + */ + loadContext: function(range, target, bulk) { + var params = this._getViewParameters(); + params.range = range; + + var pht = this.getChangesetList().getTranslations(); + + var container = JX.DOM.scry(target, 'td')[0]; + JX.DOM.setContent(container, pht('Loading...')); + JX.DOM.alterClass(target, 'differential-show-more-loading', true); + + var workflow = new JX.Workflow(this._renderURI, params) + .setHandler(JX.bind(this, this._oncontext, target)); + + if (bulk) { + // If we're loading a bunch of these because the viewer clicked + // "Show All Context" or similar, use lower-priority requests + // and draw a progress bar. + this._startContentWorkflow(workflow); + } else { + // If this is a single click on a context link, use a higher priority + // load without a chrome change. + workflow.start(); + } + + return this; + }, + + loadAllContext: function() { + var nodes = JX.DOM.scry(this._node, 'tr', 'context-target'); + for (var ii = 0; ii < nodes.length; ii++) { + var show = JX.DOM.scry(nodes[ii], 'a', 'show-more'); + for (var jj = 0; jj < show.length; jj++) { + var data = JX.Stratcom.getData(show[jj]); + if (data.type != 'all') { + continue; + } + this.loadContext(data.range, nodes[ii], true); + } + } + }, + + _startContentWorkflow: function(workflow) { + var routable = workflow.getRoutable(); + + routable + .setPriority(500) + .setType('content') + .setKey(this._getRoutableKey()); + + JX.Router.getInstance().queue(routable); + }, + + getDisplayPath: function() { + return this._displayPath; + }, + + /** + * Receive a response to a context request. + */ + _oncontext: function(target, response) { + // TODO: This should be better structured. + // If the response comes back with several top-level nodes, the last one + // is the actual context; the others are headers. Add any headers first, + // then copy the new rows into the document. + var markup = JX.$H(response.changeset).getFragment(); + var len = markup.childNodes.length; + var diff = JX.DOM.findAbove(target, 'table', 'differential-diff'); + + for (var ii = 0; ii < len - 1; ii++) { + diff.parentNode.insertBefore(markup.firstChild, diff); + } + + var table = markup.firstChild; + var root = target.parentNode; + this._moveRows(table, root, target); + root.removeChild(target); + + this._onchangesetresponse(response); + }, + + _moveRows: function(src, dst, before) { + var rows = JX.DOM.scry(src, 'tr'); + for (var ii = 0; ii < rows.length; ii++) { + + // Find the table this belongs to. If it's a sub-table, like a + // table in an inline comment, don't copy it. + if (JX.DOM.findAbove(rows[ii], 'table') !== src) { + continue; + } + + if (before) { + dst.insertBefore(rows[ii], before); + } else { + dst.appendChild(rows[ii]); + } + } + }, + + /** + * Get parameters which define the current rendering options. + */ + _getViewParameters: function() { + return { + ref: this._ref, + whitespace: this._whitespace || '', + renderer: this.getRenderer() || '', + highlight: this._highlight || '', + encoding: this._encoding || '' + }; + }, + + /** + * Get the active @{class:JX.Routable} for this changeset. + * + * After issuing a request with @{method:load} or @{method:reload}, you + * can adjust routable settings (like priority) by querying the routable + * with this method. Note that there may not be a current routable. + * + * @return JX.Routable|null Active routable, if one exists. + */ + getRoutable: function() { + return JX.Router.getInstance().getRoutableByKey(this._getRoutableKey()); + }, + + setRenderer: function(renderer) { + this._renderer = renderer; + return this; + }, + + getRenderer: function() { + if (this._renderer !== null) { + return this._renderer; + } + + // NOTE: If you load the page at one device resolution and then resize to + // a different one we don't re-render the diffs, because it's a + // complicated mess and you could lose inline comments, cursor positions, + // etc. + return (JX.Device.getDevice() == 'desktop') ? '2up' : '1up'; + }, + + getUndoTemplates: function() { + return this._undoTemplates; + }, + + setEncoding: function(encoding) { + this._encoding = encoding; + return this; + }, + + getEncoding: function() { + return this._encoding; + }, + + setHighlight: function(highlight) { + this._highlight = highlight; + return this; + }, + + getHighlight: function() { + return this._highlight; + }, + + getSelectableItems: function() { + var items = []; + + items.push({ + type: 'file', + changeset: this, + target: this, + nodes: { + begin: this._node, + end: null + } + }); + + if (!this._visible) { + return items; + } + + var rows = JX.DOM.scry(this._node, 'tr'); + + var blocks = []; + var block; + var ii; + for (ii = 0; ii < rows.length; ii++) { + var type = this._getRowType(rows[ii]); + + if (!block || (block.type !== type)) { + block = { + type: type, + items: [] + }; + blocks.push(block); + } + + block.items.push(rows[ii]); + } + + var last_inline = null; + var last_inline_item = null; + for (ii = 0; ii < blocks.length; ii++) { + block = blocks[ii]; + + if (block.type == 'change') { + items.push({ + type: block.type, + changeset: this, + target: block.items[0], + nodes: { + begin: block.items[0], + end: block.items[block.items.length - 1] + } + }); + } + + if (block.type == 'comment') { + for (var jj = 0; jj < block.items.length; jj++) { + var inline = this.getInlineForRow(block.items[jj]); + + // When comments are being edited, they have a hidden row with + // the actual comment and then a visible row with the editor. + + // In this case, we only want to generate one item, but it should + // use the editor as a scroll target. To accomplish this, check if + // this row has the same inline as the previous row. If so, update + // the last item to use this row's nodes. + + if (inline === last_inline) { + last_inline_item.nodes.begin = block.items[jj]; + last_inline_item.nodes.end = block.items[jj]; + continue; + } else { + last_inline = inline; + } + + var is_saved = (!inline.isDraft() && !inline.isEditing()); + + last_inline_item = { + type: block.type, + changeset: this, + target: inline, + hidden: inline.isHidden(), + collapsed: inline.isCollapsed(), + deleted: !inline.getID() && !inline.isEditing(), + nodes: { + begin: block.items[jj], + end: block.items[jj] + }, + attributes: { + unsaved: inline.isEditing(), + anyDraft: inline.isDraft() || inline.isDraftDone(), + undone: (is_saved && !inline.isDone()), + done: (is_saved && inline.isDone()) + } + }; + + items.push(last_inline_item); + } + } + } + + return items; + }, + + _getRowType: function(row) { + // NOTE: Don't do "className.indexOf()" elsewhere. This is evil legacy + // magic. + + if (row.className.indexOf('inline') !== -1) { + return 'comment'; + } + + var cells = JX.DOM.scry(row, 'td'); + for (var ii = 0; ii < cells.length; ii++) { + if (cells[ii].className.indexOf('old') !== -1 || + cells[ii].className.indexOf('new') !== -1) { + return 'change'; + } + } + }, + + _getNodeData: function() { + return JX.Stratcom.getData(this._node); + }, + + getVectors: function() { + return { + pos: JX.$V(this._node), + dim: JX.Vector.getDim(this._node) + }; + }, + + _onresponse: function(sequence, response) { + if (sequence != this._sequence) { + // If this isn't the most recent request, ignore it. This normally + // means the user changed view settings between the time the page loaded + // and the content filled. + return; + } + + // As we populate the changeset list, we try to hold the document scroll + // position steady, so that, e.g., users who want to leave a comment on a + // diff with a large number of changes don't constantly have the text + // area scrolled off the bottom of the screen until the entire diff loads. + // + // There are several major cases here: + // + // - If we're near the top of the document, never scroll. + // - If we're near the bottom of the document, always scroll, unless + // we have an anchor. + // - Otherwise, scroll if the changes were above (or, at least, + // almost entirely above) the viewport. + // + // We don't scroll if the changes were just near the top of the viewport + // because this makes us scroll incorrectly when an anchored change is + // visible. See T12779. + + var target = this._node; + + var old_pos = JX.Vector.getScroll(); + var old_view = JX.Vector.getViewport(); + var old_dim = JX.Vector.getDocument(); + + // Number of pixels away from the top or bottom of the document which + // count as "nearby". + var sticky = 480; + + var near_top = (old_pos.y <= sticky); + var near_bot = ((old_pos.y + old_view.y) >= (old_dim.y - sticky)); + + // If we have an anchor in the URL, never stick to the bottom of the + // page. See T11784 for discussion. + if (window.location.hash) { + near_bot = false; + } + + var target_pos = JX.Vector.getPos(target); + var target_dim = JX.Vector.getDim(target); + var target_bot = (target_pos.y + target_dim.y); + + // Detect if the changeset is entirely (or, at least, almost entirely) + // above us. The height here is roughly the height of the persistent + // banner. + var above_screen = (target_bot < old_pos.y + 64); + + // If we have a URL anchor and are currently nearby, stick to it + // no matter what. + var on_target = null; + if (window.location.hash) { + try { + var anchor = JX.$(window.location.hash.replace('#', '')); + if (anchor) { + var anchor_pos = JX.$V(anchor); + if ((anchor_pos.y > old_pos.y) && + (anchor_pos.y < old_pos.y + 96)) { + on_target = anchor; + } + } + } catch (ignored) { + // If we have a bogus anchor, just ignore it. + } + } + + var frame = this._getContentFrame(); + JX.DOM.setContent(frame, JX.$H(response.changeset)); + + if (this._stabilize) { + if (on_target) { + JX.DOM.scrollToPosition(old_pos.x, JX.$V(on_target).y - 60); + } else if (!near_top) { + if (near_bot || above_screen) { + // Figure out how much taller the document got. + var delta = (JX.Vector.getDocument().y - old_dim.y); + JX.DOM.scrollToPosition(old_pos.x, old_pos.y + delta); + } + } + this._stabilize = false; + } + + this._onchangesetresponse(response); + }, + + _onchangesetresponse: function(response) { + // Code shared by autoload and context responses. + + if (response.coverage) { + for (var k in response.coverage) { + try { + JX.DOM.replace(JX.$(k), JX.$H(response.coverage[k])); + } catch (ignored) { + // Not terribly important. + } + } + } + + if (response.undoTemplates) { + this._undoTemplates = response.undoTemplates; + } + + JX.Stratcom.invoke('differential-inline-comment-refresh'); + + this._rebuildAllInlines(); + + JX.Stratcom.invoke('resize'); + }, + + _getContentFrame: function() { + return JX.DOM.find(this._node, 'div', 'changeset-view-content'); + }, + + _getRoutableKey: function() { + return 'changeset-view.' + this._ref + '.' + this._sequence; + }, + + getInlineForRow: function(node) { + var data = JX.Stratcom.getData(node); + + if (!data.inline) { + var inline = new JX.DiffInline() + .setChangeset(this) + .bindToRow(node); + + this._inlines.push(inline); + } + + return data.inline; + }, + + newInlineForRange: function(origin, target) { + var list = this.getChangesetList(); + + var src = list.getLineNumberFromHeader(origin); + var dst = list.getLineNumberFromHeader(target); + + var changeset_id = null; + var side = list.getDisplaySideFromHeader(origin); + if (side == 'right') { + changeset_id = this.getRightChangesetID(); + } else { + changeset_id = this.getLeftChangesetID(); + } + + var is_new = false; + if (side == 'right') { + is_new = true; + } else if (this.getRightChangesetID() != this.getLeftChangesetID()) { + is_new = true; + } + + var data = { + origin: origin, + target: target, + number: src, + length: dst - src, + changesetID: changeset_id, + displaySide: side, + isNewFile: is_new + }; + + var inline = new JX.DiffInline() + .setChangeset(this) + .bindToRange(data); + + this._inlines.push(inline); + + inline.create(); + + return inline; + }, + + newInlineReply: function(original, text) { + var inline = new JX.DiffInline() + .setChangeset(this) + .bindToReply(original); + + this._inlines.push(inline); + + inline.create(text); + + return inline; + }, + + getInlineByID: function(id) { + return this._queryInline('id', id); + }, + + getInlineByPHID: function(phid) { + return this._queryInline('phid', phid); + }, + + _queryInline: function(field, value) { + // First, look for the inline in the objects we've already built. + var inline = this._findInline(field, value); + if (inline) { + return inline; + } + + // If we haven't found a matching inline yet, rebuild all the inlines + // present in the document, then look again. + this._rebuildAllInlines(); + return this._findInline(field, value); + }, + + _findInline: function(field, value) { + for (var ii = 0; ii < this._inlines.length; ii++) { + var inline = this._inlines[ii]; + + var target; + switch (field) { + case 'id': + target = inline.getID(); + break; + case 'phid': + target = inline.getPHID(); + break; + } + + if (target == value) { + return inline; + } + } + + return null; + }, + + getInlines: function() { + this._rebuildAllInlines(); + return this._inlines; + }, + + _rebuildAllInlines: function() { + var rows = JX.DOM.scry(this._node, 'tr'); + for (var ii = 0; ii < rows.length; ii++) { + var row = rows[ii]; + if (this._getRowType(row) != 'comment') { + continue; + } + + // As a side effect, this builds any missing inline objects and adds + // them to this Changeset's list of inlines. + this.getInlineForRow(row); + } + }, + + toggleVisibility: function() { + this._visible = !this._visible; + + var diff = JX.DOM.find(this._node, 'table', 'differential-diff'); + var undo = this._getUndoNode(); + + if (this._visible) { + JX.DOM.show(diff); + JX.DOM.remove(undo); + } else { + JX.DOM.hide(diff); + JX.DOM.appendContent(diff.parentNode, undo); + } + + JX.Stratcom.invoke('resize'); + }, + + isVisible: function() { + return this._visible; + }, + + _getUndoNode: function() { + if (!this._undoNode) { + var pht = this.getChangesetList().getTranslations(); + + var link_attributes = { + href: '#' + }; + + var undo_link = JX.$N('a', link_attributes, pht('Show Content')); + + var onundo = JX.bind(this, this._onundo); + JX.DOM.listen(undo_link, 'click', null, onundo); + + var node_attributes = { + className: 'differential-collapse-undo' + }; + + var node_content = [ + pht('This file content has been collapsed.'), + ' ', + undo_link + ]; + + var undo_node = JX.$N('div', node_attributes, node_content); + + this._undoNode = undo_node; + } + + return this._undoNode; + }, + + _onundo: function(e) { + e.kill(); + this.toggleVisibility(); + } + }, + + statics: { + getForNode: function(node) { + var data = JX.Stratcom.getData(node); + if (!data.changesetViewManager) { + data.changesetViewManager = new JX.DiffChangeset(node); + } + return data.changesetViewManager; + } + } +}); diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js new file mode 100644 index 0000000000..3f47f1ed35 --- /dev/null +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -0,0 +1,1844 @@ +/** + * @provides phabricator-diff-changeset-list + * @requires javelin-install + * phuix-button-view + * @javelin + */ + +JX.install('DiffChangesetList', { + + construct: function() { + this._changesets = []; + + var onload = JX.bind(this, this._ifawake, this._onload); + JX.Stratcom.listen('click', 'differential-load', onload); + + var onmore = JX.bind(this, this._ifawake, this._onmore); + JX.Stratcom.listen('click', 'show-more', onmore); + + var onmenu = JX.bind(this, this._ifawake, this._onmenu); + JX.Stratcom.listen('click', 'differential-view-options', onmenu); + + var oncollapse = JX.bind(this, this._ifawake, this._oncollapse, true); + JX.Stratcom.listen('click', 'hide-inline', oncollapse); + + var onexpand = JX.bind(this, this._ifawake, this._oncollapse, false); + JX.Stratcom.listen('click', 'reveal-inline', onexpand); + + var onedit = JX.bind(this, this._ifawake, this._onaction, 'edit'); + JX.Stratcom.listen( + 'click', + ['differential-inline-comment', 'differential-inline-edit'], + onedit); + + var ondone = JX.bind(this, this._ifawake, this._onaction, 'done'); + JX.Stratcom.listen( + 'click', + ['differential-inline-comment', 'differential-inline-done'], + ondone); + + var ondelete = JX.bind(this, this._ifawake, this._onaction, 'delete'); + JX.Stratcom.listen( + 'click', + ['differential-inline-comment', 'differential-inline-delete'], + ondelete); + + var onreply = JX.bind(this, this._ifawake, this._onaction, 'reply'); + JX.Stratcom.listen( + 'click', + ['differential-inline-comment', 'differential-inline-reply'], + onreply); + + var onresize = JX.bind(this, this._ifawake, this._onresize); + JX.Stratcom.listen('resize', null, onresize); + + var onscroll = JX.bind(this, this._ifawake, this._onscroll); + JX.Stratcom.listen('scroll', null, onscroll); + + var onselect = JX.bind(this, this._ifawake, this._onselect); + JX.Stratcom.listen( + 'mousedown', + ['differential-inline-comment', 'differential-inline-header'], + onselect); + + var onhover = JX.bind(this, this._ifawake, this._onhover); + JX.Stratcom.listen( + ['mouseover', 'mouseout'], + 'differential-inline-comment', + onhover); + + var onrangedown = JX.bind(this, this._ifawake, this._onrangedown); + JX.Stratcom.listen( + 'mousedown', + ['differential-changeset', 'tag:th'], + onrangedown); + + var onrangemove = JX.bind(this, this._ifawake, this._onrangemove); + JX.Stratcom.listen( + ['mouseover', 'mouseout'], + ['differential-changeset', 'tag:th'], + onrangemove); + + var onrangeup = JX.bind(this, this._ifawake, this._onrangeup); + JX.Stratcom.listen( + 'mouseup', + null, + onrangeup); + }, + + properties: { + translations: null, + inlineURI: null, + inlineListURI: null + }, + + members: { + _initialized: false, + _asleep: true, + _changesets: null, + + _cursorItem: null, + + _focusNode: null, + _focusStart: null, + _focusEnd: null, + + _hoverNode: null, + _hoverInline: null, + _hoverOrigin: null, + _hoverTarget: null, + + _rangeActive: false, + _rangeOrigin: null, + _rangeTarget: null, + + _bannerNode: null, + _unsavedButton: null, + _unsubmittedButton: null, + _doneButton: null, + _doneMode: null, + + _dropdownMenu: null, + _menuButton: null, + _menuItems: null, + + sleep: function() { + this._asleep = true; + + this._redrawFocus(); + this._redrawSelection(); + this.resetHover(); + }, + + wake: function() { + this._asleep = false; + + this._redrawFocus(); + this._redrawSelection(); + + if (this._initialized) { + return; + } + + this._initialized = true; + var pht = this.getTranslations(); + + var label; + + label = pht('Jump to next change.'); + this._installJumpKey('j', label, 1); + + label = pht('Jump to previous change.'); + this._installJumpKey('k', label, -1); + + label = pht('Jump to next file.'); + this._installJumpKey('J', label, 1, 'file'); + + label = pht('Jump to previous file.'); + this._installJumpKey('K', label, -1, 'file'); + + label = pht('Jump to next inline comment.'); + this._installJumpKey('n', label, 1, 'comment'); + + label = pht('Jump to previous inline comment.'); + this._installJumpKey('p', label, -1, 'comment'); + + label = pht('Jump to next inline comment, including collapsed comments.'); + this._installJumpKey('N', label, 1, 'comment', true); + + label = pht( + 'Jump to previous inline comment, including collapsed comments.'); + this._installJumpKey('P', label, -1, 'comment', true); + + label = pht('Hide or show the current file.'); + this._installKey('h', label, this._onkeytogglefile); + + label = pht('Jump to the table of contents.'); + this._installKey('t', label, this._ontoc); + + label = pht('Reply to selected inline comment or change.'); + this._installKey('r', label, JX.bind(this, this._onkeyreply, false)); + + label = pht('Reply and quote selected inline comment.'); + this._installKey('R', label, JX.bind(this, this._onkeyreply, true)); + + label = pht('Edit selected inline comment.'); + this._installKey('e', label, this._onkeyedit); + + label = pht('Mark or unmark selected inline comment as done.'); + this._installKey('w', label, this._onkeydone); + + label = pht('Collapse or expand inline comment.'); + this._installKey('q', label, this._onkeycollapse); + + label = pht('Hide or show all inline comments.'); + this._installKey('A', label, this._onkeyhideall); + + }, + + isAsleep: function() { + return this._asleep; + }, + + newChangesetForNode: function(node) { + var changeset = JX.DiffChangeset.getForNode(node); + + this._changesets.push(changeset); + changeset.setChangesetList(this); + + return changeset; + }, + + getChangesetForNode: function(node) { + return JX.DiffChangeset.getForNode(node); + }, + + getInlineByID: function(id) { + var inline = null; + + for (var ii = 0; ii < this._changesets.length; ii++) { + inline = this._changesets[ii].getInlineByID(id); + if (inline) { + break; + } + } + + return inline; + }, + + _ifawake: function(f) { + // This function takes another function and only calls it if the + // changeset list is awake, so we basically just ignore events when we + // are asleep. This may move up the stack at some point as we do more + // with Quicksand/Sheets. + + if (this.isAsleep()) { + return; + } + + return f.apply(this, [].slice.call(arguments, 1)); + }, + + _onload: function(e) { + var data = e.getNodeData('differential-load'); + + // NOTE: We can trigger a load from either an explicit "Load" link on + // the changeset, or by clicking a link in the table of contents. If + // the event was a table of contents link, we let the anchor behavior + // run normally. + if (data.kill) { + e.kill(); + } + + var node = JX.$(data.id); + var changeset = this.getChangesetForNode(node); + + changeset.load(); + + // TODO: Move this into Changeset. + var routable = changeset.getRoutable(); + if (routable) { + routable.setPriority(2000); + } + }, + + _installKey: function(key, label, handler) { + handler = JX.bind(this, this._ifawake, handler); + + return new JX.KeyboardShortcut(key, label) + .setHandler(handler) + .register(); + }, + + _installJumpKey: function(key, label, delta, filter, show_collapsed) { + filter = filter || null; + + var options = { + filter: filter, + collapsed: show_collapsed + }; + + var handler = JX.bind(this, this._onjumpkey, delta, options); + return this._installKey(key, label, handler); + }, + + _ontoc: function(manager) { + var toc = JX.$('toc'); + manager.scrollTo(toc); + }, + + getSelectedInline: function() { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + return cursor.target; + } + } + + return null; + }, + + _onkeyreply: function(is_quote) { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + var inline = cursor.target; + if (inline.canReply()) { + this.setFocus(null); + + var text; + if (is_quote) { + text = inline.getRawText(); + text = '> ' + text.replace(/\n/g, '\n> ') + '\n\n'; + } else { + text = ''; + } + + inline.reply(text); + return; + } + } + + // If the keyboard cursor is selecting a range of lines, we may have + // a mixture of old and new changes on the selected rows. It is not + // entirely unambiguous what the user means when they say they want + // to reply to this, but we use this logic: reply on the new file if + // there are any new lines. Otherwise (if there are only removed + // lines) reply on the old file. + + if (cursor.type == 'change') { + var origin = cursor.nodes.begin; + var target = cursor.nodes.end; + + // The "origin" and "target" are entire rows, but we need to find + // a range of "" nodes to actually create an inline, so go + // fishing. + + var old_list = []; + var new_list = []; + + var row = origin; + while (row) { + var header = row.firstChild; + while (header) { + if (JX.DOM.isType(header, 'th')) { + if (header.className.indexOf('old') !== -1) { + old_list.push(header); + } else if (header.className.indexOf('new') !== -1) { + new_list.push(header); + } + } + header = header.nextSibling; + } + + if (row == target) { + break; + } + + row = row.nextSibling; + } + + var use_list; + if (new_list.length) { + use_list = new_list; + } else { + use_list = old_list; + } + + var src = use_list[0]; + var dst = use_list[use_list.length - 1]; + + cursor.changeset.newInlineForRange(src, dst); + + this.setFocus(null); + return; + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a comment or change to reply to.')); + }, + + _onkeyedit: function() { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + var inline = cursor.target; + if (inline.canEdit()) { + this.setFocus(null); + + inline.edit(); + return; + } + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a comment to edit.')); + }, + + _onkeydone: function() { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + var inline = cursor.target; + if (inline.canDone()) { + this.setFocus(null); + + inline.toggleDone(); + return; + } + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a comment to mark done.')); + }, + + _onkeytogglefile: function() { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'file') { + cursor.changeset.toggleVisibility(); + return; + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a file to hide or show.')); + }, + + _onkeycollapse: function() { + var cursor = this._cursorItem; + + if (cursor) { + if (cursor.type == 'comment') { + var inline = cursor.target; + if (inline.canCollapse()) { + this.setFocus(null); + + inline.setCollapsed(!inline.isCollapsed()); + return; + } + } + } + + var pht = this.getTranslations(); + this._warnUser(pht('You must select a comment to hide.')); + }, + + _onkeyhideall: function() { + var inlines = this._getInlinesByType(); + if (inlines.visible.length) { + this._toggleInlines('all'); + } else { + this._toggleInlines('show'); + } + }, + + _warnUser: function(message) { + new JX.Notification() + .setContent(message) + .alterClassName('jx-notification-alert', true) + .setDuration(1000) + .show(); + }, + + _onjumpkey: function(delta, options) { + var state = this._getSelectionState(); + + var filter = options.filter || null; + var collapsed = options.collapsed || false; + var wrap = options.wrap || false; + var attribute = options.attribute || null; + var show = options.show || false; + + var cursor = state.cursor; + var items = state.items; + + // If there's currently no selection and the user tries to go back, + // don't do anything. + if ((cursor === null) && (delta < 0)) { + return; + } + + var did_wrap = false; + while (true) { + if (cursor === null) { + cursor = 0; + } else { + cursor = cursor + delta; + } + + // If we've gone backward past the first change, bail out. + if (cursor < 0) { + return; + } + + // If we've gone forward off the end of the list, figure out where we + // should end up. + if (cursor >= items.length) { + if (!wrap) { + // If we aren't wrapping around, we're done. + return; + } + + if (did_wrap) { + // If we're already wrapped around, we're done. + return; + } + + // Otherwise, wrap the cursor back to the top. + cursor = 0; + did_wrap = true; + } + + // If we're selecting things of a particular type (like only files) + // and the next item isn't of that type, move past it. + if (filter !== null) { + if (items[cursor].type !== filter) { + continue; + } + } + + // If the item is collapsed, don't select it when iterating with jump + // keys. It can still potentially be selected in other ways. + if (!collapsed) { + if (items[cursor].collapsed) { + continue; + } + } + + // If the item has been deleted, don't select it when iterating. The + // cursor may remain on it until it is removed. + if (items[cursor].deleted) { + continue; + } + + // If we're selecting things with a particular attribute, like + // "unsaved", skip items without the attribute. + if (attribute !== null) { + if (!(items[cursor].attributes || {})[attribute]) { + continue; + } + } + + // If this item is a hidden inline but we're clicking a button which + // selects inlines of a particular type, make it visible again. + if (items[cursor].hidden) { + if (!show) { + continue; + } + items[cursor].target.setHidden(false); + } + + // Otherwise, we've found a valid item to select. + break; + } + + this._setSelectionState(items[cursor], true); + }, + + _getSelectionState: function() { + var items = this._getSelectableItems(); + + var cursor = null; + if (this._cursorItem !== null) { + for (var ii = 0; ii < items.length; ii++) { + var item = items[ii]; + if (this._cursorItem.target === item.target) { + cursor = ii; + break; + } + } + } + + return { + cursor: cursor, + items: items + }; + }, + + _setSelectionState: function(item, scroll) { + this._cursorItem = item; + this._redrawSelection(scroll); + + return this; + }, + + _redrawSelection: function(scroll) { + var cursor = this._cursorItem; + if (!cursor) { + this.setFocus(null); + return; + } + + // If this item has been removed from the document (for example: create + // a new empty comment, then use the "Unsaved" button to select it, then + // cancel it), we can still keep the cursor here but do not want to show + // a selection reticle over an invisible node. + if (cursor.deleted) { + this.setFocus(null); + return; + } + + this.setFocus(cursor.nodes.begin, cursor.nodes.end); + + if (scroll) { + var pos = JX.$V(cursor.nodes.begin); + JX.DOM.scrollToPosition(0, pos.y - 60); + } + + return this; + }, + + redrawCursor: function() { + // NOTE: This is setting the cursor to the current cursor. Usually, this + // would have no effect. + + // However, if the old cursor pointed at an inline and the inline has + // been edited so the rows have changed, this updates the cursor to point + // at the new inline with the proper rows for the current state, and + // redraws the reticle correctly. + + var state = this._getSelectionState(); + if (state.cursor !== null) { + this._setSelectionState(state.items[state.cursor], false); + } + }, + + _getSelectableItems: function() { + var result = []; + + for (var ii = 0; ii < this._changesets.length; ii++) { + var items = this._changesets[ii].getSelectableItems(); + for (var jj = 0; jj < items.length; jj++) { + result.push(items[jj]); + } + } + + return result; + }, + + _onhover: function(e) { + if (e.getIsTouchEvent()) { + return; + } + + var inline; + if (e.getType() == 'mouseout') { + inline = null; + } else { + inline = this._getInlineForEvent(e); + } + + this._setHoverInline(inline); + }, + + _onmore: function(e) { + e.kill(); + + var node = e.getNode('differential-changeset'); + var changeset = this.getChangesetForNode(node); + + var data = e.getNodeData('show-more'); + var target = e.getNode('context-target'); + + changeset.loadContext(data.range, target); + }, + + _onmenu: function(e) { + var button = e.getNode('differential-view-options'); + + var data = JX.Stratcom.getData(button); + if (data.menu) { + // We've already built this menu, so we can let the menu itself handle + // the event. + return; + } + + e.prevent(); + + var pht = this.getTranslations(); + + var node = JX.DOM.findAbove( + button, + 'div', + 'differential-changeset'); + + var changeset = this.getChangesetForNode(node); + + var menu = new JX.PHUIXDropdownMenu(button); + var list = new JX.PHUIXActionListView(); + + var add_link = function(icon, name, href, local) { + if (!href) { + return; + } + + var link = new JX.PHUIXActionView() + .setIcon(icon) + .setName(name) + .setHref(href) + .setHandler(function(e) { + if (local) { + window.location.assign(href); + } else { + window.open(href); + } + menu.close(); + e.prevent(); + }); + + list.addItem(link); + return link; + }; + + var reveal_item = new JX.PHUIXActionView() + .setIcon('fa-eye'); + list.addItem(reveal_item); + + var visible_item = new JX.PHUIXActionView() + .setHandler(function(e) { + e.prevent(); + menu.close(); + + changeset.toggleVisibility(); + }); + list.addItem(visible_item); + + add_link('fa-file-text', pht('Browse in Diffusion'), data.diffusionURI); + add_link('fa-file-o', pht('View Standalone'), data.standaloneURI); + + var up_item = new JX.PHUIXActionView() + .setHandler(function(e) { + if (changeset.isLoaded()) { + var renderer = changeset.getRenderer(); + if (renderer == '1up') { + renderer = '2up'; + } else { + renderer = '1up'; + } + changeset.setRenderer(renderer); + } + changeset.reload(); + + e.prevent(); + menu.close(); + }); + list.addItem(up_item); + + var encoding_item = new JX.PHUIXActionView() + .setIcon('fa-font') + .setName(pht('Change Text Encoding...')) + .setHandler(function(e) { + var params = { + encoding: changeset.getEncoding() + }; + + new JX.Workflow('/services/encoding/', params) + .setHandler(function(r) { + changeset.setEncoding(r.encoding); + changeset.reload(); + }) + .start(); + + e.prevent(); + menu.close(); + }); + list.addItem(encoding_item); + + var highlight_item = new JX.PHUIXActionView() + .setIcon('fa-sun-o') + .setName(pht('Highlight As...')) + .setHandler(function(e) { + var params = { + highlight: changeset.getHighlight() + }; + + new JX.Workflow('/services/highlight/', params) + .setHandler(function(r) { + changeset.setHighlight(r.highlight); + changeset.reload(); + }) + .start(); + + e.prevent(); + menu.close(); + }); + list.addItem(highlight_item); + + add_link('fa-arrow-left', pht('Show Raw File (Left)'), data.leftURI); + add_link('fa-arrow-right', pht('Show Raw File (Right)'), data.rightURI); + add_link('fa-pencil', pht('Open in Editor'), data.editor, true); + add_link('fa-wrench', pht('Configure Editor'), data.editorConfigure); + + menu.setContent(list.getNode()); + + menu.listen('open', function() { + // When the user opens the menu, check if there are any "Show More" + // links in the changeset body. If there aren't, disable the "Show + // Entire File" menu item since it won't change anything. + + var nodes = JX.DOM.scry(JX.$(data.containerID), 'a', 'show-more'); + if (nodes.length) { + reveal_item + .setDisabled(false) + .setName(pht('Show All Context')) + .setIcon('fa-file-o') + .setHandler(function(e) { + changeset.loadAllContext(); + e.prevent(); + menu.close(); + }); + } else { + reveal_item + .setDisabled(true) + .setIcon('fa-file') + .setName(pht('All Context Shown')) + .setHandler(function(e) { e.prevent(); }); + } + + encoding_item.setDisabled(!changeset.isLoaded()); + highlight_item.setDisabled(!changeset.isLoaded()); + + if (changeset.isLoaded()) { + if (changeset.getRenderer() == '2up') { + up_item + .setIcon('fa-list-alt') + .setName(pht('View Unified')); + } else { + up_item + .setIcon('fa-files-o') + .setName(pht('View Side-by-Side')); + } + } else { + up_item + .setIcon('fa-refresh') + .setName(pht('Load Changes')); + } + + visible_item + .setDisabled(true) + .setIcon('fa-expand') + .setName(pht('Can\'t Toggle Unloaded File')); + var diffs = JX.DOM.scry( + JX.$(data.containerID), + 'table', + 'differential-diff'); + + if (diffs.length > 1) { + JX.$E( + 'More than one node with sigil "differential-diff" was found in "'+ + data.containerID+'."'); + } else if (diffs.length == 1) { + var diff = diffs[0]; + visible_item.setDisabled(false); + if (!changeset.isVisible()) { + visible_item + .setName(pht('Expand File')) + .setIcon('fa-expand'); + } else { + visible_item + .setName(pht('Collapse File')) + .setIcon('fa-compress'); + } + } else { + // Do nothing when there is no diff shown in the table. For example, + // the file is binary. + } + + }); + + data.menu = menu; + menu.open(); + }, + + _oncollapse: function(is_collapse, e) { + e.kill(); + + var inline = this._getInlineForEvent(e); + + inline.setCollapsed(is_collapse); + }, + + _onresize: function() { + this._redrawFocus(); + this._redrawSelection(); + this._redrawHover(); + + // Force a banner redraw after a resize event. Particularly, this makes + // sure the inline state updates immediately after an inline edit + // operation, even if the changeset itself has not changed. + this._bannerChangeset = null; + + this._redrawBanner(); + }, + + _onscroll: function() { + this._redrawBanner(); + }, + + _onselect: function(e) { + // If the user clicked some element inside the header, like an action + // icon, ignore the event. They have to click the header element itself. + if (e.getTarget() !== e.getNode('differential-inline-header')) { + return; + } + + var inline = this._getInlineForEvent(e); + if (!inline) { + return; + } + + // The user definitely clicked an inline, so we're going to handle the + // event. + e.kill(); + + this.selectInline(inline); + }, + + selectInline: function(inline) { + var selection = this._getSelectionState(); + var item; + + // If the comment the user clicked is currently selected, deselect it. + // This makes it easy to undo things if you clicked by mistake. + if (selection.cursor !== null) { + item = selection.items[selection.cursor]; + if (item.target === inline) { + this._setSelectionState(null, false); + return; + } + } + + // Otherwise, select the item that the user clicked. This makes it + // easier to resume keyboard operations after using the mouse to do + // something else. + var items = selection.items; + for (var ii = 0; ii < items.length; ii++) { + item = items[ii]; + if (item.target === inline) { + this._setSelectionState(item, false); + } + } + }, + + _onaction: function(action, e) { + e.kill(); + + var inline = this._getInlineForEvent(e); + var is_ref = false; + + // If we don't have a natural inline object, the user may have clicked + // an action (like "Delete") inside a preview element at the bottom of + // the page. + + // If they did, try to find an associated normal inline to act on, and + // pretend they clicked that instead. This makes the overall state of + // the page more consistent. + + // However, there may be no normal inline (for example, because it is + // on a version of the diff which is not visible). In this case, we + // act by reference. + + if (inline === null) { + var data = e.getNodeData('differential-inline-comment'); + inline = this.getInlineByID(data.id); + if (inline) { + is_ref = true; + } else { + switch (action) { + case 'delete': + this._deleteInlineByID(data.id); + return; + } + } + } + + // TODO: For normal operations, highlight the inline range here. + + switch (action) { + case 'edit': + inline.edit(); + break; + case 'done': + inline.toggleDone(); + break; + case 'delete': + inline.delete(is_ref); + break; + case 'reply': + inline.reply(); + break; + } + }, + + redrawPreview: function() { + // TODO: This isn't the cleanest way to find the preview form, but + // rendering no longer has direct access to it. + var forms = JX.DOM.scry(document.body, 'form', 'transaction-append'); + if (forms.length) { + JX.DOM.invoke(forms[0], 'shouldRefresh'); + } + + // Clear the mouse hover reticle after a substantive edit: we don't get + // a "mouseout" event if the row vanished because of row being removed + // after an edit. + this.resetHover(); + }, + + setFocus: function(node, extended_node) { + this._focusStart = node; + this._focusEnd = extended_node; + this._redrawFocus(); + }, + + _redrawFocus: function() { + var node = this._focusStart; + var extended_node = this._focusEnd || node; + + var reticle = this._getFocusNode(); + if (!node || this.isAsleep()) { + JX.DOM.remove(reticle); + return; + } + + // Outset the reticle some pixels away from the element, so there's some + // space between the focused element and the outline. + var p = JX.Vector.getPos(node); + var s = JX.Vector.getAggregateScrollForNode(node); + + p.add(s).add(-4, -4).setPos(reticle); + // Compute the size we need to extend to the full extent of the focused + // nodes. + JX.Vector.getPos(extended_node) + .add(-p.x, -p.y) + .add(JX.Vector.getDim(extended_node)) + .add(8, 8) + .setDim(reticle); + + JX.DOM.getContentFrame().appendChild(reticle); + }, + + _getFocusNode: function() { + if (!this._focusNode) { + var node = JX.$N('div', {className : 'keyboard-focus-focus-reticle'}); + this._focusNode = node; + } + return this._focusNode; + }, + + _setHoverInline: function(inline) { + this._hoverInline = inline; + + if (inline) { + var changeset = inline.getChangeset(); + + var changeset_id; + var side = inline.getDisplaySide(); + if (side == 'right') { + changeset_id = changeset.getRightChangesetID(); + } else { + changeset_id = changeset.getLeftChangesetID(); + } + + var new_part; + if (inline.isNewFile()) { + new_part = 'N'; + } else { + new_part = 'O'; + } + + var prefix = 'C' + changeset_id + new_part + 'L'; + + var number = inline.getLineNumber(); + var length = inline.getLineLength(); + + try { + var origin = JX.$(prefix + number); + var target = JX.$(prefix + (number + length)); + + this._hoverOrigin = origin; + this._hoverTarget = target; + } catch (error) { + // There may not be any nodes present in the document. A case where + // this occurs is when you reply to a ghost inline which was made + // on lines near the bottom of "long.txt" in an earlier diff, and + // the file was later shortened so those lines no longer exist. For + // more details, see T11662. + + this._hoverOrigin = null; + this._hoverTarget = null; + } + } else { + this._hoverOrigin = null; + this._hoverTarget = null; + } + + this._redrawHover(); + }, + + _setHoverRange: function(origin, target) { + this._hoverOrigin = origin; + this._hoverTarget = target; + + this._redrawHover(); + }, + + resetHover: function() { + this._setHoverInline(null); + + this._hoverOrigin = null; + this._hoverTarget = null; + }, + + _redrawHover: function() { + var reticle = this._getHoverNode(); + if (!this._hoverOrigin || this.isAsleep()) { + JX.DOM.remove(reticle); + return; + } + + JX.DOM.getContentFrame().appendChild(reticle); + + var top = this._hoverOrigin; + var bot = this._hoverTarget; + if (JX.$V(top).y > JX.$V(bot).y) { + var tmp = top; + top = bot; + bot = tmp; + } + + // Find the leftmost cell that we're going to highlight: this is the next + // in the row. In 2up views, it should be directly adjacent. In + // 1up views, we may have to skip over the other line number column. + var l = top; + while (JX.DOM.isType(l, 'th')) { + l = l.nextSibling; + } + + // Find the rightmost cell that we're going to highlight: this is the + // farthest consecutive, adjacent in the row. Sometimes the left + // and right nodes are the same (left side of 2up view); sometimes we're + // going to highlight several nodes (copy + code + coverage). + var r = l; + while (r.nextSibling && JX.DOM.isType(r.nextSibling, 'td')) { + r = r.nextSibling; + } + + var pos = JX.$V(l) + .add(JX.Vector.getAggregateScrollForNode(l)); + + var dim = JX.$V(r) + .add(JX.Vector.getAggregateScrollForNode(r)) + .add(-pos.x, -pos.y) + .add(JX.Vector.getDim(r)); + + var bpos = JX.$V(bot) + .add(JX.Vector.getAggregateScrollForNode(bot)); + dim.y = (bpos.y - pos.y) + JX.Vector.getDim(bot).y; + + pos.setPos(reticle); + dim.setDim(reticle); + + JX.DOM.show(reticle); + }, + + _getHoverNode: function() { + if (!this._hoverNode) { + var attributes = { + className: 'differential-reticle' + }; + this._hoverNode = JX.$N('div', attributes); + } + + return this._hoverNode; + }, + + _deleteInlineByID: function(id) { + var uri = this.getInlineURI(); + var data = { + op: 'refdelete', + id: id + }; + + var handler = JX.bind(this, this.redrawPreview); + + new JX.Workflow(uri, data) + .setHandler(handler) + .start(); + }, + + _getInlineForEvent: function(e) { + var node = e.getNode('differential-changeset'); + if (!node) { + return null; + } + + var changeset = this.getChangesetForNode(node); + + var inline_row = e.getNode('inline-row'); + return changeset.getInlineForRow(inline_row); + }, + + getLineNumberFromHeader: function(th) { + try { + return parseInt(th.id.match(/^C\d+[ON]L(\d+)$/)[1], 10); + } catch (x) { + return null; + } + }, + + getDisplaySideFromHeader: function(th) { + return (th.parentNode.firstChild != th) ? 'right' : 'left'; + }, + + _onrangedown: function(e) { + // NOTE: We're allowing "mousedown" from a touch event through so users + // can leave inlines on a single line. + if (e.isRightButton()) { + return; + } + + if (this._rangeActive) { + return; + } + + var target = e.getTarget(); + var number = this.getLineNumberFromHeader(target); + if (!number) { + return; + } + + e.kill(); + this._rangeActive = true; + + this._rangeOrigin = target; + this._rangeTarget = target; + + this._setHoverRange(this._rangeOrigin, this._rangeTarget); + }, + + _onrangemove: function(e) { + if (e.getIsTouchEvent()) { + return; + } + + var is_out = (e.getType() == 'mouseout'); + var target = e.getTarget(); + + this._updateRange(target, is_out); + }, + + _updateRange: function(target, is_out) { + // Don't update the range if this "" doesn't correspond to a line + // number. For instance, this may be a dead line number, like the empty + // line numbers on the left hand side of a newly added file. + var number = this.getLineNumberFromHeader(target); + if (!number) { + return; + } + + if (this._rangeActive) { + var origin = this._hoverOrigin; + + // Don't update the reticle if we're selecting a line range and the + // "" under the cursor is on the wrong side of the file. You can + // only leave inline comments on the left or right side of a file, not + // across lines on both sides. + var origin_side = this.getDisplaySideFromHeader(origin); + var target_side = this.getDisplaySideFromHeader(target); + if (origin_side != target_side) { + return; + } + + // Don't update the reticle if we're selecting a line range and the + // "" under the cursor corresponds to a different file. You can + // only leave inline comments on lines in a single file, not across + // multiple files. + var origin_table = JX.DOM.findAbove(origin, 'table'); + var target_table = JX.DOM.findAbove(target, 'table'); + if (origin_table != target_table) { + return; + } + } + + if (is_out) { + if (this._rangeActive) { + // If we're dragging a range, just leave the state as it is. This + // allows you to drag over something invalid while selecting a + // range without the range flickering or getting lost. + } else { + // Otherwise, clear the current range. + this.resetHover(); + } + return; + } + + if (this._rangeActive) { + this._rangeTarget = target; + } else { + this._rangeOrigin = target; + this._rangeTarget = target; + } + + this._setHoverRange(this._rangeOrigin, this._rangeTarget); + }, + + _onrangeup: function(e) { + if (!this._rangeActive) { + return; + } + + e.kill(); + + var origin = this._rangeOrigin; + var target = this._rangeTarget; + + // If the user dragged a range from the bottom to the top, swap the node + // order around. + if (JX.$V(origin).y > JX.$V(target).y) { + var tmp = target; + target = origin; + origin = tmp; + } + + var node = JX.DOM.findAbove(origin, null, 'differential-changeset'); + var changeset = this.getChangesetForNode(node); + + changeset.newInlineForRange(origin, target); + + this._rangeActive = false; + this._rangeOrigin = null; + this._rangeTarget = null; + + this.resetHover(); + }, + + _redrawBanner: function() { + // If the inline comment menu is open and we've done a redraw, close it. + // In particular, this makes it close when you scroll the document: + // otherwise, it stays open but the banner moves underneath it. + if (this._dropdownMenu) { + this._dropdownMenu.close(); + } + + var node = this._getBannerNode(); + var changeset = this._getVisibleChangeset(); + + // Don't do anything if nothing has changed. This seems to avoid some + // flickering issues in Safari, at least. + if (this._bannerChangeset === changeset) { + return; + } + this._bannerChangeset = changeset; + + if (!changeset) { + JX.DOM.remove(node); + return; + } + + var inlines = this._getInlinesByType(); + + var unsaved = inlines.unsaved; + var unsubmitted = inlines.unsubmitted; + var undone = inlines.undone; + var done = inlines.done; + var draft_done = inlines.draftDone; + + JX.DOM.alterClass( + node, + 'diff-banner-has-unsaved', + !!unsaved.length); + + JX.DOM.alterClass( + node, + 'diff-banner-has-unsubmitted', + !!unsubmitted.length); + + JX.DOM.alterClass( + node, + 'diff-banner-has-draft-done', + !!draft_done.length); + + var pht = this.getTranslations(); + var unsaved_button = this._getUnsavedButton(); + var unsubmitted_button = this._getUnsubmittedButton(); + var done_button = this._getDoneButton(); + var menu_button = this._getMenuButton(); + + if (unsaved.length) { + unsaved_button.setText(unsaved.length + ' ' + pht('Unsaved')); + JX.DOM.show(unsaved_button.getNode()); + } else { + JX.DOM.hide(unsaved_button.getNode()); + } + + if (unsubmitted.length || draft_done.length) { + var any_draft_count = unsubmitted.length + draft_done.length; + + unsubmitted_button.setText(any_draft_count + ' ' + pht('Unsubmitted')); + JX.DOM.show(unsubmitted_button.getNode()); + } else { + JX.DOM.hide(unsubmitted_button.getNode()); + } + + if (done.length || undone.length) { + // If you haven't marked any comments as "Done", we just show text + // like "3 Comments". If you've marked at least one done, we show + // "1 / 3 Comments". + + var done_text; + if (done.length) { + done_text = [ + done.length, + ' / ', + (done.length + undone.length), + ' ', + pht('Comments') + ]; + } else { + done_text = [ + undone.length, + ' ', + pht('Comments') + ]; + } + + done_button.setText(done_text); + + JX.DOM.show(done_button.getNode()); + + // If any comments are not marked "Done", this cycles through the + // missing comments. Otherwise, it cycles through all the saved + // comments. + if (undone.length) { + this._doneMode = 'undone'; + } else { + this._doneMode = 'done'; + } + + } else { + JX.DOM.hide(done_button.getNode()); + } + + var path_view = [icon, ' ', changeset.getDisplayPath()]; + + var buttons_attrs = { + className: 'diff-banner-buttons' + }; + + var buttons_list = [ + unsaved_button.getNode(), + unsubmitted_button.getNode(), + done_button.getNode(), + menu_button.getNode() + ]; + + var buttons_view = JX.$N('div', buttons_attrs, buttons_list); + + var icon = new JX.PHUIXIconView() + .setIcon(changeset.getIcon()) + .getNode(); + JX.DOM.setContent(node, [buttons_view, path_view]); + + document.body.appendChild(node); + }, + + _getInlinesByType: function() { + var changesets = this._changesets; + var unsaved = []; + var unsubmitted = []; + var undone = []; + var done = []; + var draft_done = []; + + var visible_done = []; + var visible_collapsed = []; + var visible_ghosts = []; + var visible = []; + var hidden = []; + + for (var ii = 0; ii < changesets.length; ii++) { + var inlines = changesets[ii].getInlines(); + var inline; + var jj; + for (jj = 0; jj < inlines.length; jj++) { + inline = inlines[jj]; + + if (inline.isDeleted()) { + continue; + } + + if (inline.isSynthetic()) { + continue; + } + + if (inline.isEditing()) { + unsaved.push(inline); + } else if (!inline.getID()) { + // These are new comments which have been cancelled, and do not + // count as anything. + continue; + } else if (inline.isDraft()) { + unsubmitted.push(inline); + } else { + // NOTE: Unlike other states, an inline may be marked with a + // draft checkmark and still be a "done" or "undone" comment. + if (inline.isDraftDone()) { + draft_done.push(inline); + } + + if (!inline.isDone()) { + undone.push(inline); + } else { + done.push(inline); + } + } + } + + for (jj = 0; jj < inlines.length; jj++) { + inline = inlines[jj]; + if (inline.isDeleted()) { + continue; + } + + if (inline.isEditing()) { + continue; + } + + if (inline.isHidden()) { + hidden.push(inline); + continue; + } + + visible.push(inline); + + if (inline.isDone()) { + visible_done.push(inline); + } + + if (inline.isCollapsed()) { + visible_collapsed.push(inline); + } + + if (inline.isGhost()) { + visible_ghosts.push(inline); + } + } + } + + return { + unsaved: unsaved, + unsubmitted: unsubmitted, + undone: undone, + done: done, + draftDone: draft_done, + visibleDone: visible_done, + visibleGhosts: visible_ghosts, + visibleCollapsed: visible_collapsed, + visible: visible, + hidden: hidden + }; + + }, + + _getUnsavedButton: function() { + if (!this._unsavedButton) { + var button = new JX.PHUIXButtonView() + .setIcon('fa-commenting-o') + .setButtonType(JX.PHUIXButtonView.BUTTONTYPE_SIMPLE); + + var node = button.getNode(); + + var onunsaved = JX.bind(this, this._onunsavedclick); + JX.DOM.listen(node, 'click', null, onunsaved); + + this._unsavedButton = button; + } + + return this._unsavedButton; + }, + + _getUnsubmittedButton: function() { + if (!this._unsubmittedButton) { + var button = new JX.PHUIXButtonView() + .setIcon('fa-comment-o') + .setButtonType(JX.PHUIXButtonView.BUTTONTYPE_SIMPLE); + + var node = button.getNode(); + + var onunsubmitted = JX.bind(this, this._onunsubmittedclick); + JX.DOM.listen(node, 'click', null, onunsubmitted); + + this._unsubmittedButton = button; + } + + return this._unsubmittedButton; + }, + + _getDoneButton: function() { + if (!this._doneButton) { + var button = new JX.PHUIXButtonView() + .setIcon('fa-comment') + .setButtonType(JX.PHUIXButtonView.BUTTONTYPE_SIMPLE); + + var node = button.getNode(); + + var ondone = JX.bind(this, this._ondoneclick); + JX.DOM.listen(node, 'click', null, ondone); + + this._doneButton = button; + } + + return this._doneButton; + }, + + _getMenuButton: function() { + if (!this._menuButton) { + var button = new JX.PHUIXButtonView() + .setIcon('fa-bars') + .setButtonType(JX.PHUIXButtonView.BUTTONTYPE_SIMPLE); + + var dropdown = new JX.PHUIXDropdownMenu(button.getNode()); + this._menuItems = {}; + + var list = new JX.PHUIXActionListView(); + dropdown.setContent(list.getNode()); + + var map = { + hideDone: { + type: 'done' + }, + hideCollapsed: { + type: 'collapsed' + }, + hideGhosts: { + type: 'ghosts' + }, + hideAll: { + type: 'all' + }, + showAll: { + type: 'show' + } + }; + + for (var k in map) { + var spec = map[k]; + + var handler = JX.bind(this, this._onhideinlines, spec.type); + var item = new JX.PHUIXActionView() + .setHandler(handler); + + list.addItem(item); + this._menuItems[k] = item; + } + + dropdown.listen('open', JX.bind(this, this._ondropdown)); + + var pht = this.getTranslations(); + + if (this.getInlineListURI()) { + list.addItem( + new JX.PHUIXActionView() + .setDivider(true)); + + list.addItem( + new JX.PHUIXActionView() + .setIcon('fa-external-link') + .setName(pht('List Inline Comments')) + .setHref(this.getInlineListURI())); + } + + this._menuButton = button; + this._dropdownMenu = dropdown; + } + + return this._menuButton; + }, + + _ondropdown: function() { + var inlines = this._getInlinesByType(); + var items = this._menuItems; + var pht = this.getTranslations(); + + items.hideDone + .setName(pht('Hide "Done" Inlines')) + .setDisabled(!inlines.visibleDone.length); + + items.hideCollapsed + .setName(pht('Hide Collapsed Inlines')) + .setDisabled(!inlines.visibleCollapsed.length); + + items.hideGhosts + .setName(pht('Hide Older Inlines')) + .setDisabled(!inlines.visibleGhosts.length); + + items.hideAll + .setName(pht('Hide All Inlines')) + .setDisabled(!inlines.visible.length); + + items.showAll + .setName(pht('Show All Inlines')) + .setDisabled(!inlines.hidden.length); + }, + + _onhideinlines: function(type, e) { + this._dropdownMenu.close(); + e.prevent(); + + this._toggleInlines(type); + }, + + _toggleInlines: function(type) { + var inlines = this._getInlinesByType(); + + // Clear the selection state since we end up in a weird place if the + // user hides the selected inline. + this._setSelectionState(null); + + var targets; + var mode = true; + switch (type) { + case 'done': + targets = inlines.visibleDone; + break; + case 'collapsed': + targets = inlines.visibleCollapsed; + break; + case 'ghosts': + targets = inlines.visibleGhosts; + break; + case 'all': + targets = inlines.visible; + break; + case 'show': + targets = inlines.hidden; + mode = false; + break; + } + + for (var ii = 0; ii < targets.length; ii++) { + targets[ii].setHidden(mode); + } + }, + + _onunsavedclick: function(e) { + e.kill(); + + var options = { + filter: 'comment', + wrap: true, + show: true, + attribute: 'unsaved' + }; + + this._onjumpkey(1, options); + }, + + _onunsubmittedclick: function(e) { + e.kill(); + + var options = { + filter: 'comment', + wrap: true, + show: true, + attribute: 'anyDraft' + }; + + this._onjumpkey(1, options); + }, + + _ondoneclick: function(e) { + e.kill(); + + var options = { + filter: 'comment', + wrap: true, + show: true, + attribute: this._doneMode + }; + + this._onjumpkey(1, options); + }, + + _getBannerNode: function() { + if (!this._bannerNode) { + var attributes = { + className: 'diff-banner', + id: 'diff-banner' + }; + + this._bannerNode = JX.$N('div', attributes); + } + + return this._bannerNode; + }, + + _getVisibleChangeset: function() { + if (this.isAsleep()) { + return null; + } + + if (JX.Device.getDevice() != 'desktop') { + return null; + } + + // Never show the banner if we're very near the top of the page. + var margin = 480; + var s = JX.Vector.getScroll(); + if (s.y < margin) { + return null; + } + + // We're going to find the changeset which spans an invisible line a + // little underneath the bottom of the banner. This makes the header + // tick over from "A.txt" to "B.txt" just as "A.txt" scrolls completely + // offscreen. + var detect_height = 64; + + for (var ii = 0; ii < this._changesets.length; ii++) { + var changeset = this._changesets[ii]; + var c = changeset.getVectors(); + + // If the changeset starts above the line... + if (c.pos.y <= (s.y + detect_height)) { + // ...and ends below the line, this is the current visible changeset. + if ((c.pos.y + c.dim.y) >= (s.y + detect_height)) { + return changeset; + } + } + } + + return null; + } + } + +}); diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js new file mode 100644 index 0000000000..fef6b2087a --- /dev/null +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -0,0 +1,758 @@ +/** + * @provides phabricator-diff-inline + * @requires javelin-dom + * @javelin + */ + +JX.install('DiffInline', { + + construct : function() { + }, + + members: { + _id: null, + _phid: null, + _changesetID: null, + _row: null, + _number: null, + _length: null, + _displaySide: null, + _isNewFile: null, + _undoRow: null, + _replyToCommentPHID: null, + _originalText: null, + _snippet: null, + + _isDeleted: false, + _isInvisible: false, + _isLoading: false, + + _changeset: null, + + _isCollapsed: false, + _isDraft: null, + _isDraftDone: null, + _isFixed: null, + _isEditing: false, + _isNew: false, + _isSynthetic: false, + _isHidden: false, + + bindToRow: function(row) { + this._row = row; + + var row_data = JX.Stratcom.getData(row); + row_data.inline = this; + this._isCollapsed = row_data.hidden || false; + + // TODO: Get smarter about this once we do more editing, this is pretty + // hacky. + var comment = JX.DOM.find(row, 'div', 'differential-inline-comment'); + var data = JX.Stratcom.getData(comment); + + this._id = data.id; + this._phid = data.phid; + + // TODO: This is very, very, very, very, very, very, very hacky. + var td = comment.parentNode; + var th = td.previousSibling; + if (th.parentNode.firstChild != th) { + this._displaySide = 'right'; + } else { + this._displaySide = 'left'; + } + + this._number = parseInt(data.number, 10); + this._length = parseInt(data.length, 10); + this._originalText = data.original; + this._isNewFile = + (this.getDisplaySide() == 'right') || + (data.left != data.right); + + this._replyToCommentPHID = data.replyToCommentPHID; + + this._isDraft = data.isDraft; + this._isFixed = data.isFixed; + this._isGhost = data.isGhost; + this._isSynthetic = data.isSynthetic; + this._isDraftDone = data.isDraftDone; + + this._changesetID = data.changesetID; + this._isNew = false; + this._snippet = data.snippet; + + this.setInvisible(false); + + return this; + }, + + isDraft: function() { + return this._isDraft; + }, + + isDone: function() { + return this._isFixed; + }, + + isEditing: function() { + return this._isEditing; + }, + + isDeleted: function() { + return this._isDeleted; + }, + + isSynthetic: function() { + return this._isSynthetic; + }, + + isDraftDone: function() { + return this._isDraftDone; + }, + + isHidden: function() { + return this._isHidden; + }, + + isGhost: function() { + return this._isGhost; + }, + + bindToRange: function(data) { + this._displaySide = data.displaySide; + this._number = parseInt(data.number, 10); + this._length = parseInt(data.length, 10); + this._isNewFile = data.isNewFile; + this._changesetID = data.changesetID; + this._isNew = true; + + // Insert the comment after any other comments which already appear on + // the same row. + var parent_row = JX.DOM.findAbove(data.target, 'tr'); + var target_row = parent_row.nextSibling; + while (target_row && JX.Stratcom.hasSigil(target_row, 'inline-row')) { + target_row = target_row.nextSibling; + } + + var row = this._newRow(); + parent_row.parentNode.insertBefore(row, target_row); + + this.setInvisible(true); + + return this; + }, + + bindToReply: function(inline) { + this._displaySide = inline._displaySide; + this._number = inline._number; + this._length = inline._length; + this._isNewFile = inline._isNewFile; + this._changesetID = inline._changesetID; + this._isNew = true; + + this._replyToCommentPHID = inline._phid; + + var changeset = this.getChangeset(); + + // We're going to figure out where in the document to position the new + // inline. Normally, it goes after any existing inline rows (so if + // several inlines reply to the same line, they appear in chronological + // order). + + // However: if inlines are threaded, we want to put the new inline in + // the right place in the thread. This might be somewhere in the middle, + // so we need to do a bit more work to figure it out. + + // To find the right place in the thread, we're going to look for any + // inline which is at or above the level of the comment we're replying + // to. This means we've reached a new fork of the thread, and should + // put our new inline before the comment we found. + var ancestor_map = {}; + var ancestor = inline; + var reply_phid; + while (ancestor) { + reply_phid = ancestor.getReplyToCommentPHID(); + if (!reply_phid) { + break; + } + ancestor_map[reply_phid] = true; + ancestor = changeset.getInlineByPHID(reply_phid); + } + + var parent_row = inline._row; + var target_row = parent_row.nextSibling; + while (target_row && JX.Stratcom.hasSigil(target_row, 'inline-row')) { + var target = changeset.getInlineForRow(target_row); + reply_phid = target.getReplyToCommentPHID(); + + // If we found an inline which is replying directly to some ancestor + // of this new comment, this is where the new rows go. + if (ancestor_map.hasOwnProperty(reply_phid)) { + break; + } + + target_row = target_row.nextSibling; + } + + var row = this._newRow(); + parent_row.parentNode.insertBefore(row, target_row); + + this.setInvisible(true); + + return this; + }, + + setChangeset: function(changeset) { + this._changeset = changeset; + return this; + }, + + getChangeset: function() { + return this._changeset; + }, + + setEditing: function(editing) { + this._isEditing = editing; + return this; + }, + + setHidden: function(hidden) { + this._isHidden = hidden; + this._redraw(); + return this; + }, + + canReply: function() { + if (!this._hasAction('reply')) { + return false; + } + + return true; + }, + + canEdit: function() { + if (!this._hasAction('edit')) { + return false; + } + + return true; + }, + + canDone: function() { + if (!JX.DOM.scry(this._row, 'input', 'differential-inline-done').length) { + return false; + } + + return true; + }, + + canCollapse: function() { + if (!JX.DOM.scry(this._row, 'a', 'hide-inline').length) { + return false; + } + + return true; + }, + + getRawText: function() { + return this._originalText; + }, + + _hasAction: function(action) { + var nodes = JX.DOM.scry(this._row, 'a', 'differential-inline-' + action); + return (nodes.length > 0); + }, + + _newRow: function() { + var attributes = { + sigil: 'inline-row' + }; + + var row = JX.$N('tr', attributes); + + JX.Stratcom.getData(row).inline = this; + this._row = row; + + this._id = null; + this._phid = null; + this._isCollapsed = false; + + this._originalText = null; + + return row; + }, + + setCollapsed: function(collapsed) { + this._isCollapsed = collapsed; + + var op; + if (collapsed) { + op = 'hide'; + } else { + op = 'show'; + } + + var inline_uri = this._getInlineURI(); + var comment_id = this._id; + + new JX.Workflow(inline_uri, {op: op, ids: comment_id}) + .setHandler(JX.bag) + .start(); + + this._redraw(); + this._didUpdate(true); + }, + + isCollapsed: function() { + return this._isCollapsed; + }, + + toggleDone: function() { + var uri = this._getInlineURI(); + var data = { + op: 'done', + id: this._id + }; + + var ondone = JX.bind(this, this._ondone); + + new JX.Workflow(uri, data) + .setHandler(ondone) + .start(); + }, + + _ondone: function(response) { + var checkbox = JX.DOM.find( + this._row, + 'input', + 'differential-inline-done'); + + checkbox.checked = (response.isChecked ? 'checked' : null); + + var comment = JX.DOM.findAbove( + checkbox, + 'div', + 'differential-inline-comment'); + + JX.DOM.alterClass(comment, 'inline-is-done', response.isChecked); + + // NOTE: This is marking the inline as having an unsubmitted checkmark, + // as opposed to a submitted checkmark. This is different from the + // top-level "draft" state of unsubmitted comments. + JX.DOM.alterClass(comment, 'inline-state-is-draft', response.draftState); + + this._isFixed = response.isChecked; + this._isDraftDone = !!response.draftState; + + this._didUpdate(); + }, + + create: function(text) { + var uri = this._getInlineURI(); + var handler = JX.bind(this, this._oncreateresponse); + var data = this._newRequestData('new', text); + + this.setLoading(true); + + new JX.Request(uri, handler) + .setData(data) + .send(); + }, + + reply: function(text) { + var changeset = this.getChangeset(); + return changeset.newInlineReply(this, text); + }, + + edit: function(text) { + var uri = this._getInlineURI(); + var handler = JX.bind(this, this._oneditresponse); + var data = this._newRequestData('edit', text || null); + + this.setLoading(true); + + new JX.Request(uri, handler) + .setData(data) + .send(); + }, + + delete: function(is_ref) { + var uri = this._getInlineURI(); + var handler = JX.bind(this, this._ondeleteresponse); + + // NOTE: This may be a direct delete (the user clicked on the inline + // itself) or a "refdelete" (the user clicked somewhere else, like the + // preview, but the inline is present on the page). + + // For a "refdelete", we prompt the user to confirm that they want to + // delete the comment, because they can not undo deletions from the + // preview. We could jump the user to the inline instead, but this would + // be somewhat disruptive and make deleting several comments more + // difficult. + + var op; + if (is_ref) { + op = 'refdelete'; + } else { + op = 'delete'; + } + + var data = this._newRequestData(op); + + this.setLoading(true); + + new JX.Workflow(uri, data) + .setHandler(handler) + .start(); + }, + + getDisplaySide: function() { + return this._displaySide; + }, + + getLineNumber: function() { + return this._number; + }, + + getLineLength: function() { + return this._length; + }, + + isNewFile: function() { + return this._isNewFile; + }, + + getID: function() { + return this._id; + }, + + getPHID: function() { + return this._phid; + }, + + getChangesetID: function() { + return this._changesetID; + }, + + getReplyToCommentPHID: function() { + return this._replyToCommentPHID; + }, + + setDeleted: function(deleted) { + this._isDeleted = deleted; + this._redraw(); + return this; + }, + + setInvisible: function(invisible) { + this._isInvisible = invisible; + this._redraw(); + return this; + }, + + setLoading: function(loading) { + this._isLoading = loading; + this._redraw(); + return this; + }, + + _newRequestData: function(operation, text) { + return { + op: operation, + id: this._id, + on_right: ((this.getDisplaySide() == 'right') ? 1 : 0), + renderer: this.getChangeset().getRenderer(), + number: this.getLineNumber(), + length: this.getLineLength(), + is_new: this.isNewFile(), + changesetID: this.getChangesetID(), + replyToCommentPHID: this.getReplyToCommentPHID() || '', + text: text || '' + }; + }, + + _oneditresponse: function(response) { + var rows = JX.$H(response).getNode(); + + this._drawEditRows(rows); + + this.setLoading(false); + this.setInvisible(true); + }, + + _oncreateresponse: function(response) { + var rows = JX.$H(response).getNode(); + + this._drawEditRows(rows); + }, + + _ondeleteresponse: function() { + this._drawUndeleteRows(); + + this.setLoading(false); + this.setDeleted(true); + + this._didUpdate(); + }, + + _drawUndeleteRows: function() { + return this._drawUndoRows('undelete', this._row); + }, + + _drawUneditRows: function(text) { + return this._drawUndoRows('unedit', null, text); + }, + + _drawUndoRows: function(mode, cursor, text) { + var templates = this.getChangeset().getUndoTemplates(); + + var template; + if (this.getDisplaySide() == 'right') { + template = templates.r; + } else { + template = templates.l; + } + template = JX.$H(template).getNode(); + + this._undoRow = this._drawRows(template, cursor, mode, text); + }, + + _drawContentRows: function(rows) { + return this._drawRows(rows, null, 'content'); + }, + + _drawEditRows: function(rows) { + this.setEditing(true); + return this._drawRows(rows, null, 'edit'); + }, + + _drawRows: function(rows, cursor, type, text) { + var first_row = JX.DOM.scry(rows, 'tr')[0]; + var first_meta; + var row = first_row; + var anchor = cursor || this._row; + cursor = cursor || this._row.nextSibling; + + var next_row; + while (row) { + // Grab this first, since it's going to change once we insert the row + // into the document. + next_row = row.nextSibling; + + // Bind edit and undo rows to this DiffInline object so that + // interactions like hovering work properly. + JX.Stratcom.getData(row).inline = this; + + anchor.parentNode.insertBefore(row, cursor); + cursor = row; + + var row_meta = { + node: row, + type: type, + text: text || null, + listeners: [] + }; + + if (!first_meta) { + first_meta = row_meta; + } + + if (type == 'edit') { + row_meta.listeners.push( + JX.DOM.listen( + row, + ['submit', 'didSyntheticSubmit'], + 'inline-edit-form', + JX.bind(this, this._onsubmit, row_meta))); + + row_meta.listeners.push( + JX.DOM.listen( + row, + 'click', + 'inline-edit-cancel', + JX.bind(this, this._oncancel, row_meta))); + } else if (type == 'content') { + // No special listeners for these rows. + } else { + row_meta.listeners.push( + JX.DOM.listen( + row, + 'click', + 'differential-inline-comment-undo', + JX.bind(this, this._onundo, row_meta))); + } + + // If the row has a textarea, focus it. This allows the user to start + // typing a comment immediately after a "new", "edit", or "reply" + // action. + var textareas = JX.DOM.scry( + row, + 'textarea', + 'differential-inline-comment-edit-textarea'); + if (textareas.length) { + var area = textareas[0]; + area.focus(); + + var length = area.value.length; + JX.TextAreaUtils.setSelectionRange(area, length, length); + } + + row = next_row; + } + + JX.Stratcom.invoke('resize'); + + return first_meta; + }, + + _onsubmit: function(row, e) { + e.kill(); + + var handler = JX.bind(this, this._onsubmitresponse, row); + + this.setLoading(true); + + JX.Workflow.newFromForm(e.getTarget()) + .setHandler(handler) + .start(); + }, + + _onundo: function(row, e) { + e.kill(); + + this._removeRow(row); + + if (row.type == 'undelete') { + var uri = this._getInlineURI(); + var data = this._newRequestData('undelete'); + var handler = JX.bind(this, this._onundelete); + + this.setDeleted(false); + this.setLoading(true); + + new JX.Request(uri, handler) + .setData(data) + .send(); + } + + if (row.type == 'unedit') { + if (this.getID()) { + this.edit(row.text); + } else { + this.create(row.text); + } + } + }, + + _onundelete: function() { + this.setLoading(false); + this._didUpdate(); + }, + + _oncancel: function(row, e) { + e.kill(); + + var text = this._readText(row.node); + if (text && text.length && (text != this._originalText)) { + this._drawUneditRows(text); + } + + this._removeRow(row); + this.setEditing(false); + + this.setInvisible(false); + + this._didUpdate(true); + }, + + _readText: function(row) { + var textarea; + try { + textarea = JX.DOM.find( + row, + 'textarea', + 'differential-inline-comment-edit-textarea'); + } catch (ex) { + return null; + } + + return textarea.value; + }, + + _onsubmitresponse: function(row, response) { + this._removeRow(row); + + this.setLoading(false); + this.setInvisible(false); + this.setEditing(false); + + this._onupdate(response); + }, + + _onupdate: function(response) { + var new_row; + if (response.markup) { + new_row = this._drawContentRows(JX.$H(response.markup).getNode()).node; + } + + // TODO: Save the old row so the action it's undo-able if it was a + // delete. + var remove_old = true; + if (remove_old) { + JX.DOM.remove(this._row); + } + + // If you delete the content on a comment and save it, it acts like a + // delete: the server does not return a new row. + if (new_row) { + this.bindToRow(new_row); + } else { + this.setDeleted(true); + this._row = null; + } + + this._didUpdate(); + }, + + _didUpdate: function(local_only) { + // After making changes to inline comments, refresh the transaction + // preview at the bottom of the page. + if (!local_only) { + this.getChangeset().getChangesetList().redrawPreview(); + } + + this.getChangeset().getChangesetList().redrawCursor(); + this.getChangeset().getChangesetList().resetHover(); + + // Emit a resize event so that UI elements like the keyboard focus + // reticle can redraw properly. + JX.Stratcom.invoke('resize'); + }, + + _redraw: function() { + var is_invisible = + (this._isInvisible || this._isDeleted || this._isHidden); + var is_loading = this._isLoading; + var is_collapsed = (this._isCollapsed && !this._isHidden); + + var row = this._row; + JX.DOM.alterClass(row, 'differential-inline-hidden', is_invisible); + JX.DOM.alterClass(row, 'differential-inline-loading', is_loading); + JX.DOM.alterClass(row, 'inline-hidden', is_collapsed); + }, + + _removeRow: function(row) { + JX.DOM.remove(row.node); + for (var ii = 0; ii < row.listeners.length; ii++) { + row.listeners[ii].remove(); + } + }, + + _getInlineURI: function() { + var changeset = this.getChangeset(); + var list = changeset.getChangesetList(); + return list.getInlineURI(); + } + } + +}); diff --git a/webroot/rsrc/js/application/differential/ChangesetViewManager.js b/webroot/rsrc/js/application/differential/ChangesetViewManager.js deleted file mode 100644 index 0c23e18737..0000000000 --- a/webroot/rsrc/js/application/differential/ChangesetViewManager.js +++ /dev/null @@ -1,397 +0,0 @@ -/** - * @provides changeset-view-manager - * @requires javelin-dom - * javelin-util - * javelin-stratcom - * javelin-install - * javelin-workflow - * javelin-router - * javelin-behavior-device - * javelin-vector - */ - - -JX.install('ChangesetViewManager', { - - construct : function(node) { - this._node = node; - - var data = this._getNodeData(); - this._renderURI = data.renderURI; - this._ref = data.ref; - this._whitespace = data.whitespace; - this._renderer = data.renderer; - this._highlight = data.highlight; - this._encoding = data.encoding; - this._loaded = data.loaded; - }, - - members: { - _node: null, - _loaded: false, - _sequence: 0, - _stabilize: false, - - _renderURI: null, - _ref: null, - _whitespace: null, - _renderer: null, - _highlight: null, - _encoding: null, - _undoTemplates: null, - - - /** - * Has the content of this changeset been loaded? - * - * This method returns `true` if a request has been fired, even if the - * response has not returned yet. - * - * @return bool True if the content has been loaded. - */ - isLoaded: function() { - return this._loaded; - }, - - - /** - * Configure stabilization of the document position on content load. - * - * When we dump the changeset into the document, we can try to stabilize - * the document scroll position so that the user doesn't feel like they - * are jumping around as things load in. This is generally useful when - * populating initial changes. - * - * However, if a user explicitly requests a content load by clicking a - * "Load" link or using the dropdown menu, this stabilization generally - * feels unnatural, so we don't use it in response to explicit user action. - * - * @param bool True to stabilize the next content fill. - * @return this - */ - setStabilize: function(stabilize) { - this._stabilize = stabilize; - return this; - }, - - - /** - * Should this changeset load immediately when the page loads? - * - * Normally, changes load immediately, but if a diff or commit is very - * large we stop doing this and have the user load files explicitly, or - * choose to load everything. - * - * @return bool True if the changeset should load automatically when the - * page loads. - */ - shouldAutoload: function() { - return this._getNodeData().autoload; - }, - - - /** - * Load this changeset, if it isn't already loading. - * - * This fires a request to fill the content of this changeset, provided - * there isn't already a request in flight. To force a reload, use - * @{method:reload}. - * - * @return this - */ - load: function() { - if (this._loaded) { - return this; - } - - return this.reload(); - }, - - - /** - * Reload the changeset content. - * - * This method always issues a request, even if the content is already - * loading. To load conditionally, use @{method:load}. - * - * @return this - */ - reload: function() { - this._loaded = true; - this._sequence++; - - var params = this._getViewParameters(); - - var workflow = new JX.Workflow(this._renderURI, params) - .setHandler(JX.bind(this, this._onresponse, this._sequence)); - - this._startContentWorkflow(workflow); - - JX.DOM.setContent( - this._getContentFrame(), - JX.$N( - 'div', - {className: 'differential-loading'}, - 'Loading...')); - - return this; - }, - - /** - * Load missing context in a changeset. - * - * We do this when the user clicks "Show X Lines". We also expand all of - * the missing context when they "Show All Context". - * - * @param string Line range specification, like "0-40/0-20". - * @param node Row where the context should be rendered after loading. - * @param bool True if this is a bulk load of multiple context blocks. - * @return this - */ - loadContext: function(range, target, bulk) { - var params = this._getViewParameters(); - params.range = range; - - var container = JX.DOM.scry(target, 'td')[0]; - // TODO: pht() - JX.DOM.setContent(container, 'Loading...'); - JX.DOM.alterClass(target, 'differential-show-more-loading', true); - - var workflow = new JX.Workflow(this._renderURI, params) - .setHandler(JX.bind(this, this._oncontext, target)); - - if (bulk) { - // If we're loading a bunch of these because the viewer clicked - // "Show All Context" or similar, use lower-priority requests - // and draw a progress bar. - this._startContentWorkflow(workflow); - } else { - // If this is a single click on a context link, use a higher priority - // load without a chrome change. - workflow.start(); - } - - return this; - }, - - _startContentWorkflow: function(workflow) { - var routable = workflow.getRoutable(); - - routable - .setPriority(500) - .setType('content') - .setKey(this._getRoutableKey()); - - JX.Router.getInstance().queue(routable); - }, - - - /** - * Receive a response to a context request. - */ - _oncontext: function(target, response) { - // TODO: This should be better structured. - // If the response comes back with several top-level nodes, the last one - // is the actual context; the others are headers. Add any headers first, - // then copy the new rows into the document. - var markup = JX.$H(response.changeset).getFragment(); - var len = markup.childNodes.length; - var diff = JX.DOM.findAbove(target, 'table', 'differential-diff'); - - for (var ii = 0; ii < len - 1; ii++) { - diff.parentNode.insertBefore(markup.firstChild, diff); - } - - var table = markup.firstChild; - var root = target.parentNode; - this._moveRows(table, root, target); - root.removeChild(target); - - this._onchangesetresponse(response); - }, - - _moveRows: function(src, dst, before) { - var rows = JX.DOM.scry(src, 'tr'); - for (var ii = 0; ii < rows.length; ii++) { - - // Find the table this belongs to. If it's a sub-table, like a - // table in an inline comment, don't copy it. - if (JX.DOM.findAbove(rows[ii], 'table') !== src) { - continue; - } - - if (before) { - dst.insertBefore(rows[ii], before); - } else { - dst.appendChild(rows[ii]); - } - } - }, - - /** - * Get parameters which define the current rendering options. - */ - _getViewParameters: function() { - return { - ref: this._ref, - whitespace: this._whitespace || '', - renderer: this.getRenderer() || '', - highlight: this._highlight || '', - encoding: this._encoding || '' - }; - }, - - /** - * Get the active @{class:JX.Routable} for this changeset. - * - * After issuing a request with @{method:load} or @{method:reload}, you - * can adjust routable settings (like priority) by querying the routable - * with this method. Note that there may not be a current routable. - * - * @return JX.Routable|null Active routable, if one exists. - */ - getRoutable: function() { - return JX.Router.getInstance().getRoutableByKey(this._getRoutableKey()); - }, - - setRenderer: function(renderer) { - this._renderer = renderer; - return this; - }, - - getRenderer: function() { - if (this._renderer !== null) { - return this._renderer; - } - - // NOTE: If you load the page at one device resolution and then resize to - // a different one we don't re-render the diffs, because it's a - // complicated mess and you could lose inline comments, cursor positions, - // etc. - return (JX.Device.getDevice() == 'desktop') ? '2up' : '1up'; - }, - - getUndoTemplates: function() { - return this._undoTemplates; - }, - - setEncoding: function(encoding) { - this._encoding = encoding; - return this; - }, - - getEncoding: function() { - return this._encoding; - }, - - setHighlight: function(highlight) { - this._highlight = highlight; - return this; - }, - - getHighlight: function() { - return this._highlight; - }, - - _getNodeData: function() { - return JX.Stratcom.getData(this._node); - }, - - - _onresponse: function(sequence, response) { - if (sequence != this._sequence) { - // If this isn't the most recent request, ignore it. This normally - // means the user changed view settings between the time the page loaded - // and the content filled. - return; - } - - // As we populate the changeset list, we try to hold the document scroll - // position steady, so that, e.g., users who want to leave a comment on a - // diff with a large number of changes don't constantly have the text - // area scrolled off the bottom of the screen until the entire diff loads. - // - // There are two three major cases here: - // - // - If we're near the top of the document, never scroll. - // - If we're near the bottom of the document, always scroll. - // - Otherwise, scroll if the changes were above the midline of the - // viewport. - - var target = this._node; - - var old_pos = JX.Vector.getScroll(); - var old_view = JX.Vector.getViewport(); - var old_dim = JX.Vector.getDocument(); - - // Number of pixels away from the top or bottom of the document which - // count as "nearby". - var sticky = 480; - - var near_top = (old_pos.y <= sticky); - var near_bot = ((old_pos.y + old_view.y) >= (old_dim.y - sticky)); - - var target_pos = JX.Vector.getPos(target); - var target_dim = JX.Vector.getDim(target); - var target_mid = (target_pos.y + (target_dim.y / 2)); - - var view_mid = (old_pos.y + (old_view.y / 2)); - var above_mid = (target_mid < view_mid); - - var frame = this._getContentFrame(); - JX.DOM.setContent(frame, JX.$H(response.changeset)); - - if (this._stabilize) { - if (!near_top) { - if (near_bot || above_mid) { - // Figure out how much taller the document got. - var delta = (JX.Vector.getDocument().y - old_dim.y); - JX.DOM.scrollToPosition(old_pos.x, old_pos.y + delta); - } - } - this._stabilize = false; - } - - this._onchangesetresponse(response); - }, - - _onchangesetresponse: function(response) { - // Code shared by autoload and context responses. - - if (response.coverage) { - for (var k in response.coverage) { - try { - JX.DOM.replace(JX.$(k), JX.$H(response.coverage[k])); - } catch (ignored) { - // Not terribly important. - } - } - } - - if (response.undoTemplates) { - this._undoTemplates = response.undoTemplates; - } - - JX.Stratcom.invoke('differential-inline-comment-refresh'); - }, - - _getContentFrame: function() { - return JX.DOM.find(this._node, 'div', 'changeset-view-content'); - }, - - _getRoutableKey: function() { - return 'changeset-view.' + this._ref + '.' + this._sequence; - } - - }, - - statics: { - getForNode: function(node) { - var data = JX.Stratcom.getData(node); - if (!data.changesetViewManager) { - data.changesetViewManager = new JX.ChangesetViewManager(node); - } - return data.changesetViewManager; - } - } -}); diff --git a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js b/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js deleted file mode 100644 index 7e6015d969..0000000000 --- a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js +++ /dev/null @@ -1,382 +0,0 @@ -/** - * @provides differential-inline-comment-editor - * @requires javelin-dom - * javelin-util - * javelin-stratcom - * javelin-install - * javelin-request - * javelin-workflow - */ - -JX.install('DifferentialInlineCommentEditor', { - - construct : function(uri) { - this._uri = uri; - }, - - events : ['done'], - - members : { - _uri : null, - _undoText : null, - _completed: false, - _skipOverInlineCommentRows : function(node) { - // TODO: Move this semantic information out of class names. - while (node && node.className.indexOf('inline') !== -1) { - node = node.nextSibling; - } - return node; - }, - _buildRequestData : function() { - return { - op : this.getOperation(), - on_right : this.getOnRight(), - id : this.getID(), - number : this.getLineNumber(), - is_new : (this.getIsNew() ? 1 : 0), - length : this.getLength(), - changesetID : this.getChangesetID(), - text : this.getText() || '', - renderer: this.getRenderer(), - replyToCommentPHID: this.getReplyToCommentPHID() || '', - }; - }, - _draw : function(content, exact_row) { - var row = this.getRow(); - var table = this.getTable(); - var target = exact_row ? row : this._skipOverInlineCommentRows(row); - - function copyRows(dst, src, before) { - var rows = JX.DOM.scry(src, 'tr'); - for (var ii = 0; ii < rows.length; ii++) { - - // Find the table this belongs to. If it's a sub-table, like a - // table in an inline comment, don't copy it. - if (JX.DOM.findAbove(rows[ii], 'table') !== src) { - continue; - } - - if (before) { - dst.insertBefore(rows[ii], before); - } else { - dst.appendChild(rows[ii]); - } - } - return rows; - } - - return copyRows(table, content, target); - }, - _removeUndoLink : function() { - var rows = JX.DifferentialInlineCommentEditor._undoRows; - if (rows) { - for (var ii = 0; ii < rows.length; ii++) { - JX.DOM.remove(rows[ii]); - } - } - JX.DifferentialInlineCommentEditor._undoRows = []; - }, - _undo : function() { - this._removeUndoLink(); - - if (this._undoText) { - this.setText(this._undoText); - } else { - this.setOperation('undelete'); - } - - this.start(); - }, - _registerUndoListener : function() { - if (!JX.DifferentialInlineCommentEditor._activeEditor) { - JX.Stratcom.listen( - 'click', - 'differential-inline-comment-undo', - function(e) { - JX.DifferentialInlineCommentEditor._activeEditor._undo(); - e.kill(); - }); - } - JX.DifferentialInlineCommentEditor._activeEditor = this; - }, - _setRowState : function(state) { - var is_hidden = (state == 'hidden'); - var is_loading = (state == 'loading'); - var row = this.getRow(); - JX.DOM.alterClass(row, 'differential-inline-hidden', is_hidden); - JX.DOM.alterClass(row, 'differential-inline-loading', is_loading); - }, - _didContinueWorkflow : function(response) { - var drawn = this._draw(JX.$H(response).getNode()); - - var op = this.getOperation(); - if (op == 'edit') { - this._setRowState('hidden'); - } - - JX.DOM.find( - drawn[0], - 'textarea', - 'differential-inline-comment-edit-textarea').focus(); - - var oncancel = JX.bind(this, function(e) { - e.kill(); - - this._didCancelWorkflow(); - - if (op == 'edit') { - this._setRowState('visible'); - } - - JX.DOM.remove(drawn[0]); - }); - JX.DOM.listen(drawn[0], 'click', 'inline-edit-cancel', oncancel); - - var onsubmit = JX.bind(this, function(e) { - e.kill(); - - JX.Workflow.newFromForm(e.getTarget()) - .setHandler(JX.bind(this, function(response) { - JX.DOM.remove(drawn[0]); - if (op == 'edit') { - this._setRowState('visible'); - } - this._didCompleteWorkflow(response); - })) - .start(); - - JX.DOM.alterClass(drawn[0], 'differential-inline-loading', true); - }); - JX.DOM.listen( - drawn[0], - ['submit', 'didSyntheticSubmit'], - 'inline-edit-form', - onsubmit); - }, - - - _didCompleteWorkflow : function(response) { - var op = this.getOperation(); - - // We don't get any markup back if the user deletes a comment, or saves - // an empty comment (which effects a delete). - if (response.markup) { - this._draw(JX.$H(response.markup).getNode()); - } - - if (op == 'delete' || op == 'refdelete') { - this._undoText = null; - this._drawUndo(); - } else { - this._removeUndoLink(); - } - - // These operations remove the old row (edit adds a new row first). - var remove_old = (op == 'edit' || op == 'delete' || op == 'refdelete'); - if (remove_old) { - this._setRowState('hidden'); - } - - if (op == 'undelete') { - this._setRowState('visible'); - } - - this._completed = true; - - this._didUpdate(); - this.invoke('done'); - }, - - - _didCancelWorkflow : function() { - this.invoke('done'); - - switch (this.getOperation()) { - case 'delete': - case 'refdelete': - if (!this._completed) { - this._setRowState('visible'); - } - return; - case 'undelete': - return; - } - - var textarea; - try { - textarea = JX.DOM.find( - document.body, // TODO: use getDialogRootNode() when available - 'textarea', - 'differential-inline-comment-edit-textarea'); - } catch (ex) { - // The close handler is called whenever the dialog closes, even if the - // user closed it by completing the workflow with "Save". The - // JX.Workflow API should probably be refined to allow programmatic - // distinction of close caused by 'cancel' vs 'submit'. Testing for - // presence of the textarea serves as a proxy for detecting a 'cancel'. - return; - } - - var text = textarea.value; - - // If the user hasn't edited the text (i.e., no change from original for - // 'edit' or no text at all), don't offer them an undo. - if (text == this.getOriginalText() || text === '') { - return; - } - - // Save the text so we can 'undo' back to it. - this._undoText = text; - - this._drawUndo(); - }, - - _drawUndo: function() { - var templates = this.getTemplates(); - var template = this.getOnRight() ? templates.r : templates.l; - template = JX.$H(template).getNode(); - - // NOTE: Operation order matters here; we can't remove anything until - // after we draw the new rows because _draw uses the old rows to figure - // out where to place the comment. - - // We use 'exact_row' to put the "undo" text directly above the affected - // comment. - var exact_row = true; - var rows = this._draw(template, exact_row); - - this._removeUndoLink(); - - JX.DifferentialInlineCommentEditor._undoRows = rows; - }, - - _onBusyWorkflow: function() { - // If the user clicks the "Jump to Inline" button, scroll to the row - // being edited. - JX.DOM.scrollTo(this.getRow()); - }, - - start : function() { - var op = this.getOperation(); - - // The user is already editing a comment, we're going to give them an - // error message. - if (op == 'busy') { - var onbusy = JX.bind(this, this._onBusyWorkflow); - - new JX.Workflow(this._uri, {op: op}) - .setHandler(onbusy) - .start(); - - return this; - } - - this._registerUndoListener(); - var data = this._buildRequestData(); - - if (op == 'delete' || op == 'refdelete' || op == 'undelete') { - this._setRowState('loading'); - - var oncomplete = JX.bind(this, this._didCompleteWorkflow); - var oncancel = JX.bind(this, this._didCancelWorkflow); - - new JX.Workflow(this._uri, data) - .setHandler(oncomplete) - .setCloseHandler(oncancel) - .start(); - } else { - var handler = JX.bind(this, this._didContinueWorkflow); - - if (op == 'edit') { - this._setRowState('loading'); - } - - new JX.Request(this._uri, handler) - .setData(data) - .send(); - } - - return this; - }, - - deleteByID: function(id) { - var data = { - op: 'refdelete', - id: id - }; - - new JX.Workflow(this._uri, data) - .setHandler(JX.bind(this, function() { - this._didUpdate(); - })) - .start(); - }, - - toggleCheckbox: function(id, checkbox) { - var data = { - op: 'done', - id: id - }; - - new JX.Workflow(this._uri, data) - .setHandler(JX.bind(this, function(r) { - checkbox.checked = !checkbox.checked; - - var comment = JX.DOM.findAbove( - checkbox, - 'div', - 'differential-inline-comment'); - JX.DOM.alterClass(comment, 'inline-is-done', !!checkbox.checked); - JX.DOM.alterClass(comment, 'inline-state-is-draft', r.draftState); - - this._didUpdate(); - })) - .start(); - }, - - _didUpdate: function() { - // After making changes to inline comments, refresh the transaction - // preview at the bottom of the page. - - // TODO: This isn't the cleanest way to find the preview form, but - // rendering no longer has direct access to it. - var forms = JX.DOM.scry(document.body, 'form', 'transaction-append'); - if (forms.length) { - JX.DOM.invoke(forms[0], 'shouldRefresh'); - } - } - - }, - - statics : { - /** - * Global refernece to the 'undo' rows currently rendered in the document. - */ - _undoRows : null, - - /** - * Global listener for the 'undo' click associated with the currently - * displayed 'undo' link. When an editor is start()ed, it becomes the active - * editor. - */ - _activeEditor : null - }, - - properties : { - operation : null, - row : null, - table : null, - onRight : null, - ID : null, - lineNumber : null, - changesetID : null, - length : null, - isNew : null, - text : null, - templates : null, - originalText : null, - renderer: null, - replyToCommentPHID: null - } - -}); diff --git a/webroot/rsrc/js/application/differential/behavior-comment-jump.js b/webroot/rsrc/js/application/differential/behavior-comment-jump.js deleted file mode 100644 index 53d43fd05e..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-comment-jump.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @provides javelin-behavior-differential-comment-jump - * @requires javelin-behavior - * javelin-stratcom - * javelin-dom - */ - -JX.behavior('differential-comment-jump', function() { - function handle_jump(offset) { - return function(e) { - var parent = JX.$('differential-review-stage'); - var clicked = e.getNode('differential-inline-comment'); - var inlines = JX.DOM.scry(parent, 'div', 'differential-inline-comment'); - var jumpto = null; - - for (var ii = 0; ii < inlines.length; ii++) { - if (inlines[ii] == clicked) { - jumpto = inlines[(ii + offset + inlines.length) % inlines.length]; - break; - } - } - JX.Stratcom.invoke('differential-toggle-file-request', null, { - element: jumpto - }); - JX.DOM.scrollTo(jumpto); - e.kill(); - }; - } - - JX.Stratcom.listen('click', 'differential-inline-prev', handle_jump(-1)); - JX.Stratcom.listen('click', 'differential-inline-next', handle_jump(+1)); -}); diff --git a/webroot/rsrc/js/application/differential/behavior-comment-preview.js b/webroot/rsrc/js/application/differential/behavior-comment-preview.js index 283e6ea12c..beb9f9a5d9 100644 --- a/webroot/rsrc/js/application/differential/behavior-comment-preview.js +++ b/webroot/rsrc/js/application/differential/behavior-comment-preview.js @@ -74,6 +74,8 @@ JX.behavior('differential-feedback-preview', function(config) { }); updateLinks(); + + JX.Stratcom.invoke('resize'); }) .setTimeout(5000) .send(); diff --git a/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js b/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js deleted file mode 100644 index 1905e3a433..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js +++ /dev/null @@ -1,254 +0,0 @@ -/** - * @provides javelin-behavior-differential-dropdown-menus - * @requires javelin-behavior - * javelin-dom - * javelin-util - * javelin-stratcom - * javelin-workflow - * phuix-dropdown-menu - * phuix-action-list-view - * phuix-action-view - * phabricator-phtize - * changeset-view-manager - */ - -JX.behavior('differential-dropdown-menus', function(config) { - var pht = JX.phtize(config.pht); - - function show_more(container) { - var view = JX.ChangesetViewManager.getForNode(container); - - var nodes = JX.DOM.scry(container, 'tr', 'context-target'); - for (var ii = 0; ii < nodes.length; ii++) { - var show = JX.DOM.scry(nodes[ii], 'a', 'show-more'); - for (var jj = 0; jj < show.length; jj++) { - var data = JX.Stratcom.getData(show[jj]); - if (data.type != 'all') { - continue; - } - view.loadContext(data.range, nodes[ii], true); - } - } - } - - JX.Stratcom.listen( - 'click', - 'differential-reveal-all', - function(e) { - var containers = JX.DOM.scry( - JX.$('differential-review-stage'), - 'div', - 'differential-changeset'); - for (var i=0; i < containers.length; i++) { - show_more(containers[i]); - } - e.kill(); - }); - - var buildmenu = function(e) { - var button = e.getNode('differential-view-options'); - var data = JX.Stratcom.getData(button); - if (data.menu) { - return; - } - - e.prevent(); - - var changeset = JX.DOM.findAbove( - button, - 'div', - 'differential-changeset'); - - var view = JX.ChangesetViewManager.getForNode(changeset); - var menu = new JX.PHUIXDropdownMenu(button); - var list = new JX.PHUIXActionListView(); - - var add_link = function(icon, name, href, local) { - if (!href) { - return; - } - - var link = new JX.PHUIXActionView() - .setIcon(icon) - .setName(name) - .setHref(href) - .setHandler(function(e) { - if (local) { - window.location.assign(href); - } else { - window.open(href); - } - menu.close(); - e.prevent(); - }); - - list.addItem(link); - return link; - }; - - var reveal_item = new JX.PHUIXActionView() - .setIcon('fa-eye'); - list.addItem(reveal_item); - - var visible_item = new JX.PHUIXActionView() - .setHandler(function(e) { - var diff = JX.DOM.scry( - JX.$(data.containerID), - 'table', - 'differential-diff'); - - JX.Stratcom.invoke('differential-toggle-file', null, {diff: diff}); - e.prevent(); - menu.close(); - }); - list.addItem(visible_item); - - add_link('fa-file-text', pht('Browse in Diffusion'), data.diffusionURI); - add_link('fa-file-o', pht('View Standalone'), data.standaloneURI); - - var up_item = new JX.PHUIXActionView() - .setHandler(function(e) { - if (view.isLoaded()) { - var renderer = view.getRenderer(); - if (renderer == '1up') { - renderer = '2up'; - } else { - renderer = '1up'; - } - view.setRenderer(renderer); - } - view.reload(); - - e.prevent(); - menu.close(); - }); - list.addItem(up_item); - - var encoding_item = new JX.PHUIXActionView() - .setIcon('fa-font') - .setName(pht('Change Text Encoding...')) - .setHandler(function(e) { - var params = { - encoding: view.getEncoding() - }; - - new JX.Workflow('/services/encoding/', params) - .setHandler(function(r) { - view.setEncoding(r.encoding); - view.reload(); - }) - .start(); - - e.prevent(); - menu.close(); - }); - list.addItem(encoding_item); - - var highlight_item = new JX.PHUIXActionView() - .setIcon('fa-sun-o') - .setName(pht('Highlight As...')) - .setHandler(function(e) { - var params = { - highlight: view.getHighlight() - }; - - new JX.Workflow('/services/highlight/', params) - .setHandler(function(r) { - view.setHighlight(r.highlight); - view.reload(); - }) - .start(); - - e.prevent(); - menu.close(); - }); - list.addItem(highlight_item); - - add_link('fa-arrow-left', pht('Show Raw File (Left)'), data.leftURI); - add_link('fa-arrow-right', pht('Show Raw File (Right)'), data.rightURI); - add_link('fa-pencil', pht('Open in Editor'), data.editor, true); - add_link('fa-wrench', pht('Configure Editor'), data.editorConfigure); - - menu.setContent(list.getNode()); - - menu.listen('open', function() { - // When the user opens the menu, check if there are any "Show More" - // links in the changeset body. If there aren't, disable the "Show - // Entire File" menu item since it won't change anything. - - var nodes = JX.DOM.scry(JX.$(data.containerID), 'a', 'show-more'); - if (nodes.length) { - reveal_item - .setDisabled(false) - .setName(pht('Show All Context')) - .setIcon('fa-file-o') - .setHandler(function(e) { - show_more(JX.$(data.containerID)); - e.prevent(); - menu.close(); - }); - } else { - reveal_item - .setDisabled(true) - .setIcon('fa-file') - .setName(pht('All Context Shown')) - .setHandler(function(e) { e.prevent(); }); - } - - encoding_item.setDisabled(!view.isLoaded()); - highlight_item.setDisabled(!view.isLoaded()); - - if (view.isLoaded()) { - if (view.getRenderer() == '2up') { - up_item - .setIcon('fa-list-alt') - .setName(pht('View Unified')); - } else { - up_item - .setIcon('fa-files-o') - .setName(pht('View Side-by-Side')); - } - } else { - up_item - .setIcon('fa-refresh') - .setName(pht('Load Changes')); - } - - visible_item - .setDisabled(true) - .setIcon('fa-expand') - .setName(pht('Can\'t Toggle Unloaded File')); - var diffs = JX.DOM.scry( - JX.$(data.containerID), - 'table', - 'differential-diff'); - - if (diffs.length > 1) { - JX.$E( - 'More than one node with sigil "differential-diff" was found in "'+ - data.containerID+'."'); - } else if (diffs.length == 1) { - var diff = diffs[0]; - visible_item.setDisabled(false); - if (JX.Stratcom.getData(diff).hidden) { - visible_item - .setName(pht('Expand File')) - .setIcon('fa-expand'); - } else { - visible_item - .setName(pht('Collapse File')) - .setIcon('fa-compress'); - } - } else { - // Do nothing when there is no diff shown in the table. For example, - // the file is binary. - } - - }); - - data.menu = menu; - menu.open(); - }; - - JX.Stratcom.listen('click', 'differential-view-options', buildmenu); -}); diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js deleted file mode 100644 index 032b8cec68..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ /dev/null @@ -1,511 +0,0 @@ -/** - * @provides javelin-behavior-differential-edit-inline-comments - * @requires javelin-behavior - * javelin-stratcom - * javelin-dom - * javelin-util - * javelin-vector - * differential-inline-comment-editor - */ - -JX.behavior('differential-edit-inline-comments', function(config) { - - var selecting = false; - var reticle = JX.$N('div', {className: 'differential-reticle'}); - var old_cells = []; - JX.DOM.hide(reticle); - - var origin = null; - var target = null; - var root = null; - var changeset = null; - - var editor = null; - - function updateReticleForComment(e) { - root = e.getNode('differential-changeset'); - if (!root) { - return; - } - - var data = e.getNodeData('differential-inline-comment'); - var change = e.getNodeData('differential-changeset'); - - var id_part = data.on_right ? change.right : change.left; - var new_part = data.isNewFile ? 'N' : 'O'; - var prefix = 'C' + id_part + new_part + 'L'; - - origin = JX.$(prefix + data.number); - target = JX.$(prefix + (parseInt(data.number, 10) + - parseInt(data.length, 10))); - - updateReticle(); - } - - function updateReticle() { - JX.DOM.getContentFrame().appendChild(reticle); - - var top = origin; - var bot = target; - if (JX.$V(top).y > JX.$V(bot).y) { - var tmp = top; - top = bot; - bot = tmp; - } - - // Find the leftmost cell that we're going to highlight: this is the next - // in the row. In 2up views, it should be directly adjacent. In - // 1up views, we may have to skip over the other line number column. - var l = top; - while (JX.DOM.isType(l, 'th')) { - l = l.nextSibling; - } - - // Find the rightmost cell that we're going to highlight: this is the - // farthest consecutive, adjacent in the row. Sometimes the left - // and right nodes are the same (left side of 2up view); sometimes we're - // going to highlight several nodes (copy + code + coverage). - var r = l; - while (r.nextSibling && JX.DOM.isType(r.nextSibling, 'td')) { - r = r.nextSibling; - } - - var pos = JX.$V(l) - .add(JX.Vector.getAggregateScrollForNode(l)); - - var dim = JX.$V(r) - .add(JX.Vector.getAggregateScrollForNode(r)) - .add(-pos.x, -pos.y) - .add(JX.Vector.getDim(r)); - - var bpos = JX.$V(bot) - .add(JX.Vector.getAggregateScrollForNode(bot)); - dim.y = (bpos.y - pos.y) + JX.Vector.getDim(bot).y; - - pos.setPos(reticle); - dim.setDim(reticle); - - JX.DOM.show(reticle); - - // Find all the cells in the same row position between the top and bottom - // cell, so we can highlight them. - var seq = 0; - var row = top.parentNode; - for (seq = 0; seq < row.childNodes.length; seq++) { - if (row.childNodes[seq] == top) { - break; - } - } - - var cells = []; - while (true) { - cells.push(row.childNodes[seq]); - if (row.childNodes[seq] == bot) { - break; - } - row = row.nextSibling; - } - - setSelectedCells(cells); - } - - function setSelectedCells(new_cells) { - updateSelectedCellsClass(old_cells, false); - updateSelectedCellsClass(new_cells, true); - old_cells = new_cells; - } - - function updateSelectedCellsClass(cells, selected) { - for (var ii = 0; ii < cells.length; ii++) { - JX.DOM.alterClass(cells[ii], 'selected', selected); - } - } - - function hideReticle() { - JX.DOM.hide(reticle); - setSelectedCells([]); - } - - JX.DifferentialInlineCommentEditor.listen('done', function() { - selecting = false; - editor = false; - hideReticle(); - set_link_state(false); - }); - - function isOnRight(node) { - return node.parentNode.firstChild != node; - } - - function isNewFile(node) { - var data = JX.Stratcom.getData(root); - return isOnRight(node) || (data.left != data.right); - } - - function getRowNumber(th_node) { - try { - return parseInt(th_node.id.match(/^C\d+[ON]L(\d+)$/)[1], 10); - } catch (x) { - return undefined; - } - } - - var set_link_state = function(active) { - JX.DOM.alterClass(JX.$(config.stage), 'inline-editor-active', active); - }; - - JX.Stratcom.listen( - 'mousedown', - ['differential-changeset', 'tag:th'], - function(e) { - if (e.isRightButton() || - getRowNumber(e.getTarget()) === undefined) { - return; - } - - if (editor) { - new JX.DifferentialInlineCommentEditor(config.uri) - .setOperation('busy') - .setRow(editor.getRow().previousSibling) - .start(); - return; - } - - if (selecting) { - return; - } - - selecting = true; - root = e.getNode('differential-changeset'); - - origin = target = e.getTarget(); - - var data = e.getNodeData('differential-changeset'); - if (isOnRight(target)) { - changeset = data.right; - } else { - changeset = data.left; - } - - updateReticle(); - - e.kill(); - }); - - JX.Stratcom.listen( - ['mouseover', 'mouseout'], - ['differential-changeset', 'tag:th'], - function(e) { - if (e.getIsTouchEvent()) { - return; - } - - if (editor) { - // Don't update the reticle if we're editing a comment, since this - // would be distracting and we want to keep the lines corresponding - // to the comment highlighted during the edit. - return; - } - - if (getRowNumber(e.getTarget()) === undefined) { - // Don't update the reticle if this "" doesn't correspond to a - // line number. For instance, this may be a dead line number, like the - // empty line numbers on the left hand side of a newly added file. - return; - } - - if (selecting) { - if (isOnRight(e.getTarget()) != isOnRight(origin)) { - // Don't update the reticle if we're selecting a line range and the - // "" under the cursor is on the wrong side of the file. You - // can only leave inline comments on the left or right side of a - // file, not across lines on both sides. - return; - } - - if (e.getNode('differential-changeset') !== root) { - // Don't update the reticle if we're selecting a line range and - // the "" under the cursor corresponds to a different file. - // You can only leave inline comments on lines in a single file, - // not across multiple files. - return; - } - } - - if (e.getType() == 'mouseout') { - if (selecting) { - // Don't hide the reticle if we're selecting, since we want to - // keep showing the line range that will be used if the mouse is - // released. - return; - } - hideReticle(); - } else { - target = e.getTarget(); - if (!selecting) { - // If we're just hovering the mouse and not selecting a line range, - // set the origin to the current row so we highlight it. - origin = target; - } - - updateReticle(); - } - }); - - JX.Stratcom.listen( - 'mouseup', - null, - function(e) { - if (editor || !selecting) { - return; - } - - var o = getRowNumber(origin); - var t = getRowNumber(target); - - var insert; - var len; - if (t < o) { - len = (o - t); - o = t; - insert = origin.parentNode; - } else { - len = (t - o); - insert = target.parentNode; - } - - var view = JX.ChangesetViewManager.getForNode(root); - - editor = new JX.DifferentialInlineCommentEditor(config.uri) - .setTemplates(view.getUndoTemplates()) - .setOperation('new') - .setChangesetID(changeset) - .setLineNumber(o) - .setLength(len) - .setIsNew(isNewFile(target) ? 1 : 0) - .setOnRight(isOnRight(target) ? 1 : 0) - .setRow(insert.nextSibling) - .setTable(insert.parentNode) - .setRenderer(view.getRenderer()) - .start(); - - set_link_state(true); - - e.kill(); - }); - - JX.Stratcom.listen( - ['mouseover', 'mouseout'], - 'differential-inline-comment', - function(e) { - if (e.getIsTouchEvent()) { - return; - } - - if (e.getType() == 'mouseout') { - hideReticle(); - } else { - updateReticleForComment(e); - } - }); - - var action_handler = function(op, e) { - e.kill(); - - if (editor) { - return; - } - - var node = e.getNode('differential-inline-comment'); - - // If we're on a touch device, we didn't highlight the affected lines - // earlier because we can't use hover events to mutate the document. - // Highlight them now. - updateReticleForComment(e); - - handle_inline_action(node, op); - }; - - var handle_inline_action = function(node, op) { - var data = JX.Stratcom.getData(node); - - // If you click an action in the preview at the bottom of the page, we - // find the corresponding node and simulate clicking that, if it's - // present on the page. This gives the editor a more consistent view - // of the document. - if (JX.Stratcom.hasSigil(node, 'differential-inline-comment-preview')) { - var nodes = JX.DOM.scry( - JX.DOM.getContentFrame(), - 'div', - 'differential-inline-comment'); - - var found = false; - var node_data; - for (var ii = 0; ii < nodes.length; ++ii) { - if (nodes[ii] == node) { - // Don't match the preview itself. - continue; - } - node_data = JX.Stratcom.getData(nodes[ii]); - if (node_data.id == data.id) { - node = nodes[ii]; - data = node_data; - found = true; - break; - } - } - - if (!found) { - switch (op) { - case 'delete': - new JX.DifferentialInlineCommentEditor(config.uri) - .deleteByID(data.id); - return; - } - } - - if (op == 'delete') { - op = 'refdelete'; - } - } - - if (op == 'done') { - var checkbox = JX.DOM.find(node, 'input', 'differential-inline-done'); - new JX.DifferentialInlineCommentEditor(config.uri) - .toggleCheckbox(data.id, checkbox); - return; - } - - var original = data.original; - var reply_phid = null; - if (op == 'reply') { - // If the user hit "reply", the original text is empty (a new reply), not - // the text of the comment they're replying to. - original = ''; - reply_phid = data.phid; - } - - var row = JX.DOM.findAbove(node, 'tr'); - var changeset_root = JX.DOM.findAbove( - node, - 'div', - 'differential-changeset'); - var view = JX.ChangesetViewManager.getForNode(changeset_root); - - editor = new JX.DifferentialInlineCommentEditor(config.uri) - .setTemplates(view.getUndoTemplates()) - .setOperation(op) - .setID(data.id) - .setChangesetID(data.changesetID) - .setLineNumber(data.number) - .setLength(data.length) - .setOnRight(data.on_right) - .setOriginalText(original) - .setRow(row) - .setTable(row.parentNode) - .setReplyToCommentPHID(reply_phid) - .setRenderer(view.getRenderer()) - .start(); - - set_link_state(true); - }; - - for (var op in {'edit': 1, 'delete': 1, 'reply': 1, 'done': 1}) { - JX.Stratcom.listen( - 'click', - ['differential-inline-comment', 'differential-inline-' + op], - JX.bind(null, action_handler, op)); - } - - JX.Stratcom.listen( - 'differential-inline-action', - null, - function(e) { - var data = e.getData(); - handle_inline_action(data.node, data.op); - }); - - // Respond to the user clicking the "Hide Inline" button on an inline - // comment. - JX.Stratcom.listen('click', 'hide-inline', function(e) { - e.kill(); - - var row = e.getNode('inline-row'); - JX.DOM.hide(row); - - var prev = row.previousSibling; - while (prev && JX.Stratcom.hasSigil(prev, 'inline-row')) { - prev = prev.previousSibling; - } - - if (!prev) { - return; - } - - var comment = e.getNodeData('differential-inline-comment'); - - var slots = []; - for (var ii = 0; ii < prev.childNodes.length; ii++) { - if (JX.DOM.isType(prev.childNodes[ii], 'th')) { - slots.push(prev.childNodes[ii]); - } - } - - // Select the right-hand side if the comment is on the right. - var slot = (comment.on_right && slots[1]) || slots[0]; - - var reveal = JX.DOM.scry(slot, 'a', 'reveal-inlines')[0]; - if (!reveal) { - reveal = JX.$N( - 'a', - { - className: 'reveal-inlines', - sigil: 'reveal-inlines' - }, - JX.$H(config.revealIcon)); - - JX.DOM.prependContent(slot, reveal); - } - - new JX.Workflow(config.uri, {op: 'hide', ids: comment.id}) - .setHandler(JX.bag) - .start(); - }); - - JX.Stratcom.listen('click', 'reveal-inlines', function(e) { - e.kill(); - - var row = e.getNode('tag:tr'); - var next = row.nextSibling; - - var ids = []; - var ii; - - // Show any hidden inline comment rows directly below this one. - while (next && JX.Stratcom.hasSigil(next, 'inline-row')) { - JX.DOM.show(next); - - var comments = JX.DOM.scry(next, 'div', 'differential-inline-comment'); - for (ii = 0; ii < comments.length; ii++) { - var id = JX.Stratcom.getData(comments[ii]).id; - if (id) { - ids.push(id); - } - } - - next = next.nextSibling; - } - - // Remove any "reveal" icons on the row. - var reveals = JX.DOM.scry(row, 'a', 'reveal-inlines'); - for (ii = 0; ii < reveals.length; ii++) { - JX.DOM.remove(reveals[ii]); - } - - new JX.Workflow(config.uri, {op: 'show', ids: ids.join(',')}) - .setHandler(JX.bag) - .start(); - }); - - -}); diff --git a/webroot/rsrc/js/application/differential/behavior-keyboard-nav.js b/webroot/rsrc/js/application/differential/behavior-keyboard-nav.js deleted file mode 100644 index f48df4d3dc..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-keyboard-nav.js +++ /dev/null @@ -1,264 +0,0 @@ -/** - * @provides javelin-behavior-differential-keyboard-navigation - * @requires javelin-behavior - * javelin-dom - * javelin-stratcom - * phabricator-keyboard-shortcut - */ - -JX.behavior('differential-keyboard-navigation', function(config) { - - var cursor = -1; - var changesets; - - var selection_begin = null; - var selection_end = null; - - var refreshFocus = function() {}; - - function init() { - if (changesets) { - return; - } - changesets = JX.DOM.scry(document.body, 'div', 'differential-changeset'); - } - - function getBlocks(cursor) { - // TODO: This might not be terribly fast; we can't currently memoize it - // because it can change as ajax requests come in (e.g., content loads). - - var rows = JX.DOM.scry(changesets[cursor], 'tr'); - var blocks = [[changesets[cursor], changesets[cursor]]]; - var start = null; - var type; - var ii; - - // Don't show code blocks inside a collapsed file. - var diff = JX.DOM.scry(changesets[cursor], 'table', 'differential-diff'); - if (diff.length == 1 && JX.Stratcom.getData(diff[0]).hidden) { - return blocks; - } - - function push() { - if (start) { - blocks.push([start, rows[ii - 1]]); - } - start = null; - } - - for (ii = 0; ii < rows.length; ii++) { - type = getRowType(rows[ii]); - if (type == 'comment') { - // If we see these types of rows, make a block for each one. - push(); - } - if (!type) { - push(); - } else if (type && !start) { - start = rows[ii]; - } - } - push(); - - return blocks; - } - - function getRowType(row) { - // NOTE: Being somewhat over-general here to allow other types of objects - // to be easily focused in the future (inline comments, 'show more..'). - - if (row.className.indexOf('inline') !== -1) { - return 'comment'; - } - - if (row.className.indexOf('differential-changeset') !== -1) { - return 'file'; - } - - var cells = JX.DOM.scry(row, 'td'); - - for (var ii = 0; ii < cells.length; ii++) { - // NOTE: The semantic use of classnames here is for performance; don't - // emulate this elsewhere since it's super terrible. - if (cells[ii].className.indexOf('old') !== -1 || - cells[ii].className.indexOf('new') !== -1) { - return 'change'; - } - } - - return null; - } - - function jump(manager, delta, jump_to_type) { - init(); - - if (cursor < 0) { - if (delta < 0) { - // If the user goes "back" without a selection, just reject the action. - return; - } else { - cursor = 0; - } - } - - while (true) { - var blocks = getBlocks(cursor); - var focus; - if (delta < 0) { - focus = blocks.length; - } else { - focus = -1; - } - - for (var ii = 0; ii < blocks.length; ii++) { - if (blocks[ii][0] == selection_begin) { - focus = ii; - break; - } - } - - while (true) { - focus += delta; - - if (blocks[focus]) { - var row_type = getRowType(blocks[focus][0]); - if (jump_to_type && row_type != jump_to_type) { - continue; - } - - selection_begin = blocks[focus][0]; - selection_end = blocks[focus][1]; - - manager.scrollTo(selection_begin); - - refreshFocus = function() { - manager.focusOn(selection_begin, selection_end); - }; - - refreshFocus(); - - return; - } else { - var adjusted = (cursor + delta); - if (adjusted < 0 || adjusted >= changesets.length) { - // Stop cursor movement when the user reaches either end. - return; - } - cursor = adjusted; - - // Break the inner loop and go to the next file. - break; - } - } - } - - } - - // When inline comments are updated, wipe out our cache of blocks since - // comments may have been added or deleted. - JX.Stratcom.listen( - null, - 'differential-inline-comment-update', - function() { - changesets = null; - }); - // Same thing when a file is hidden or shown; don't want to highlight - // invisible code. - JX.Stratcom.listen( - 'differential-toggle-file-toggled', - null, - function() { - changesets = null; - init(); - refreshFocus(); - }); - - new JX.KeyboardShortcut('j', 'Jump to next change.') - .setHandler(function(manager) { - jump(manager, 1); - }) - .register(); - - new JX.KeyboardShortcut('k', 'Jump to previous change.') - .setHandler(function(manager) { - jump(manager, -1); - }) - .register(); - - new JX.KeyboardShortcut('J', 'Jump to next file.') - .setHandler(function(manager) { - jump(manager, 1, 'file'); - }) - .register(); - - new JX.KeyboardShortcut('K', 'Jump to previous file.') - .setHandler(function(manager) { - jump(manager, -1, 'file'); - }) - .register(); - - new JX.KeyboardShortcut('n', 'Jump to next inline comment.') - .setHandler(function(manager) { - jump(manager, 1, 'comment'); - }) - .register(); - - new JX.KeyboardShortcut('p', 'Jump to previous inline comment.') - .setHandler(function(manager) { - jump(manager, -1, 'comment'); - }) - .register(); - - - new JX.KeyboardShortcut('t', 'Jump to the table of contents.') - .setHandler(function(manager) { - var toc = JX.$('toc'); - manager.scrollTo(toc); - }) - .register(); - - new JX.KeyboardShortcut( - 'h', - 'Collapse or expand the file display (after jump).') - .setHandler(function() { - if (!changesets || !changesets[cursor]) { - return; - } - JX.Stratcom.invoke('differential-toggle-file', null, { - diff: JX.DOM.scry(changesets[cursor], 'table', 'differential-diff') - }); - }) - .register(); - - - function inline_op(node, op) { - // nothing selected - if (!node) { - return; - } - if (!JX.DOM.scry(node, 'a', 'differential-inline-' + op)) { - // No link for this operation, e.g. editing a comment you can't edit. - return; - } - - var data = { - node: JX.DOM.find(node, 'div', 'differential-inline-comment'), - op: op - }; - - JX.Stratcom.invoke('differential-inline-action', null, data); - } - - new JX.KeyboardShortcut('r', 'Reply to selected inline comment.') - .setHandler(function() { - inline_op(selection_begin, 'reply'); - }) - .register(); - - new JX.KeyboardShortcut('e', 'Edit selected inline comment.') - .setHandler(function() { - inline_op(selection_begin, 'edit'); - }) - .register(); - -}); diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js index 6d0aabb213..0a4a7912e4 100644 --- a/webroot/rsrc/js/application/differential/behavior-populate.js +++ b/webroot/rsrc/js/application/differential/behavior-populate.js @@ -4,52 +4,81 @@ * javelin-dom * javelin-stratcom * phabricator-tooltip - * changeset-view-manager + * phabricator-diff-changeset-list + * phabricator-diff-changeset + * @javelin */ -JX.behavior('differential-populate', function(config) { +JX.behavior('differential-populate', function(config, statics) { + + // When we perform a Quicksand navigation, deactivate the changeset lists on + // the current page and activate the changeset lists on the new page. + var onredraw = function(page_id) { + // If the current page is already active, we don't need to do anything. + if (statics.pageID === page_id) { + return; + } + + var ii; + + // Put the old lists to sleep. + var old_lists = get_lists(statics.pageID); + for (ii = 0; ii < old_lists.length; ii++) { + old_lists[ii].sleep(); + } + statics.pageID = null; + + // Awaken the new lists, if they exist. + if (statics.pages.hasOwnProperty(page_id)) { + var new_lists = get_lists(page_id); + for (ii = 0; ii < new_lists.length; ii++) { + new_lists[ii].wake(); + } + + statics.pageID = page_id; + } + }; + + // Get changeset lists on the current page. + var get_lists = function(page_id) { + if (page_id === null) { + return []; + } + + return statics.pages[page_id] || []; + }; + + if (!statics.installed) { + statics.installed = true; + statics.pages = {}; + statics.pageID = null; + + JX.Stratcom.listen('quicksand-redraw', null, function(e) { + onredraw(e.getData().newResponseID); + }); + } + + var changeset_list = new JX.DiffChangesetList() + .setTranslations(JX.phtize(config.pht)) + .setInlineURI(config.inlineURI) + .setInlineListURI(config.inlineListURI); + + // Install and activate the current page. + var page_id = JX.Quicksand.getCurrentPageID(); + statics.pages[page_id] = [changeset_list]; + onredraw(page_id); + + for (var ii = 0; ii < config.changesetViewIDs.length; ii++) { var id = config.changesetViewIDs[ii]; - var view = JX.ChangesetViewManager.getForNode(JX.$(id)); - if (view.shouldAutoload()) { - view.setStabilize(true).load(); + var node = JX.$(id); + var changeset = changeset_list.newChangesetForNode(node); + if (changeset.shouldAutoload()) { + changeset.setStabilize(true).load(); } } - JX.Stratcom.listen( - 'click', - 'differential-load', - function(e) { - var meta = e.getNodeData('differential-load'); - var changeset = JX.$(meta.id); - var view = JX.ChangesetViewManager.getForNode(changeset); - - view.load(); - var routable = view.getRoutable(); - if (routable) { - routable.setPriority(2000); - } - - if (meta.kill) { - e.kill(); - } - }); - - JX.Stratcom.listen( - 'click', - 'show-more', - function(e) { - e.kill(); - - var changeset = e.getNode('differential-changeset'); - var view = JX.ChangesetViewManager.getForNode(changeset); - var data = e.getNodeData('show-more'); - var target = e.getNode('context-target'); - - view.loadContext(data.range, target); - }); - var highlighted = null; var highlight_class = null; diff --git a/webroot/rsrc/js/application/differential/behavior-toggle-files.js b/webroot/rsrc/js/application/differential/behavior-toggle-files.js deleted file mode 100644 index 296b6f91b2..0000000000 --- a/webroot/rsrc/js/application/differential/behavior-toggle-files.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * @provides javelin-behavior-differential-toggle-files - * @requires javelin-behavior - * javelin-dom - * javelin-stratcom - * phabricator-phtize - */ - -JX.behavior('differential-toggle-files', function(config) { - var pht = JX.phtize(config.pht); - - JX.Stratcom.listen( - 'differential-toggle-file', - null, - function(e) { - if (e.getData().diff.length != 1) { - return; - } - - var diff = e.getData().diff[0], - data = JX.Stratcom.getData(diff); - if (data.hidden) { - data.hidden = false; - JX.DOM.show(diff); - JX.DOM.remove(data.undo); - data.undo = null; - } else { - data.hidden = true; - data.undo = render_collapse_undo(); - JX.DOM.hide(diff); - JX.DOM.listen( - data.undo, - 'click', - 'differential-collapse-undo', - function(e) { - e.kill(); - data.hidden = false; - JX.DOM.show(diff); - JX.DOM.remove(data.undo); - data.undo = null; - }); - JX.DOM.appendContent(diff.parentNode, data.undo); - } - JX.Stratcom.invoke('differential-toggle-file-toggled'); - }); - - JX.Stratcom.listen( - 'differential-toggle-file-request', - null, - function(e) { - var elt = e.getData().element; - while (elt !== document.body) { - if (JX.Stratcom.hasSigil(elt, 'differential-changeset')) { - var diffs = JX.DOM.scry(elt, 'table', 'differential-diff'); - var invoked = false; - for (var i = 0; i < diffs.length; ++i) { - if (JX.Stratcom.getData(diffs[i]).hidden) { - JX.Stratcom.invoke('differential-toggle-file', null, { - diff: [ diffs[i] ] - }); - invoked = true; - } - } - if (!invoked) { - e.prevent(); - } - return; - } - elt = elt.parentNode; - } - e.prevent(); - }); - - JX.Stratcom.listen( - 'click', - 'tag:a', - function(e) { - var link = e.getNode('tag:a'); - var id = link.getAttribute('href'); - if (!id || !id.match(/^#.+/)) { - return; - } - var raw = e.getRawEvent(); - if (raw.altKey || raw.ctrlKey || raw.metaKey || raw.shiftKey) { - return; - } - // The target may have either a matching name or a matching id. - var target; - try { - target = JX.$(id.substr(1)); - } catch(err) { - var named = document.getElementsByName(id.substr(1)); - for (var i = 0; i < named.length; ++i) { - if (named[i].tagName.toLowerCase() == 'a') { - if (target) { - return; - } - target = named[i]; - } - } - if (!target) { - return; - } - } - var event = JX.Stratcom.invoke('differential-toggle-file-request', null, { - element: target - }); - if (!event.getPrevented()) { - // This event is processed after the hash has changed, so it doesn't - // automatically jump there like we want. - JX.DOM.scrollTo(target); - } - }); - - var render_collapse_undo = function() { - var link = JX.$N( - 'a', - {href: '#', sigil: 'differential-collapse-undo'}, - pht('undo')); - - return JX.$N( - 'div', - {className: 'differential-collapse-undo', - sigil: 'differential-collapse-undo-div'}, - [pht('collapsed'), ' ', link]); - }; - -}); diff --git a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js index 317a132240..309f972324 100644 --- a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js +++ b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js @@ -53,7 +53,7 @@ JX.behavior('diffusion-commit-graph', function(config) { return (col * cell) + (cell / 2); }; - var h = 32; + var h = 34; var w = cell * config.count; var canvas = JX.$N('canvas', {width: w, height: h}); diff --git a/webroot/rsrc/js/application/projects/WorkboardColumn.js b/webroot/rsrc/js/application/projects/WorkboardColumn.js index 81fba84077..9973648593 100644 --- a/webroot/rsrc/js/application/projects/WorkboardColumn.js +++ b/webroot/rsrc/js/application/projects/WorkboardColumn.js @@ -285,9 +285,9 @@ JX.install('WorkboardColumn', { JX.DOM.alterClass(panel, 'project-panel-over-limit', over_limit); var color_map = { - 'phui-tag-shade-disabled': (total_points === 0), - 'phui-tag-shade-blue': (total_points > 0 && !over_limit), - 'phui-tag-shade-red': (over_limit) + 'phui-tag-disabled': (total_points === 0), + 'phui-tag-blue': (total_points > 0 && !over_limit), + 'phui-tag-red': (over_limit) }; for (var c in color_map) { diff --git a/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js b/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js index b4dcd2975b..28754ee3a2 100644 --- a/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js +++ b/webroot/rsrc/js/application/transactions/behavior-show-older-transactions.js @@ -17,6 +17,13 @@ JX.behavior('phabricator-show-older-transactions', function(config) { if (!hash) { return false; } + + // If the hash isn't purely numeric, ignore it. Comments always have + // numeric hashes. See PHI43 and T12970. + if (!hash.match(/^\d+$/)) { + return false; + } + var id = 'anchor-'+hash; try { JX.$(id); @@ -60,6 +67,7 @@ JX.behavior('phabricator-show-older-transactions', function(config) { var show_older = function(swap, r) { JX.DOM.replace(swap, JX.$H(r.timeline).getFragment()); + JX.Stratcom.invoke('resize'); }; var load_hidden_hash_callback = function(swap, r) { diff --git a/webroot/rsrc/js/application/uiexample/JavelinViewExample.js b/webroot/rsrc/js/application/uiexample/JavelinViewExample.js deleted file mode 100644 index a8e17923b2..0000000000 --- a/webroot/rsrc/js/application/uiexample/JavelinViewExample.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @provides phabricator-uiexample-javelin-view - * @requires javelin-install - * javelin-dom - * javelin-view - */ - -JX.install('JavelinViewExample', { - extend: 'View', - members: { - render: function(rendered_children) { - return JX.$N( - 'div', - { className: 'client-view' }, - rendered_children - ); - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorButtonExample.js b/webroot/rsrc/js/application/uiexample/ReactorButtonExample.js deleted file mode 100644 index 41982f192f..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorButtonExample.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-button - * @requires javelin-install - * javelin-dom - * javelin-util - * javelin-dynval - * javelin-reactor-dom - */ - -JX.install('ReactorButtonExample', { - extend: 'View', - members: { - render: function() { - var button = JX.$N('button', {}, 'Fun'); - var clicks = JX.RDOM.clickPulses(button); - - var time = JX.RDOM.time(); - - // function snapshot(pulses, dynval) { - // return new DynVal( - // pulses.transform(JX.bind(dynval, dynval.getValueNow)), - // dynval.getValueNow() - // ); - // } - // - // Below could be... - // time.snapshot(clicks) - // clicks.snapshot(time) - - var snapshot_time = new JX.DynVal( - clicks.transform(JX.bind(time, time.getValueNow)), - time.getValueNow() - ); - - return [button, JX.RDOM.$DT(snapshot_time)]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorCheckboxExample.js b/webroot/rsrc/js/application/uiexample/ReactorCheckboxExample.js deleted file mode 100644 index 983f6d96ab..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorCheckboxExample.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-checkbox - * @requires javelin-install - * javelin-dom - * javelin-reactor-dom - */ - -JX.install('ReactorCheckboxExample', { - extend: 'View', - members: { - render: function() { - var checkbox = JX.$N('input', {type: 'checkbox'}); - - return [checkbox, JX.RDOM.$DT(JX.RDOM.checkbox(checkbox))]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorFocusExample.js b/webroot/rsrc/js/application/uiexample/ReactorFocusExample.js deleted file mode 100644 index f4c53b2865..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorFocusExample.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-focus - * @requires javelin-install - * javelin-dom - * javelin-reactor-dom - */ - -JX.install('ReactorFocusExample', { - extend: 'View', - members: { - render: function() { - var input = JX.$N('input'); - return [input, JX.RDOM.$DT(JX.RDOM.hasFocus(input))]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorInputExample.js b/webroot/rsrc/js/application/uiexample/ReactorInputExample.js deleted file mode 100644 index 70f4c9fb70..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorInputExample.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-input - * @requires javelin-install - * javelin-reactor-dom - * javelin-view-html - * javelin-view-interpreter - * javelin-view-renderer - */ - -JX.install('ReactorInputExample', { - extend: 'View', - members: { - render: function() { - var html = JX.HTMLView.registerToInterpreter(new JX.ViewInterpreter()); - - var raw_input = JX.ViewRenderer.render( - html.input({ value: this.getAttr('init') }) - ); - var input = JX.RDOM.input(raw_input); - - return JX.ViewRenderer.render( - html.div( - raw_input, - html.br(), - html.span(JX.RDOM.$DT(input)), - html.br(), - html.span(JX.RDOM.$DT(input.calm(500))) - ) - ); - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorMouseoverExample.js b/webroot/rsrc/js/application/uiexample/ReactorMouseoverExample.js deleted file mode 100644 index 5f9656e2f3..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorMouseoverExample.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-mouseover - * @requires javelin-install - * javelin-dom - * javelin-reactor-dom - */ - -JX.install('ReactorMouseoverExample', { - extend: 'View', - members: { - render: function() { - var target = JX.$N('span', 'mouseover me '); - return [target, JX.RDOM.$DT(JX.RDOM.isMouseOver(target))]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorRadioExample.js b/webroot/rsrc/js/application/uiexample/ReactorRadioExample.js deleted file mode 100644 index 3e99e0e674..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorRadioExample.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-radio - * @requires javelin-install - * javelin-dom - * javelin-reactor-dom - */ - -JX.install('ReactorRadioExample', { - extend: 'View', - members: { - render: function() { - var radio_one = JX.$N('input', {type: 'radio', name: 'n', value: 'one'}); - var radio_two = JX.$N('input', {type: 'radio', name: 'n', value: 'two'}); - - radio_one.checked = true; - - return [ - radio_one, - radio_two, - JX.RDOM.$DT(JX.RDOM.radio([radio_one, radio_two])) - ]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorSelectExample.js b/webroot/rsrc/js/application/uiexample/ReactorSelectExample.js deleted file mode 100644 index 628fbed122..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorSelectExample.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-select - * @requires javelin-install - * javelin-dom - * javelin-reactor-dom - */ - -JX.install('ReactorSelectExample', { - extend: 'View', - members: { - render: function() { - var select = JX.$N('select', {}, [ - JX.$N('option', { value: 'goat' }, 'Goat'), - JX.$N('option', { value: 'bat' }, 'Bat'), - JX.$N('option', { value: 'duck' }, 'Duck') - ]); - - return [select, JX.RDOM.$DT(JX.RDOM.select(select))]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorSendClassExample.js b/webroot/rsrc/js/application/uiexample/ReactorSendClassExample.js deleted file mode 100644 index 62e44b7d0c..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorSendClassExample.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-sendclass - * @requires javelin-install - * javelin-dom - * javelin-reactor-dom - */ - -JX.install('ReactorSendClassExample', { - extend: 'View', - members: { - render: function() { - var input = JX.$N('input', { type: 'checkbox' }); - var span = JX.$N('a', 'Hey'); - JX.RDOM.sendClass(JX.RDOM.checkbox(input), span, 'disabled'); - return [input, span]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/ReactorSendPropertiesExample.js b/webroot/rsrc/js/application/uiexample/ReactorSendPropertiesExample.js deleted file mode 100644 index f297e413dc..0000000000 --- a/webroot/rsrc/js/application/uiexample/ReactorSendPropertiesExample.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @provides phabricator-uiexample-reactor-sendproperties - * @requires javelin-install - * javelin-dom - * javelin-reactor-dom - */ - -JX.install('ReactorSendPropertiesExample', { - extend: 'View', - members: { - render: function() { - var color = JX.$N('input', {value: '#fff000'}); - var title = JX.$N('input', {value: 'seen on hover'}); - var target = JX.$N('span', 'Change my color and title'); - - JX.RDOM.sendProps(target, { - style: { - backgroundColor: JX.RDOM.input(color) - }, - title: JX.RDOM.input(title) - }); - - return [color, title, target]; - } - } -}); diff --git a/webroot/rsrc/js/application/uiexample/busy-example.js b/webroot/rsrc/js/application/uiexample/busy-example.js deleted file mode 100644 index 64ee390112..0000000000 --- a/webroot/rsrc/js/application/uiexample/busy-example.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @provides javelin-behavior-phabricator-busy-example - * @requires phabricator-busy - * javelin-behavior - */ - -JX.behavior('phabricator-busy-example', function() { - JX.Busy.start(); -}); diff --git a/webroot/rsrc/js/core/KeyboardShortcutManager.js b/webroot/rsrc/js/core/KeyboardShortcutManager.js index c9a1fbe64e..281c12a8f3 100644 --- a/webroot/rsrc/js/core/KeyboardShortcutManager.js +++ b/webroot/rsrc/js/core/KeyboardShortcutManager.js @@ -54,7 +54,6 @@ JX.install('KeyboardShortcutManager', { members : { _shortcuts : null, - _focusReticle : null, /** * Instead of calling this directly, you should call @@ -83,48 +82,6 @@ JX.install('KeyboardShortcutManager', { JX.DOM.scrollToPosition(0, node_position.y + scroll_distance.y - 60); }, - /** - * Move the keyboard shortcut focus to an element. - * - * @param Node Node to focus, or pass null to clear the focus. - * @param Node To focus multiple nodes (like rows in a table), specify the - * top-left node as the first parameter and the bottom-right - * node as the focus extension. - * @return void - */ - focusOn : function(node, extended_node) { - this._clearReticle(); - - if (!node) { - return; - } - - var r = JX.$N('div', {className : 'keyboard-focus-focus-reticle'}); - - extended_node = extended_node || node; - - // Outset the reticle some pixels away from the element, so there's some - // space between the focused element and the outline. - var p = JX.Vector.getPos(node); - var s = JX.Vector.getAggregateScrollForNode(node); - - p.add(s).add(-4, -4).setPos(r); - // Compute the size we need to extend to the full extent of the focused - // nodes. - JX.Vector.getPos(extended_node) - .add(-p.x, -p.y) - .add(JX.Vector.getDim(extended_node)) - .add(8, 8) - .setDim(r); - JX.DOM.getContentFrame().appendChild(r); - - this._focusReticle = r; - }, - - _clearReticle : function() { - this._focusReticle && JX.DOM.remove(this._focusReticle); - this._focusReticle = null; - }, _onkeypress : function(e) { if (!(this._getKey(e) in JX.KeyboardShortcutManager._downkeys)) { this._onkeyhit(e); diff --git a/webroot/rsrc/js/core/Notification.js b/webroot/rsrc/js/core/Notification.js index 50585419c5..9e59ec1501 100644 --- a/webroot/rsrc/js/core/Notification.js +++ b/webroot/rsrc/js/core/Notification.js @@ -27,6 +27,7 @@ JX.install('Notification', { _hideTimer : null, _duration : 12000, _desktopReady : false, + _webReady : false, _key : null, _title : null, _body : null, @@ -35,6 +36,12 @@ JX.install('Notification', { show : function() { var self = JX.Notification; + + // This person doesn't like any real-time notification + if (!this._desktopReady && !this._webReady) { + return; + } + if (!this._visible) { this._visible = true; @@ -92,6 +99,11 @@ JX.install('Notification', { return this; }, + setWebReady : function(ready) { + this._webReady = ready; + return this; + }, + setTitle : function(title) { this._title = title; return this; diff --git a/webroot/rsrc/js/core/ToolTip.js b/webroot/rsrc/js/core/ToolTip.js index 6fef44ab55..635c9466ad 100644 --- a/webroot/rsrc/js/core/ToolTip.js +++ b/webroot/rsrc/js/core/ToolTip.js @@ -21,6 +21,10 @@ JX.install('Tooltip', { return; } + if (content === null) { + return; + } + if (__DEV__) { switch (align) { case 'N': @@ -50,7 +54,11 @@ JX.install('Tooltip', { { className: 'jx-tooltip-container' }, node_inner); - node.style.maxWidth = scale + 'px'; + if (scale == 'auto') { + node.style.maxWidth = ''; + } else { + node.style.maxWidth = scale + 'px'; + } JX.Tooltip.hide(); self._node = node; diff --git a/webroot/rsrc/js/core/behavior-copy.js b/webroot/rsrc/js/core/behavior-copy.js new file mode 100644 index 0000000000..4457f6e022 --- /dev/null +++ b/webroot/rsrc/js/core/behavior-copy.js @@ -0,0 +1,43 @@ +/** + * @provides javelin-behavior-phabricator-clipboard-copy + * @requires javelin-behavior + * javelin-dom + * javelin-stratcom + * @javelin + */ + +JX.behavior('phabricator-clipboard-copy', function() { + + if (!document.queryCommandSupported) { + return; + } + + if (!document.queryCommandSupported('copy')) { + return; + } + + JX.DOM.alterClass(document.body, 'supports-clipboard', true); + + JX.Stratcom.listen('click', 'clipboard-copy', function(e) { + e.kill(); + + var data = e.getNodeData('clipboard-copy'); + var attr = { + value: data.text || '', + className: 'clipboard-buffer' + }; + + var node = JX.$N('textarea', attr); + document.body.appendChild(node); + + try { + node.select(); + document.execCommand('copy'); + } catch (ignored) { + // Ignore any errors we hit. + } + + JX.DOM.remove(node); + }); + +}); diff --git a/webroot/rsrc/js/core/behavior-fancy-datepicker.js b/webroot/rsrc/js/core/behavior-fancy-datepicker.js index 15558dbfe1..afd5ff25ad 100644 --- a/webroot/rsrc/js/core/behavior-fancy-datepicker.js +++ b/webroot/rsrc/js/core/behavior-fancy-datepicker.js @@ -287,26 +287,38 @@ JX.behavior('fancy-datepicker', function(config, statics) { }; function getValidDate() { - var written_date = new Date(value_y, value_m-1, value_d); + var year_int = parseInt(value_y, 10); + if (isNaN(year_int) || year_int < 0) { + return new Date(); + } + + // If the user enters "11" for the year, interpret it as "2011" (which + // is almost certainly what they mean) not "1911" (which is the default + // behavior of Javascript). + if (year_int < 70) { + year_int += 2000; + } + + var month_int = parseInt(value_m, 10); + if (isNaN(month_int) || month_int < 1 || month_int > 12) { + return new Date(); + } + + // In Javascript, January is "0", not "1", so adjust the value down. + month_int = month_int - 1; + + var day_int = parseInt(value_d, 10); + if (isNaN(day_int) || day_int < 1 || day_int > 31) { + return new Date(); + } + + var written_date = new Date(year_int, month_int, day_int); if (isNaN(written_date.getTime())) { return new Date(); - } else { - //year 01 should be 2001, not 1901 - if (written_date.getYear() < 70) { - value_y += 2000; - written_date = new Date(value_y, value_m-1, value_d); - } - - // adjust for a date like February 31 - var adjust = 1; - while (written_date.getMonth() !== value_m-1) { - written_date = new Date(value_y, value_m-1, value_d-adjust); - adjust++; - } - - return written_date; } + + return written_date; } diff --git a/webroot/rsrc/js/core/behavior-lightbox-attachments.js b/webroot/rsrc/js/core/behavior-lightbox-attachments.js index 929222f2f9..b9ea0dd8db 100644 --- a/webroot/rsrc/js/core/behavior-lightbox-attachments.js +++ b/webroot/rsrc/js/core/behavior-lightbox-attachments.js @@ -169,7 +169,7 @@ JX.behavior('lightbox-attachments', function (config) { ); var commentIcon = new JX.PHUIXIconView() - .setIcon('fa-comments') + .setIcon('fa-comments phui-icon-circle-icon') .getNode(); var commentButton = JX.$N('a', @@ -181,7 +181,7 @@ JX.behavior('lightbox-attachments', function (config) { commentIcon ); var closeIcon = new JX.PHUIXIconView() - .setIcon('fa-times') + .setIcon('fa-times phui-icon-circle-icon') .getNode(); var closeButton = JX.$N('a', diff --git a/webroot/rsrc/js/core/behavior-object-selector.js b/webroot/rsrc/js/core/behavior-object-selector.js index 686f0f8820..722cfdd562 100644 --- a/webroot/rsrc/js/core/behavior-object-selector.js +++ b/webroot/rsrc/js/core/behavior-object-selector.js @@ -137,7 +137,9 @@ JX.behavior('phabricator-object-selector', function(config) { var select_object_button = JX.$N( 'a', - {href: '#', sigil: 'object-attacher', className: 'button small grey'}, + {href: '#', + sigil: 'object-attacher', + className: 'button small button-grey'}, attach ? 'Select' : 'Remove'); var cells = [ diff --git a/webroot/rsrc/js/core/behavior-phabricator-nav.js b/webroot/rsrc/js/core/behavior-phabricator-nav.js index ca7fc0d14a..e37680abf0 100644 --- a/webroot/rsrc/js/core/behavior-phabricator-nav.js +++ b/webroot/rsrc/js/core/behavior-phabricator-nav.js @@ -113,6 +113,10 @@ JX.behavior('phabricator-nav', function(config) { new JX.Request('/settings/adjust/', JX.bag) .setData({ key : 'nav-collapsed', value : (collapsed ? 1 : 0) }) .send(); + + // Invoke a resize event so page elements can redraw if they need to. One + // example is the selection reticles in Differential. + JX.Stratcom.invoke('resize'); }); @@ -126,8 +130,19 @@ JX.behavior('phabricator-nav', function(config) { return; } + // When the buoyant header is visible, move the menu down below it. This + // is a bit of a hack. + var banner_height = 0; + try { + var banner = JX.$('diff-banner'); + banner_height = JX.Vector.getDim(banner).y; + } catch (error) { + // Ignore if there's no banner on the page. + } + local.style.top = Math.max( 0, + banner_height, JX.$V(content).y - Math.max(0, JX.Vector.getScroll().y)) + 'px'; } diff --git a/webroot/rsrc/js/core/behavior-search-typeahead.js b/webroot/rsrc/js/core/behavior-search-typeahead.js index fc752a6079..ddcc2e6aaa 100644 --- a/webroot/rsrc/js/core/behavior-search-typeahead.js +++ b/webroot/rsrc/js/core/behavior-search-typeahead.js @@ -45,6 +45,10 @@ JX.behavior('phabricator-search-typeahead', function(config) { JX.$N('span', {className: 'result-type'}, object.type) ]); + if (object.closed) { + JX.DOM.alterClass(render, 'result-closed', true); + } + object.display = render; return object; diff --git a/webroot/rsrc/js/phuix/PHUIXActionView.js b/webroot/rsrc/js/phuix/PHUIXActionView.js index d5d849e733..2db58dbdc4 100644 --- a/webroot/rsrc/js/phuix/PHUIXActionView.js +++ b/webroot/rsrc/js/phuix/PHUIXActionView.js @@ -16,6 +16,7 @@ JX.install('PHUIXActionView', { _label: false, _handler: null, _selected: false, + _divider: false, _iconNode: null, _nameNode: null, @@ -41,6 +42,15 @@ JX.install('PHUIXActionView', { return this; }, + setDivider: function(divider) { + this._divider = divider; + JX.DOM.alterClass( + this.getNode(), + 'phabricator-action-view-type-divider', + divider); + return this; + }, + setSelected: function(selected) { this._selected = selected; JX.DOM.alterClass( diff --git a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js index ac073a0e38..df4a031377 100644 --- a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js +++ b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js @@ -199,7 +199,7 @@ JX.install('PHUIXAutocomplete', { // to press Alt to type characters like "@" on a German keyboard layout. // The cost of misfiring autocompleters is very small since we do not // eat the keystroke. See T10252. - if (r.metaKey || r.ctrlKey) { + if (r.metaKey || (r.ctrlKey && !r.altKey)) { return; } diff --git a/webroot/rsrc/js/phuix/PHUIXButtonView.js b/webroot/rsrc/js/phuix/PHUIXButtonView.js new file mode 100644 index 0000000000..b7ca44a677 --- /dev/null +++ b/webroot/rsrc/js/phuix/PHUIXButtonView.js @@ -0,0 +1,119 @@ +/** + * @provides phuix-button-view + * @requires javelin-install + * javelin-dom + */ +JX.install('PHUIXButtonView', { + + statics: { + BUTTONTYPE_DEFAULT: 'buttontype.default', + BUTTONTYPE_SIMPLE: 'buttontype.simple' + }, + + members: { + _node: null, + _textNode: null, + + _iconView: null, + _color: null, + _selected: null, + _buttonType: null, + + setIcon: function(icon) { + this.getIconView().setIcon(icon); + return this; + }, + + getIconView: function() { + if (!this._iconView) { + this._iconView = new JX.PHUIXIconView(); + this._redraw(); + } + return this._iconView; + }, + + setColor: function(color) { + var node = this.getNode(); + + if (this._color) { + JX.DOM.alterClass(node, 'button-' + this._color, false); + } + this._color = color; + JX.DOM.alterClass(node, 'button-' + this._color, true); + + return this; + }, + + setSelected: function(selected) { + var node = this.getNode(); + this._selected = selected; + JX.DOM.alterClass(node, 'selected', this._selected); + return this; + }, + + setButtonType: function(button_type) { + var self = JX.PHUIXButtonView; + + this._buttonType = button_type; + var node = this.getNode(); + + var is_simple = (this._buttonType == self.BUTTONTYPE_SIMPLE); + JX.DOM.alterClass(node, 'phui-button-simple', is_simple); + + return this; + }, + + setText: function(text) { + JX.DOM.setContent(this._getTextNode(), text); + this._redraw(); + return this; + }, + + getNode: function() { + if (!this._node) { + var attrs = { + className: 'button' + }; + + this._node = JX.$N('button', attrs); + + this._redraw(); + } + + return this._node; + }, + + _getTextNode: function() { + if (!this._textNode) { + var attrs = { + className: 'phui-button-text' + }; + + this._textNode = JX.$N('div', attrs); + } + + return this._textNode; + }, + + _redraw: function() { + var node = this.getNode(); + + var icon = this._iconView; + var text = this._textNode; + + var content = []; + if (icon) { + content.push(icon.getNode()); + } + + if (text) { + content.push(text); + } + + JX.DOM.alterClass(node, 'has-icon', !!icon); + JX.DOM.alterClass(node, 'has-text', !!text); + JX.DOM.setContent(node, content); + } + } + +}); diff --git a/webroot/rsrc/js/phuix/PHUIXExample.js b/webroot/rsrc/js/phuix/PHUIXExample.js new file mode 100644 index 0000000000..87f36b04c3 --- /dev/null +++ b/webroot/rsrc/js/phuix/PHUIXExample.js @@ -0,0 +1,65 @@ +/** + * @provides javelin-behavior-phuix-example + * @requires javelin-install + * javelin-dom + * phuix-button-view + */ + +JX.behavior('phuix-example', function(config) { + var node; + var spec; + var defaults; + + switch (config.type) { + case 'button': + var button = new JX.PHUIXButtonView(); + defaults = { + text: null, + icon: null, + type: null, + color: null + }; + + spec = JX.copy(defaults, config.spec); + + if (spec.text !== null) { + button.setText(spec.text); + } + + if (spec.icon !== null) { + button.setIcon(spec.icon); + } + + if (spec.type !== null) { + button.setButtonType(spec.type); + } + + if (spec.color !== null) { + button.setColor(spec.color); + } + + node = button.getNode(); + break; + case 'icon': + var icon = new JX.PHUIXIconView(); + defaults = { + icon: null, + color: null + }; + + spec = JX.copy(defaults, config.spec); + + if (spec.icon !== null) { + icon.setIcon(spec.icon); + } + + if (spec.color !== null) { + icon.setColor(spec.color); + } + + node = icon.getNode(); + break; + } + + JX.DOM.setContent(JX.$(config.id), node); +});