1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-15 10:00:55 +01:00

(stable) Promote 2017 Week 25

This commit is contained in:
epriestley 2017-06-23 16:47:54 -07:00
commit 0934ef6abc
110 changed files with 2581 additions and 1315 deletions

21
externals/octicons/LICENSE vendored Normal file
View file

@ -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.

194
externals/octicons/README.md vendored Normal file
View file

@ -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: '<path d="M8.865 1.52c-.18-.31-.51-.5-.87-.5s-.69.19-.87.5L.275 13.5c-.18.31-.18.69 0 1 .19.31.52.5.87.5h13.7c.36 0 .69-.19.86-.5.17-.31.18-.69.01-1L8.865 1.52zM8.995 13h-2v-2h2v2zm0-3h-2V6h2v4z"/>',
// 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
// <path d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"></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()
// <svg version="1.1" width="12" height="16" viewBox="0 0 12 16" class="octicon octicon-x" aria-hidden="true"><path d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"/></svg>
```
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 `<svg>` tag.
```js
octicons.x.toSVG({ "class": "close" })
// <svg version="1.1" width="12" height="16" viewBox="0 0 12 16" class="octicon octicon-x close" aria-hidden="true"><path d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"/></svg>
```
##### aria-label
Add accessibility `aria-label` to the icon.
```js
octicons.x.toSVG({ "aria-label": "Close the window" })
// <svg version="1.1" width="12" height="16" viewBox="0 0 12 16" class="octicon octicon-x" aria-label="Close the window" role="img"><path d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"/></svg>
```
##### width & height
Size the SVG icon larger using `width` & `height` independently or together.
```js
octicons.x.toSVG({ "width": 45 })
// <svg version="1.1" width="45" height="60" viewBox="0 0 12 16" class="octicon octicon-x" aria-hidden="true"><path d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"/></svg>
```
#### `octicons.alert.toSVGUse()`
Returns a string of the svg tag with the `<use>` tag, for use with the spritesheet located in the /build/ directory.
```js
octicons.x.toSVGUse()
// <svg version="1.1" width="12" height="16" viewBox="0 0 12 16" class="octicon octicon-x" aria-hidden="true"><use xlink:href="#x" /></svg>
```
### 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 youre 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 youre happy with your icon set, choose File > Export…
3. Choose all the artboards youd like to export and then press “Export”
4. Export to `/lib/svg/`
Youll next need to build your Octicons.
## Building Octicons
All the files you need will be in the `/build/` directory already, but if youve 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 <newversion>` 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_repository.repository
ADD profileImagePHID VARBINARY(64);

View file

@ -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;

View file

@ -453,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',
@ -472,8 +469,6 @@ 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',
'DifferentialLineAdjustmentMap' => 'applications/differential/parser/DifferentialLineAdjustmentMap.php',
'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php',
@ -547,7 +542,7 @@ 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',
'DifferentialRevisionInlinesController' => 'applications/differential/controller/DifferentialRevisionInlinesController.php',
'DifferentialRevisionListController' => 'applications/differential/controller/DifferentialRevisionListController.php',
'DifferentialRevisionListView' => 'applications/differential/view/DifferentialRevisionListView.php',
'DifferentialRevisionMailReceiver' => 'applications/differential/mail/DifferentialRevisionMailReceiver.php',
@ -612,6 +607,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',
@ -727,6 +723,7 @@ 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',
@ -850,6 +847,7 @@ 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',
@ -889,6 +887,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',
@ -1503,6 +1502,7 @@ phutil_register_library_map(array(
'ManiphestSearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestSearchConduitAPIMethod.php',
'ManiphestStatusConfigOptionType' => 'applications/maniphest/config/ManiphestStatusConfigOptionType.php',
'ManiphestStatusEmailCommand' => 'applications/maniphest/command/ManiphestStatusEmailCommand.php',
'ManiphestStatusSearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestStatusSearchConduitAPIMethod.php',
'ManiphestSubpriorityController' => 'applications/maniphest/controller/ManiphestSubpriorityController.php',
'ManiphestSubtypesConfigOptionsType' => 'applications/maniphest/config/ManiphestSubtypesConfigOptionsType.php',
'ManiphestTask' => 'applications/maniphest/storage/ManiphestTask.php',
@ -1737,6 +1737,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',
@ -2155,6 +2156,7 @@ phutil_register_library_map(array(
'PhabricatorBoolEditField' => 'applications/transactions/editfield/PhabricatorBoolEditField.php',
'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.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',
'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php',
@ -2164,6 +2166,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',
@ -2313,6 +2316,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',
@ -2923,6 +2927,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',
@ -3741,6 +3746,7 @@ phutil_register_library_map(array(
'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',
@ -4195,6 +4201,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',
@ -5398,13 +5405,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',
@ -5423,8 +5427,6 @@ phutil_register_library_map(array(
'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery',
'DifferentialJIRAIssuesCommitMessageField' => 'DifferentialCommitMessageCustomField',
'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField',
'DifferentialLandingActionMenuEventListener' => 'PhabricatorEventListener',
'DifferentialLandingStrategy' => 'Phobject',
'DifferentialLegacyHunk' => 'DifferentialHunk',
'DifferentialLineAdjustmentMap' => 'Phobject',
'DifferentialLintField' => 'DifferentialHarbormasterField',
@ -5515,7 +5517,7 @@ phutil_register_library_map(array(
'DifferentialRevisionHeraldField' => 'HeraldField',
'DifferentialRevisionHeraldFieldGroup' => 'HeraldFieldGroup',
'DifferentialRevisionIDCommitMessageField' => 'DifferentialCommitMessageField',
'DifferentialRevisionLandController' => 'DifferentialController',
'DifferentialRevisionInlinesController' => 'DifferentialController',
'DifferentialRevisionListController' => 'DifferentialController',
'DifferentialRevisionListView' => 'AphrontView',
'DifferentialRevisionMailReceiver' => 'PhabricatorObjectMailReceiver',
@ -5580,6 +5582,7 @@ phutil_register_library_map(array(
'DiffusionBlameConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionBlameQuery' => 'DiffusionQuery',
'DiffusionBlockHeraldAction' => 'HeraldAction',
'DiffusionBranchListView' => 'DiffusionView',
'DiffusionBranchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionBranchTableController' => 'DiffusionController',
'DiffusionBranchTableView' => 'DiffusionView',
@ -5698,6 +5701,7 @@ phutil_register_library_map(array(
'DiffusionRepositoryClusterEngineLogInterface',
),
'DiffusionGitUploadPackSSHWorkflow' => 'DiffusionGitSSHWorkflow',
'DiffusionGraphController' => 'DiffusionController',
'DiffusionHistoryController' => 'DiffusionController',
'DiffusionHistoryListView' => 'DiffusionHistoryView',
'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
@ -5820,6 +5824,7 @@ phutil_register_library_map(array(
'DiffusionRepositoryManagementPanel' => 'Phobject',
'DiffusionRepositoryPath' => 'Phobject',
'DiffusionRepositoryPoliciesManagementPanel' => 'DiffusionRepositoryManagementPanel',
'DiffusionRepositoryProfilePictureController' => 'DiffusionController',
'DiffusionRepositoryRef' => 'Phobject',
'DiffusionRepositoryRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'DiffusionRepositorySearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
@ -5859,6 +5864,7 @@ phutil_register_library_map(array(
'DiffusionSymbolQuery' => 'PhabricatorOffsetPagedQuery',
'DiffusionTagListController' => 'DiffusionController',
'DiffusionTagListView' => 'DiffusionView',
'DiffusionTagTableView' => 'DiffusionView',
'DiffusionTaggedRepositoriesFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionURIEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
@ -6598,6 +6604,7 @@ phutil_register_library_map(array(
'ManiphestSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'ManiphestStatusConfigOptionType' => 'PhabricatorConfigJSONOptionType',
'ManiphestStatusEmailCommand' => 'ManiphestEmailCommand',
'ManiphestStatusSearchConduitAPIMethod' => 'ManiphestConduitAPIMethod',
'ManiphestSubpriorityController' => 'ManiphestController',
'ManiphestSubtypesConfigOptionsType' => 'PhabricatorConfigJSONOptionType',
'ManiphestTask' => array(
@ -6870,6 +6877,7 @@ phutil_register_library_map(array(
'PHUIDiffInlineCommentTableScaffold' => 'AphrontView',
'PHUIDiffInlineCommentUndoView' => 'PHUIDiffInlineCommentView',
'PHUIDiffInlineCommentView' => 'AphrontView',
'PHUIDiffInlineThreader' => 'Phobject',
'PHUIDiffOneUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold',
'PHUIDiffRevealIconView' => 'AphrontView',
'PHUIDiffTableOfContentsItemView' => 'AphrontView',
@ -7343,6 +7351,7 @@ phutil_register_library_map(array(
'PhabricatorBoolEditField' => 'PhabricatorEditField',
'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation',
'PhabricatorBuiltinDraftEngine' => 'PhabricatorDraftEngine',
'PhabricatorBuiltinFileCachePurger' => 'PhabricatorCachePurger',
'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList',
'PhabricatorBulkContentSource' => 'PhabricatorContentSource',
'PhabricatorCacheDAO' => 'PhabricatorLiskDAO',
@ -7352,6 +7361,7 @@ phutil_register_library_map(array(
'PhabricatorCacheManagementPurgeWorkflow' => 'PhabricatorCacheManagementWorkflow',
'PhabricatorCacheManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorCacheMarkupGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorCachePurger' => 'Phobject',
'PhabricatorCacheSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorCacheSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorCacheSpec' => 'Phobject',
@ -7537,6 +7547,7 @@ phutil_register_library_map(array(
'PhabricatorCelerityApplication' => 'PhabricatorApplication',
'PhabricatorCelerityTestCase' => 'PhabricatorTestCase',
'PhabricatorChangeParserTestCase' => 'PhabricatorWorkingCopyTestCase',
'PhabricatorChangesetCachePurger' => 'PhabricatorCachePurger',
'PhabricatorChangesetResponse' => 'AphrontProxyResponse',
'PhabricatorChatLogApplication' => 'PhabricatorApplication',
'PhabricatorChatLogChannel' => array(
@ -8239,6 +8250,7 @@ phutil_register_library_map(array(
'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow',
'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow',
'PhabricatorGarbageCollectorManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorGeneralCachePurger' => 'PhabricatorCachePurger',
'PhabricatorGestureUIExample' => 'PhabricatorUIExample',
'PhabricatorGitGraphStream' => 'PhabricatorRepositoryGraphStream',
'PhabricatorGitHubAuthProvider' => 'PhabricatorOAuth2AuthProvider',
@ -9190,6 +9202,7 @@ phutil_register_library_map(array(
'PhabricatorRegistrationProfile' => 'Phobject',
'PhabricatorReleephApplication' => 'PhabricatorApplication',
'PhabricatorReleephApplicationConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorRemarkupCachePurger' => 'PhabricatorCachePurger',
'PhabricatorRemarkupControl' => 'AphrontFormTextAreaControl',
'PhabricatorRemarkupCowsayBlockInterpreter' => 'PhutilRemarkupBlockInterpreter',
'PhabricatorRemarkupCustomBlockRule' => 'PhutilRemarkupBlockRule',
@ -9742,6 +9755,7 @@ phutil_register_library_map(array(
'PhabricatorUserBadgesCacheType' => 'PhabricatorUserCacheType',
'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField',
'PhabricatorUserCache' => 'PhabricatorUserDAO',
'PhabricatorUserCachePurger' => 'PhabricatorCachePurger',
'PhabricatorUserCacheType' => 'Phobject',
'PhabricatorUserCardView' => 'AphrontTagView',
'PhabricatorUserConfigOptions' => 'PhabricatorApplicationConfigOptions',

View file

@ -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() {

View file

@ -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())

View file

@ -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;
}

View file

@ -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)

View file

@ -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;
}

View file

@ -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(

View file

@ -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);

View file

@ -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...'));
}

View file

@ -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 )-------------------------- */

View file

@ -483,9 +483,11 @@ 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) {
if ($object instanceof PhabricatorLiskDAO) {
$action_list->setObject($object);
}
}
$curtain = id(new PHUICurtainView())
->setViewer($viewer)

View file

@ -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',
'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 ($purge['changeset']) {
$console->writeOut(pht('Purging changeset cache...'));
$this->purgeChangesetCache();
$console->writeOut("%s\n", pht('Done.'));
}
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.'));
if (!$purgers) {
throw new PhutilArgumentUsageException(
pht(
'When using "--caches", you must select at least one valid '.
'cache to purge.'));
}
}
private function purgeRemarkupCache() {
$conn_w = id(new PhabricatorMarkupCache())->establishConnection('w');
$viewer = $this->getViewer();
queryfx(
$conn_w,
'TRUNCATE TABLE %T',
id(new PhabricatorMarkupCache())->getTableName());
foreach ($purgers as $key => $purger) {
$purger->setViewer($viewer);
echo tsprintf(
"%s\n",
pht(
'Purging "%s" cache...',
$key));
$purger->purgeCache();
}
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;
}
}

View file

@ -0,0 +1,22 @@
<?php
final class PhabricatorBuiltinFileCachePurger
extends PhabricatorCachePurger {
const PURGERKEY = 'builtin-file';
public function purgeCache() {
$viewer = $this->getViewer();
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withIsBuiltin(true)
->execute();
$engine = new PhabricatorDestructionEngine();
foreach ($files as $file) {
$engine->destroyObject($file);
}
}
}

View file

@ -0,0 +1,30 @@
<?php
abstract class PhabricatorCachePurger
extends Phobject {
private $viewer;
abstract public function purgeCache();
final public function setViewer(PhabricatorUser $viewer) {
$this->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();
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorChangesetCachePurger
extends PhabricatorCachePurger {
const PURGERKEY = 'changeset';
public function purgeCache() {
$table = new DifferentialChangeset();
$conn = $table->establishConnection('w');
queryfx(
$conn,
'TRUNCATE TABLE %T',
DifferentialChangeset::TABLE_CACHE);
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorGeneralCachePurger
extends PhabricatorCachePurger {
const PURGERKEY = 'general';
public function purgeCache() {
$table = new PhabricatorMarkupCache();
$conn = $table->establishConnection('w');
queryfx(
$conn,
'TRUNCATE TABLE %T',
'cache_general');
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorRemarkupCachePurger
extends PhabricatorCachePurger {
const PURGERKEY = 'remarkup';
public function purgeCache() {
$table = new PhabricatorMarkupCache();
$conn = $table->establishConnection('w');
queryfx(
$conn,
'TRUNCATE TABLE %T',
$table->getTableName());
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorUserCachePurger
extends PhabricatorCachePurger {
const PURGERKEY = 'user';
public function purgeCache() {
$table = new PhabricatorUserCache();
$conn = $table->establishConnection('w');
queryfx(
$conn,
'TRUNCATE TABLE %T',
$table->getTableName());
}
}

View file

@ -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();
}

View file

@ -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);

View file

@ -41,12 +41,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 +63,14 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication {
=> 'DifferentialRevisionEditController',
$this->getEditRoutePattern('attach/(?P<diffID>[^/]+)/to/')
=> 'DifferentialRevisionEditController',
'land/(?:(?P<id>[1-9]\d*))/(?P<strategy>[^/]+)/'
=> 'DifferentialRevisionLandController',
'closedetails/(?P<phid>[^/]+)/'
=> 'DifferentialRevisionCloseDetailsController',
'update/(?P<revisionID>[1-9]\d*)/'
=> 'DifferentialDiffCreateController',
'operation/(?P<id>[1-9]\d*)/'
=> 'DifferentialRevisionOperationController',
'inlines/(?P<id>[1-9]\d*)/'
=> 'DifferentialRevisionInlinesController',
),
'comment/' => array(
'preview/(?P<id>[1-9]\d*)/' => 'DifferentialCommentPreviewController',

View file

@ -0,0 +1,190 @@
<?php
final class DifferentialRevisionInlinesController
extends DifferentialController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->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);
}
}

View file

@ -1,157 +0,0 @@
<?php
final class DifferentialRevisionLandController extends DifferentialController {
private $pushStrategy;
public function handleRequest(AphrontRequest $request) {
$viewer = $this->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:<br><pre>%s</pre>',
$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<br><br>%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;
}
}

View file

@ -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);
}

View file

@ -1,186 +0,0 @@
<?php
final class DifferentialGitHubLandingStrategy
extends DifferentialLandingStrategy {
private $account;
private $provider;
public function processLandRequest(
AphrontRequest $request,
DifferentialRevision $revision,
PhabricatorRepository $repository) {
$viewer = $request->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'));
}
}
}

View file

@ -1,127 +0,0 @@
<?php
final class DifferentialHostedGitLandingStrategy
extends DifferentialLandingStrategy {
public function processLandRequest(
AphrontRequest $request,
DifferentialRevision $revision,
PhabricatorRepository $repository) {
$viewer = $request->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'));
}
}

View file

@ -1,106 +0,0 @@
<?php
final class DifferentialHostedMercurialLandingStrategy
extends DifferentialLandingStrategy {
public function processLandRequest(
AphrontRequest $request,
DifferentialRevision $revision,
PhabricatorRepository $repository) {
$viewer = $request->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'));
}
}

View file

@ -1,81 +0,0 @@
<?php
/**
* This class adds a "Land this" button to revision view.
*/
final class DifferentialLandingActionMenuEventListener
extends PhabricatorEventListener {
public function register() {
$this->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);
}
}
}

View file

@ -1,87 +0,0 @@
<?php
abstract class DifferentialLandingStrategy extends Phobject {
abstract public function processLandRequest(
AphrontRequest $request,
DifferentialRevision $revision,
PhabricatorRepository $repository);
/**
* @return PhabricatorActionView or null.
*/
abstract public function createMenuItem(
PhabricatorUser $viewer,
DifferentialRevision $revision,
PhabricatorRepository $repository);
/**
* @return PhabricatorActionView which can be attached to the revision view.
*/
protected function createActionView($revision, $name) {
$strategy = get_class($this);
$revision_id = $revision->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);
}
}
}

View file

@ -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;

View file

@ -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 )-------------------------- */

View file

@ -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;
@ -208,6 +218,7 @@ final class DifferentialChangesetListView extends AphrontView {
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'),
@ -257,15 +268,15 @@ final class DifferentialChangesetListView extends AphrontView {
'You must select a comment to mark done.' =>
pht('You must select a comment to mark done.'),
'Hide or show inline comment.' =>
pht('Hide or show inline comment.'),
'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 hidden comments.' =>
pht('Jump to next inline comment, including hidden comments.'),
'Jump to previous inline comment, including hidden comments.' =>
pht('Jump to previous inline comment, including hidden comments.'),
'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.'),
@ -279,6 +290,14 @@ final class DifferentialChangesetListView extends AphrontView {
'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'),
),
));

View file

@ -56,6 +56,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
'repository/(?P<dblob>.*)' => 'DiffusionRepositoryController',
'change/(?P<dblob>.*)' => 'DiffusionChangeController',
'history/(?P<dblob>.*)' => 'DiffusionHistoryController',
'graph/(?P<dblob>.*)' => 'DiffusionGraphController',
'browse/(?P<dblob>.*)' => 'DiffusionBrowseController',
'lastmodified/(?P<dblob>.*)' => 'DiffusionLastModifiedController',
'diff/' => 'DiffusionDiffController',
@ -140,6 +141,8 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
$this->getEditRoutePattern('edit/') =>
'DiffusionCommitEditController',
),
'picture/(?P<id>[0-9]\d*)/'
=> 'DiffusionRepositoryProfilePictureController',
),
);
}

View file

@ -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,7 @@ final class DiffusionBranchTableController extends DiffusionController {
$content = id(new PHUIObjectBoxView())
->setHeaderText($repository->getName())
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table)
->setTable($list)
->setPager($pager);
}
@ -84,10 +84,7 @@ final class DiffusionBranchTableController extends DiffusionController {
$repository->getDisplayName(),
))
->setCrumbs($crumbs)
->appendChild(
array(
$view,
));
->appendChild($view);
}
}

View file

@ -204,6 +204,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;

View file

@ -0,0 +1,108 @@
<?php
final class DiffusionGraphController extends DiffusionController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$response = $this->loadDiffusionContext();
if ($response) {
return $response;
}
$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)
->setPager($pager);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter($graph_view);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildHeader(DiffusionRequest $drequest) {
$viewer = $this->getViewer();
$tag = $this->renderCommitHashTag($drequest);
$history_uri = $drequest->generateURI(
array(
'action' => 'history',
));
$history_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('History'))
->setHref($history_uri)
->setIcon('fa-history');
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setPolicyObject($drequest->getRepository())
->addTag($tag)
->setHeader($this->renderPathLinks($drequest, $mode = 'history'))
->setHeaderIcon('fa-code-fork')
->addActionLink($history_button);
return $header;
}
}

View file

@ -77,24 +77,28 @@ final class DiffusionHistoryController extends DiffusionController {
$viewer = $this->getViewer();
$tag = $this->renderCommitHashTag($drequest);
$browse_uri = $drequest->generateURI(
array(
'action' => 'browse',
));
$browse_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Browse'))
->setHref($browse_uri)
->setIcon('fa-code');
$show_graph = !strlen($drequest->getPath());
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setPolicyObject($drequest->getRepository())
->addTag($tag)
->setHeader($this->renderPathLinks($drequest, $mode = 'history'))
->setHeaderIcon('fa-clock-o')
->addActionLink($browse_button);
->setHeaderIcon('fa-clock-o');
if ($show_graph) {
$graph_uri = $drequest->generateURI(
array(
'action' => 'graph',
));
$graph_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Graph'))
->setHref($graph_uri)
->setIcon('fa-code-fork');
$header->addActionLink($graph_button);
}
return $header;

View file

@ -274,7 +274,9 @@ final class DiffusionRepositoryController extends DiffusionController {
->setHeader($repository->getName())
->setUser($viewer)
->setPolicyObject($repository)
->setHeaderIcon('fa-code');
->setProfileHeader(true)
->setImage($repository->getProfileImageURI())
->setImageEditURL('/diffusion/picture/'.$repository->getID().'/');
if (!$repository->isTracked()) {
$header->setStatus('fa-ban', 'dark', pht('Inactive'));
@ -449,11 +451,11 @@ final class DiffusionRepositoryController extends DiffusionController {
$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(
$button = id(new PHUIButtonView())
->setText(pht('Show All'))
->setTag('a')
->setIcon('fa-code-fork')
->setHref($drequest->generateURI(
array(
'action' => 'branches',
)));
@ -490,7 +492,7 @@ final class DiffusionRepositoryController extends DiffusionController {
->needCommitData(true)
->execute();
$view = id(new DiffusionTagListView())
$view = id(new DiffusionTagTableView())
->setUser($viewer)
->setDiffusionRequest($drequest)
->setTags($tags)
@ -509,11 +511,11 @@ final class DiffusionRepositoryController extends DiffusionController {
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(
$button = id(new PHUIButtonView())
->setText(pht('Show All Tags'))
->setTag('a')
->setIcon('fa-tag')
->setHref($drequest->generateURI(
array(
'action' => 'tags',
)));
@ -565,23 +567,30 @@ 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'))
$history = id(new PHUIButtonView())
->setText(pht('History'))
->setHref($drequest->generateURI(
array(
'action' => 'history',
)))
->setTag('a')
->setIcon($icon);
->setIcon('fa-history');
$graph = id(new PHUIButtonView())
->setText(pht('Graph'))
->setHref($drequest->generateURI(
array(
'action' => 'graph',
)))
->setTag('a')
->setIcon('fa-code-fork');
$panel = id(new PHUIObjectBoxView())
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
$header = id(new PHUIHeaderView())
->setHeader(pht('Recent Commits'))
->addActionLink($button);
->addActionLink($graph)
->addActionLink($history);
$panel->setHeader($header);
$panel->setTable($history_table);
@ -670,14 +679,11 @@ final class DiffusionRepositoryController extends DiffusionController {
$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);
$button = id(new PHUIButtonView())
->setText(pht('Browse'))
->setTag('a')
->setIcon('fa-code')
->setHref($browse_uri);
$header->addActionLink($button);
$browse_panel->setHeader($header);

View file

@ -0,0 +1,246 @@
<?php
final class DiffusionRepositoryProfilePictureController
extends DiffusionController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->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/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);
}
}

View file

@ -64,17 +64,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 +88,9 @@ final class DiffusionTagListController extends DiffusionController {
));
$crumbs->setBorder(true);
$box = id(new PHUIObjectBoxView())
->setHeaderText($repository->getDisplayName())
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($view)
->setPager($pager);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$box,
));
->setFooter($content);
return $this->newPage()
->setTitle(
@ -103,7 +99,8 @@ final class DiffusionTagListController extends DiffusionController {
$repository->getDisplayName(),
))
->setCrumbs($crumbs)
->appendChild($view);
->appendChild($view)
->addClass('diffusion-history-view');
}
}

View file

@ -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) {

View file

@ -0,0 +1,138 @@
<?php
final class DiffusionBranchListView extends DiffusionView {
private $branches;
private $commits = array();
public function setBranches(array $branches) {
assert_instances_of($branches, 'DiffusionRepositoryRef');
$this->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-history-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));
}
}
$fields = $branch->getRawFields();
$closed = idx($fields, 'closed');
if ($closed) {
$status = pht('Closed');
} else {
$status = pht('Open');
}
$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));
$list->addItem($item);
}
return $list;
}
}

View file

@ -119,19 +119,7 @@ final class DiffusionHistoryListView extends DiffusionHistoryView {
if ($show_builds) {
$buildable = idx($buildables, $commit->getPHID());
if ($buildable !== null) {
$status = $buildable->getBuildableStatus();
$icon = HarbormasterBuildable::getBuildableStatusIcon($status);
$color = HarbormasterBuildable::getBuildableStatusColor($status);
$name = HarbormasterBuildable::getBuildableStatusName($status);
$build_view = 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');
$build_view = $this->renderBuildable($buildable, 'button');
}
}

View file

@ -29,36 +29,28 @@ final class DiffusionTagListView extends DiffusionView {
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$viewer = $this->getViewer();
require_celerity_resource('diffusion-history-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',
'action' => 'history',
'commit' => $tag->getName(),
)),
),
$tag->getName());
));
$commit_link = phutil_tag(
'a',
array(
'href' => $drequest->generateURI(
$commit_href = $drequest->generateURI(
array(
'action' => 'commit',
'commit' => $tag->getCommitIdentifier(),
)),
),
$repository->formatCommitName(
$tag->getCommitIdentifier()));
));
$author = null;
if ($commit && $commit->getAuthorPHID()) {
@ -69,6 +61,15 @@ final class DiffusionTagListView extends DiffusionView {
$author = self::renderName($tag->getAuthor());
}
$committed = phabricator_datetime($commit->getEpoch(), $viewer);
$author_name = phutil_tag(
'strong',
array(
'class' => 'diffusion-history-author-name',
),
$author);
$authored = pht('%s on %s.', $author_name, $committed);
$description = null;
if ($tag->getType() == 'git/tag') {
// In Git, a tag may be a "real" tag, or just a reference to a commit.
@ -83,58 +84,71 @@ 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());
$rows[] = array(
$history,
$tag_link,
$commit_link,
$build,
$author,
$description,
$viewer->formatShortDateTime($tag->getEpoch()),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
if ($repository->supportsBranchComparison()) {
$compare_uri = $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' => 'compare',
'head' => $tag->getName(),
));
return $table->render();
$button_bar->addButton(
id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-balance-scale')
->setToolTip(pht('Compare'))
->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE)
->setWorkflow(true)
->setHref($compare_uri));
}
$commit_name = $repository->formatCommitName(
$tag->getCommitIdentifier(), $local = true);
$browse_href = $drequest->generateURI(
array(
'action' => 'browse',
'commit' => $tag->getName(),
));
$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)
->addAttribute($authored)
->setSideColumn(array(
$build_view,
$button_bar,
));
$list->addItem($item);
}
return $list;
}
}

View file

@ -0,0 +1,140 @@
<?php
final class DiffusionTagTableView extends DiffusionView {
private $tags;
private $commits = array();
private $handles = array();
public function setTags($tags) {
$this->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();
}
}

View file

@ -116,10 +116,10 @@ abstract class DiffusionView extends AphrontView {
if ($button) {
return id(new PHUIButtonView())
->setText(pht('Browse'))
->setTag('a')
->setIcon('fa-code')
->setHref($href)
->setTag('a')
->setToolTip(pht('Browse'))
->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE);
}
@ -190,7 +190,8 @@ abstract class DiffusionView extends AphrontView {
}
final protected function renderBuildable(
HarbormasterBuildable $buildable) {
HarbormasterBuildable $buildable,
$type = null) {
$status = $buildable->getBuildableStatus();
Javelin::initBehavior('phabricator-tooltips');
@ -198,6 +199,18 @@ abstract class DiffusionView extends AphrontView {
$color = HarbormasterBuildable::getBuildableStatusColor($status);
$name = HarbormasterBuildable::getBuildableStatusName($status);
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');
}
return id(new PHUIIconView())
->setIcon($icon.' '.$color)
->addSigil('has-tooltip')

View file

@ -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;
}

View file

@ -393,6 +393,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
$tmp = new TempFile();
Filesystem::writeFile($tmp, $data);
$file->setMimeType(Filesystem::getMimeType($tmp));
unset($tmp);
}
try {

View file

@ -186,12 +186,18 @@ abstract class PhabricatorFileUploadSource
$actual_length = strlen($data);
$rope->removeBytesFromHead($actual_length);
$chunk_data = PhabricatorFile::newFromFileData(
$data,
array(
$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() {

View file

@ -194,11 +194,14 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase {
$dst,
$is_after);
$keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap();
$keyword = head($keyword_map[$pri]);
$xactions = array();
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
->setNewValue($pri);
->setNewValue($keyword);
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE)
@ -217,11 +220,14 @@ 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(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
->setNewValue($pri);
->setNewValue($keyword);
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE)

View file

@ -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;

View file

@ -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();
@ -72,7 +62,7 @@ final class ManiphestPriorityEmailCommand
$xactions[] = $object->getApplicationTransactionTemplate()
->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
->setNewValue($priority);
->setNewValue($keyword);
return $xactions;
}

View file

@ -99,7 +99,9 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod {
throw id(new ConduitException('ERR-INVALID-PARAMETER'))
->setErrorDescription(pht('Priority set to invalid value.'));
}
$changes[ManiphestTaskPriorityTransaction::TRANSACTIONTYPE] = $priority;
$keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap();
$keyword = head(idx($keyword_map, $priority));
$changes[ManiphestTaskPriorityTransaction::TRANSACTIONTYPE] = $keyword;
}
$owner_phid = $request->getValue('ownerPHID');

View file

@ -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.');
}
}

View file

@ -0,0 +1,52 @@
<?php
final class ManiphestStatusSearchConduitAPIMethod
extends ManiphestConduitAPIMethod {
public function getAPIMethodName() {
return 'maniphest.status.search';
}
public function getMethodSummary() {
return pht('Read information about task statuses.');
}
public function getMethodDescription() {
return pht(
'Returns information about the possible statuses for Maniphest '.
'tasks.');
}
protected function defineParamTypes() {
return array();
}
protected function defineReturnType() {
return 'map<string, wild>';
}
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);
}
}

View file

@ -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<string, string> 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,6 +159,18 @@ 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);
@ -116,6 +182,18 @@ final class ManiphestTaskPriority extends ManiphestConstants {
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(
@ -147,9 +225,24 @@ final class ManiphestTaskPriority extends ManiphestConstants {
'name' => 'string',
'short' => 'optional string',
'color' => 'optional string',
'keywords' => 'optional list<string>',
'keywords' => 'list<string>',
'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'));
}
}
}
}

View file

@ -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'));

View file

@ -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) {

View file

@ -40,11 +40,14 @@ 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(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
->setNewValue($pri);
->setNewValue($keyword);
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE)

View file

@ -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 {
@ -215,8 +217,9 @@ EODOCS
->setConduitTypeDescription(pht('New task priority constant.'))
->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
->setIsCopyable(true)
->setValue($object->getPriority())
->setValue($object->getPriorityKeyword())
->setOptions($priority_map)
->setOptionAliases($alias_map)
->setCommentActionLabel(pht('Change Priority')),
);
@ -289,29 +292,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(
'<Unknown: %s>',
$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(

View file

@ -39,12 +39,15 @@ final class ManiphestTaskPriorityHeraldAction
return;
}
$keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap();
$keyword = head(idx($keyword_map, $priority));
$xaction = $adapter->newTransaction()
->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
->setNewValue($priority);
->setNewValue($keyword);
$adapter->queueTransaction($xaction);
$this->logEffect(self::DO_PRIORITY, $priority);
$this->logEffect(self::DO_PRIORITY, $keyword);
}
public function getHeraldActionStandardType() {

View file

@ -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() {

View file

@ -79,7 +79,7 @@ final class ManiphestTask extends ManiphestDAO
),
self::CONFIG_COLUMN_SCHEMA => array(
'ownerPHID' => 'phid?',
'status' => 'text12',
'status' => 'text64',
'priority' => 'uint32',
'title' => 'sort',
'originalTitle' => 'text',
@ -245,6 +245,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();

View file

@ -12,6 +12,19 @@ final class ManiphestTaskPriorityTransaction
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);
}
@ -116,4 +129,50 @@ final class ManiphestTaskPriorityTransaction
}
}
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;
}
}

View file

@ -372,6 +372,8 @@ final class PhabricatorMetaMTAMail
}
$editor->save();
$this->saveTransaction();
// Queue a task to send this mail.
$mailer_task = PhabricatorWorker::scheduleTask(
'PhabricatorMetaMTAWorker',
@ -380,8 +382,6 @@ final class PhabricatorMetaMTAMail
'priority' => PhabricatorWorker::PRIORITY_ALERTS,
));
$this->saveTransaction();
return $result;
}

View file

@ -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();

View file

@ -153,11 +153,14 @@ final class PhabricatorProjectMoveController
break;
}
$keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap();
$keyword = head(idx($keyword_map, $pri));
$xactions = array();
if ($pri !== null) {
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
->setNewValue($pri);
->setNewValue($keyword);
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(
ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE)

View file

@ -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(
'committed' => 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;
}

View file

@ -15,7 +15,8 @@ final class PhabricatorRepositorySearchEngine
return id(new PhabricatorRepositoryQuery())
->needProjectPHIDs(true)
->needCommitCounts(true)
->needMostRecentCommits(true);
->needMostRecentCommits(true)
->needProfileImage(true);
}
protected function buildCustomSearchFields() {
@ -165,7 +166,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) {

View file

@ -57,6 +57,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
protected $viewPolicy;
protected $editPolicy;
protected $pushPolicy;
protected $profileImagePHID;
protected $versionControlSystem;
protected $details = array();
@ -69,6 +70,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
private $mostRecentCommit = self::ATTACHABLE;
private $projectPHIDs = self::ATTACHABLE;
private $uris = self::ATTACHABLE;
private $profileImageFile = self::ATTACHABLE;
public static function initializeNewRepository(PhabricatorUser $actor) {
@ -110,6 +112,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
'credentialPHID' => 'phid?',
'almanacServicePHID' => 'phid?',
'localPath' => 'text128?',
'profileImagePHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'callsign' => array(
@ -478,6 +481,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 +699,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
$action = idx($params, 'action');
switch ($action) {
case 'history':
case 'graph':
case 'browse':
case 'change':
case 'lastmodified':
@ -759,6 +777,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
switch ($action) {
case 'change':
case 'history':
case 'graph':
case 'browse':
case 'lastmodified':
case 'tags':

View file

@ -238,7 +238,14 @@ abstract class PhabricatorProfileMenuEngine extends Phobject {
case 'view':
$navigation->selectFilter($selected_item->getDefaultMenuItemKey());
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();

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -159,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())

View file

@ -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())

View file

@ -60,4 +60,7 @@ interface PhabricatorInlineCommentInterface extends PhabricatorMarkupInterface {
public function supportsHiding();
public function isHidden();
public function getDateModified();
public function getDateCreated();
}

View file

@ -107,6 +107,14 @@ final class PHUIDiffInlineCommentDetailView
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;
@ -126,6 +134,7 @@ final class PHUIDiffInlineCommentDetailView
'isFixed' => $is_fixed,
'isGhost' => $inline->getIsGhost(),
'isSynthetic' => $is_synthetic,
'isDraftDone' => $is_draft_done,
);
$sigil = 'differential-inline-comment';
@ -265,7 +274,7 @@ final class PHUIDiffInlineCommentDetailView
if (!$this->preview && $this->canHide()) {
$action_buttons[] = id(new PHUIButtonView())
->setTag('a')
->setTooltip(pht('Hide Comment'))
->setTooltip(pht('Collapse'))
->setIcon('fa-times')
->addSigil('hide-inline')
->setMustCapture(true);

View file

@ -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',

View file

@ -0,0 +1,66 @@
<?php
final class PHUIDiffInlineThreader extends Phobject {
public function reorderAndThreadCommments(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;
}
}

View file

@ -8,7 +8,7 @@ final class PHUIDiffRevealIconView extends AphrontView {
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Show Hidden Comment'),
'tip' => pht('Expand'),
'align' => 'E',
'size' => 275,
));

View file

@ -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;
}
$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);
}

View file

@ -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,
);
}

View file

@ -10,6 +10,7 @@ final class PHUITwoColumnView extends AphrontTagView {
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;
@ -91,6 +98,10 @@ final class PHUITwoColumnView extends AphrontTagView {
$classes[] = 'phui-two-column-fluid';
}
if ($this->tabs) {
$classes[] = 'with-tabs';
}
if ($this->subheader) {
$classes[] = 'with-subheader';
}
@ -124,6 +135,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 +154,7 @@ final class PHUITwoColumnView extends AphrontTagView {
),
array(
$header,
$tabs,
$subheader,
$table,
$footer,

Some files were not shown because too many files have changed in this diff Show more