650 Commits

Author SHA1 Message Date
81827bee9a Fix typo in custom policy handler 2022-08-22 17:07:04 +02:00
Anna Sirota
c041cfa1bd Fix alignment of custom badges
Reviewed By: Sergey Sharybin (sergey)
Differential Revision: https://developer.blender.org/D13741
2022-01-06 12:08:16 +01:00
e53bf0655e Add custom icons for Blender ID badges
Reviewed By: Sergey Sharybin (sergey)
Differential Revision: https://developer.blender.org/D13535
2021-12-10 13:00:51 +01:00
114b2b9f1b Disable as Spam - admin-only option to handle spammers
This implements a convenient way to handle spam users, mimicking the steps the
admins already do manually:

Performed tasks:
* Disable Account
* Real Name -> "spam"
* Title -> ""
* Icon → ""
* Blurb → ""
* Profile Picture → default

This is disabling the account using the same functionality as the "Disable User"
button. And on top of that it replaces the account real name with "spam" and
wipe the other personal information.

Note that this doesn't delete any posts from the user.
2021-07-14 16:21:22 +02:00
883046f69f Phabricator: Extend CONDUIT API to support xaction transactions
Example of null transactions and their expected value:

* D9:transaction 44 - "abandon"
* D22:transaction 112 - "commit"
* D22:transaction 96 - "accept"
* D1123:transaction 22872 - "rethink"

Reviewers: sergey

https://developer.blender.org/D11568
2021-06-10 16:34:13 +02:00
1810f1dda9 Fix duplicate system users added on every update
Was a mistake in the way how key de-duplication and non-phabricator-managed
commit access was dealt with.

For now simple fix: make sure user list is unique. Proper fix would require
something more sophisticated.
2021-03-05 12:10:25 +01:00
14aa6a671b Merge branch 'master' into blender-tweaks 2020-11-18 14:29:07 +01:00
284204e6bb Detect duplicate keys form user configuration
Apparently there are some duplicates in the phabricator
configuration, even though the interface has a check against
this.
2020-11-05 12:50:00 +01:00
1df20d771c Initialize committers variable
Prevented the check for variable collision detection from
previous commit: first time the committers property was
null.
2020-11-05 12:45:23 +01:00
6fa976f749 Make sure committers variable is never overwritten
Solves possible issue when having repositories "foo-bar" and "boo_bar".
2020-11-05 10:15:13 +01:00
8808cf8e8c Fix repositories not being properly escaped
In gitolite configuration variable which controls commit access must
not include dash.
2020-11-04 19:00:21 +01:00
f72b9824f3 Do not overwrite files if they did not change
This causes an extra file read, but the benefit is that it is easier
to compare data on the file system, quickly see what did actually change
and what did not.
2020-11-04 11:39:40 +01:00
epriestley
34082efb02 Add a basic "harbormaster.step.edit" API method
Summary: Ref T13585. Provide a minimal but technically functional "harbormaster.step.edit" API method.

Test Plan: Used the web console to modify the URI for a "Make HTTP Request" build step.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13585

Differential Revision: https://secure.phabricator.com/D21489
2020-11-03 12:50:18 -08:00
epriestley
bf8707d3a9 Add a basic "harbormaster.step.search" API method
Summary: Ref T13585. This isn't particularly useful (notably, it does not include custom field values and isn't searchable by build plan PHID) but get the basics into place.

Test Plan: Used the web UI to make API calls, reviewed results.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13585

Differential Revision: https://secure.phabricator.com/D21488
2020-11-03 12:50:17 -08:00
epriestley
ae5a38f334 Guarantee terms in PhabricatorAuthPasswordEngine are strings
Summary:
Ref T2312. Numeric strings are read out of arrays as integers, and modern PHP raises appropriate warnings when they're then treated as strings.

For now, cast the keys to strings explicitly (we know we inserted only strings). In the future, introduction of a `StringMap` type or similar might be appropriate.

Test Plan:
  - Added "abc.12345.xyz" to the blocklist, changed my VCS password.
  - Before: fatal when trying to "strpos()" an integer.
  - After: password change worked correctly.

Maniphest Tasks: T2312

Differential Revision: https://secure.phabricator.com/D21487
2020-11-03 11:04:49 -08:00
ac94643ac7 Correct for the previous system keys refactor 2020-11-03 17:11:18 +01:00
ce3c14919d Fix for previous commit
Syntax error.

That's what one gets by typing code on one machine and testing on another.
2020-11-03 17:09:39 +01:00
c9fb4c2945 More fixes for gitolite configuration script
More usages of undefined variable.
Corrected mistake in collection of system keys.
2020-11-03 17:08:00 +01:00
57099f29d1 Fixes for gitolite synchronization script
- Typo in the variable used, caused by bad refactoring skills.
- Explicitly do pull rebase semantic.
2020-11-03 17:04:03 +01:00
acfdc33789 Fix gitolite config incrementally adding more and more new lines 2020-11-03 16:24:29 +01:00
a5efb1e8cd Initial implementation of gitolite integration script
Is based on old gitosis/gitadmin script, but it is heavily refactored
in the process of migration.
2020-11-02 16:13:24 +01:00
epriestley
c04147328f Fix isValidGitShallowCloneResponse
Summary:
Changes the heuristic method by which non-zero exit statuses from git-http-backend are found to be due to packfile negotiation during shallow fetches, etc.

Instead of checking git-http-backend stderr for a generic "hung up" error message, see if the pack-result response contains a terminating flush packet ("0000"). This should give a greater assurance that the request was handled correctly and the response is complete.

Test Plan: Run `GIT_CURL_VERBOSE=1 git fetch --depth 1 https://host.example/source/repo.git HEAD` to ensure it completes and includes two successful POST requests during packfile negotiation (the last one actually receives the packfile).

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley, #blessed_reviewers

Subscribers: Korvin, dzduvall

Tags: #diffusion

Differential Revision: https://secure.phabricator.com/D21484
2020-10-30 13:46:24 -07:00
epriestley
671986592b Add a missing "GROUP BY" to MailQuery when querying for multiple recipients
Summary:
See <https://discourse.phabricator-community.org/t/mail-details-view-broken/4315>. The change in D21400 detects a missing "GROUP BY" in some variations of this query.

Specifically, we may join multiple recipient rows (since mail may have multiple recipients) and then fail to group the results.

Fix this by adding the "GROUP BY". Additionally, remove the special-cased behavior when no authors or recipients are specified -- it's complicated and not entirely correct (e.g., may produce a "no object" instead of a policy error when querying by ID), and likely predates overheating.

Test Plan:
  - Disabled `metamta.one-mail-per-recipient` in Config.
  - Generated a message to 2+ recipients.
  - Viewed the message detail; queried for the message by specifying 2+ recipients.
  - Viewed the unfiltered list of messages, saw the query overheat.

Differential Revision: https://secure.phabricator.com/D21486
2020-10-30 13:02:22 -07:00
epriestley
bc4f86d279 When a new, deleted, draft inline is revived with "Undo", undelete it
Summary:
See PHI1876. Normally, deleted inlines are undeleted with an "undelete" operation, which clears the "isDeleted" flag.

However, when an inline is deleted implicitly by using "Cancel" without first saving it, the flag currently isn't cleared properly. This can lead to cases where inlines seem to vanish (they are shown to the user in the UI, but treated as deleted on submission).

Test Plan:
There are two affected sequences here:

  - Create a new inline, type text, cancel, undo.
  - Create a new inline, type text, cancel, undo, save.

The former sequence triggers an "edit" operation. The subsequent "Save" in the second sequence triggers a "save" operation.

It's normally impossible in the UI to execute a "save" without executing an "edit" first, but "save" clearly should undelete the comment if you get there somehow, so this change clears the deleted flag in both cases for completeness.

  - Executed both sequences, saw comment persist in preview, on reload, and after submission.

Differential Revision: https://secure.phabricator.com/D21483
2020-10-19 12:34:03 -07:00
epriestley
b2e96df3a3 Update "arc call-conduit" instructions in Conduit API console for required "--"
Summary: See PHI1912. Ref T13491. "arc" now requires "--" when stdin is not a TTY; provide this argument for users.

Test Plan: Viewed example in console, saw "--". Executed example.

Maniphest Tasks: T13491

Differential Revision: https://secure.phabricator.com/D21482
2020-10-19 12:02:30 -07:00
epriestley
2b8bbae5fb Set an explicit height when drawing the dependent revision graph
Summary:
See PHI1900. Recent changes to how commit graphs are drawn made the height automatic in most cases, but it fails in Differential because the element isn't initially visible so the computed height is 0.

Just give them an explicit height so they show up again.

Test Plan: Viewed graphs in Maniphest, Differential, and Diffusion; saw them all render properly.

Differential Revision: https://secure.phabricator.com/D21481
2020-10-16 14:10:36 -07:00
epriestley
058d2489e7 Expose the "file attached to object" and "object attached to file" edges via "edge.search"
Summary:
See PHI1901. An install would like improved support for identifying files related to an object (like a task or revision) for retention/archival/backup/migration/snapshotting purposes.

The "attachment" edge is not really user-level: it just means "if you can see the object, that allows you to see the file". This set includes files that users may not think of as "attached", like thumbnails and internal objects which are attached for technical reasons.

However, this is generally an appropriate relationship to expose for retention purposes.

Test Plan: Used "edge.search" to find files attached to a revision and objects attached to a file.

Differential Revision: https://secure.phabricator.com/D21480
2020-10-16 13:45:35 -07:00
epriestley
1f7c736f9a Add a "Comment content" field to Herald
Summary: Ref T13583. To improve support for making it harder to improperly mix data retention policies, allow Herald to act on comment content.

Test Plan:
  - Wrote comment content Herald rules in Maniphest and Differential.
  - Submitted non-matching comments (no action) and matching comments (Herald action).
  - In Differential, triggered rules by submitting non-matching main content and a matching inline comment.

Maniphest Tasks: T13583

Differential Revision: https://secure.phabricator.com/D21479
2020-10-16 13:42:56 -07:00
epriestley
0f27cd46cc Never render "Show More Context" inside an inline comment suggestion diff
Summary:
See PHI1896. If you do this:

  - Create an inline comment over a wide range of lines.
  - Suggest an edit.
  - Make a change near the beginning of the block.
  - Make a change near the end of the block.
  - Save the inline.

...you get a rendering which includes a "Show More Context" fold in the middle.

Currently, this element renders in a visually broken way and consumes too many columns.

However, this element isn't ever desirable inside inline comment suggestions. Stop it from rendering entirely.

Test Plan:
  - Made an inline comment suggestion across lines 1-50 with edits at the beginning and end, saw a contiguous diff.
  - Made smaller inline comment suggestions (one line, a few lines).

Differential Revision: https://secure.phabricator.com/D21476
2020-10-02 09:47:32 -07:00
epriestley
0f0e94ca71 Use "getInlines()", not "_inlines", to access inlines on client Changeset objects
Summary:
See PHI1898. An install is reporting an execution/initialization order issue where this code is reachable before `_inlines` is initialized.

I can't immediately reproduce it, but using "getInlines()" is preferable anyway and seems likely to fix the problem.

Test Plan: Viewed revisions with inlines, added/removed/edited/replied to inlines, didn't find anything broken.

Differential Revision: https://secure.phabricator.com/D21475
2020-10-02 09:19:04 -07:00
epriestley
a5f20f7106 When printing, wrap all content in Remarkup tables more aggressively
Summary:
Ref T13564. See PHI1798. Earlier efforts here (see D21439) still leave us with:

  - Incorrect behavior for long URIs, like `http://www.example.com/MMMMM...`.
  - Incorrect beahvior for long text blocks, like `MMMMMM...`.
  - Undesirable behavior for monospaced text in non-printing contexts (it wraps when we'd prefer it not wrap).

Apply the wrapping rules to all "<td>" content to resolve these three prongs.

Test Plan:
  - Viewed long URIs, text blocks, and monospaced text in and out of tables, while printed and not printed, in Safari, Firefox, and Chrome.
  - All browser behavior now appears to be correct ("all content is preserved in printed document").
  - Some browser behavior when making wrapping choices is questionable, but I can't find an automatic solution for that.

Maniphest Tasks: T13564

Differential Revision: https://secure.phabricator.com/D21472
2020-09-28 09:47:46 -07:00
epriestley
58d3f6145a Fix an issue where known Subversion commits are incorrectly shown as "Discovering..."
Summary:
Ref T13552. The behavior of "RepositoryQuery" with ambiguous identifiers under "withRepositoryPHIDs()" is tricky. This leads to failure to load commits in Subversion in some cases.

Use "withRepository()", which gives us the correct identifier resolution behavior.

Test Plan: Viewed a subversion repository history in Diffusion, saw commit details after change.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21469
2020-09-17 13:55:47 -07:00
epriestley
f21a00a315 Fix an out-of-order issue in the new update-during-publish behavior
Summary:
Ref T13552. The Herald field "Accepted Differential revision" (and similar fields) depend on the task/revision update steps running before Herald executes.

Herald currently executes first, so it never sees associated revisions. Swap this order.

Test Plan: Published a commit, got a clean parse/import. Will test with production rules ("Cowboy Commits").

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21468
2020-09-17 13:40:45 -07:00
epriestley
a754c694de Add missing indexes to DrydockRepositoryOperation
Summary: See PHI1885. Repository operations are queryable by state and author, but neither column has a usable key. Add usable keys.

Test Plan: Ran EXPLAIN on a state query. Ran `bin/storage upgrade`. Ran EXPLAIN again, saw query go from a table scan to a `const` key lookup.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Differential Revision: https://secure.phabricator.com/D21465
2020-09-17 12:10:00 -07:00
e0bd65be95 Support custom policy for GIT access rules
Implements following rules:
- Users
- Users of any project
- Users of all projects
- Administrators
- Signers

The 'If No Rules Match' the access is implicitly considered to
be 'DENY'.

It is not possible to control access based on the Moon phase.
2020-09-17 13:26:33 +02:00
1bcc201b0a Support custom policy for SVN access rules
Implements following rules:
- Users
- Users of any project
- Users of all projects
- Administrators
- Signers

The 'If No Rules Match' the access is implicitly considered to
be 'DENY'.

It is not possible to control access based on the Moon phase.
2020-09-17 12:38:14 +02:00
b2d7879162 Cleanup: Make variable naming more clear
Match the generator name passed via the command line.
2020-09-17 11:39:19 +02:00
775a1c2eef Remove SVN authfile generation
It is no longer needed (replaced with the auth_provider).
2020-09-17 11:37:18 +02:00
bd07cea6a6 Remove access to htpassword custom field
It is retired and is no longer in use, replaced with the auth_provider.
2020-09-17 11:35:36 +02:00
epriestley
969587f7b0 Log unexpected exceptions raised by Conduit calls
Summary:
Ref T13581. Currently, unexpected exceptions inside Conduit calls are passed to the client, but not logged on the server.

These exceptions should generally be unexpected, and producing a server-side trace is potentially useful.

Test Plan: Simulated a during-execution exception, saw it get logged on the server.

Maniphest Tasks: T13581

Differential Revision: https://secure.phabricator.com/D21464
2020-09-15 17:36:43 -07:00
epriestley
2a83df5786 Fix an issue where a GROUP BY was missing when a query matched a revision using multiple hashes
Summary:
Ref T13581. If you query for revisions by hash and provide multiple hashes (A, B) which match a single revision (e.g., older and newer diffs for that revision), the query omits a GROUP BY clause but should contain one.

Add a GROUP BY clause in this case.

Test Plan:
With a working copy that has multiple hashes corresponding to a single revision, ran `arc branches` before and after the change. Before, got this error:

```
[2020-09-15 17:02:07] EXCEPTION: (ConduitClientException) ERR-CONDUIT-CORE: Rows passed to "loadAllFromArray(...)" include two or more rows with the same ID ("130"). Rows must have unique IDs. An underlying query may be missing a GROUP BY. at [<arcanist>/src/conduit/ConduitFuture.php:65]
```

After, clean execution.

Maniphest Tasks: T13581

Differential Revision: https://secure.phabricator.com/D21462
2020-09-15 17:36:42 -07:00
epriestley
6f78e2a91c When a commit is marked "closeable", clear the "published" flag
Summary:
Ref T13552. When a previously discovered commit becomes reachable from a permanent ref, we re-queue workers to update it. However, the commit may already be marked as "published", so the publish worker may do nothing.

It would perhaps be simpler to not mark the commit as published when it isn't reachable from a permanent ref, but this is tricky because the flag is also part of the "imported / all steps" state (see T13580).

Until that can be cleaned up, just clear the flag.

Test Plan:
  - Pushed a commit with "fixes X" to a non-permanent branch.
  - Pushed it to a permanent branch.
  - Before change: task failed to close.
  - After change: task closes properly.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21460
2020-09-15 17:36:42 -07:00
epriestley
737e7c8541 When an in-process worker subtask fails permanently, don't fatal the whole process
Summary:
Ref T13552. Fixes T13569. Currently, if a process uses in-process tasks (usually, a debugging/diagnostic workflow) and those tasks (or tasks those tasks queue) fail permanently, the exception escapes to top level and the process exits.

This isn't desirable; catch the exception and fail them locally instead.

Test Plan:
With a failing Asana integration and misconfigured Webhook, ran `bin/repository reparse --publish ...`.

  - Before: fatals on each substep.
  - After: warnings emitted for failed substep, but process completes.

Maniphest Tasks: T13569, T13552

Differential Revision: https://secure.phabricator.com/D21459
2020-09-15 17:36:41 -07:00
epriestley
93ef902ffa Fix a view fatal in CommitGraphView when commits are undiscovered
Summary:
Ref T13552. See <https://discourse.phabricator-community.org/t/viewing-repository-history-for-svn-repository-causes-unhandled-exception/4225/>.

This condition is flipped and can fatal by passing a `NULL` value for `$commit` to a typehinted method.

Test Plan: Viewed history page with undiscovered commits.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21458
2020-09-15 17:36:41 -07:00
epriestley
a39c590442 Move task and revision closure to the "publishing" step of the commit import pipeline
Summary:
Ref T13552. Now that these steps can build their own "CommitRef" object from storage on the "CommitData" object, move them from the "Message" step to the "Publishing" step.

This should resolve the root issue in T13552, where a commit moved from a non-permanent branch to a permanent branch does not publish closures properly.

Test Plan: Used "bin/repository reparse --publish ..." to republish changes.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21450
2020-09-15 17:36:40 -07:00
epriestley
cebde34425 Make "CommitData" wrap and persist a "CommitRef" record
Summary:
Ref T13552. Turn "CommitData" into an application-level layer on top of the repository-level "CommitRef" object.

For older commits which will not have a "CommitRef" record on disk, build a synthetic one at runtime. This could eventually be migrated.

Test Plan: Ran "bin/repository reparse --message", browsed Diffusion.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21449
2020-09-15 17:36:40 -07:00
epriestley
e454c3dafe Wrap all direct access to author/committer properties on "CommitData"
Summary: Ref T13552. Currently, various callers read raw properties off "CommitData" directly. Wrap these in accessors to support storage changes which persist "CommitRef" information instead.

Test Plan:
- Ran "diffusion.querycommits", saw the same data before and after.
- Looked at a commit, saw authorship information and date.
- Viewed tags in a repository, saw author information.
- Ran "rebuild-identities", saw no net effect.
- Grepped for callers to "getCommitDetail(...)".

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21448
2020-09-15 17:36:39 -07:00
epriestley
7d6874d9f0 Turn "bypassCache" into a no-op in "diffusion.querycommits"
Summary: Ref T13552. The internal caller for this now uses "internal.commit.search", which is always authority-reading. No legitimate external caller should rely on the behavior of "bypassCache"; no-op it to simplify behavior.

Test Plan: Called "diffusion.querycommits", saw the same data as before.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21447
2020-09-15 17:36:39 -07:00
epriestley
3a80efa440 Build "DiffusionCommitRef" objects from "internal.commit.search", not "diffusion.querycommits", in the message parser worker
Summary: Ref T13552. Swap the call we're using to build "CommitRef" objects here to the recently-introduced "internal.commit.search" method.

Test Plan: Used "bin/repository reparse --message ..." to reparse commits, added "var_dump()" to inspect results. Saw sensible CommitRef and CommitData objects get built.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21446
2020-09-15 17:36:39 -07:00
epriestley
f6238f9d9b Remove "bin/repository lookup-users" workflow
Summary:
Ref T13552. This is one of two callsites to "diffusion.querycommits". It's an old debugging workflow which I haven't used in years and which is likely obsoleted by identities and other changes.

I believe the root problem here was also ultimately user error (a user has misconfigured their local Git author email as another user).

Test Plan: Grepped for "lookup-users", got no hits.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21444
2020-09-15 17:36:38 -07:00
epriestley
a9506097ea Add "internal.commit.search" to replace the cache bypass mode of "diffusion.querycommits"
Summary:
Ref T13552. Commit parsers currently invoke a special mode of "diffusion.querycommits", which is an older frozen method.

The replacement, "diffusion.commit.search", is not really appropriate for low-level access. This mode of having a single method which operates in "cache" or "non-cache" modes also ends up in a lot of unnecessary field shuffling.

Provide "internal.commit.search" as a modern equivalent that returns a "DiffusionCommitRef"-compatible structure.

Test Plan: Executed "internal.commit.search", got sensible low-level commit results.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21443
2020-09-15 17:36:38 -07:00
epriestley
a745055813 Lift Diffusion Conduit call proxying to the root level of Conduit
Summary:
Ref T13552. Some Diffusion conduit calls may only be served by a node which hosts a working copy on disk, so they're proxied if received by a different node.

This capability is currently bound tightly to "DiffusionRequest", which is a bundle of context parameters used by some Diffusion calls. However, call proxying is not fundamentally a Diffusion behavior.

I want to perform proxying on a "*.search" call which does not use the "DiffusionRequest" parameter bundle. Lift proxying to the root level of Conduit.

Test Plan: Browsed diffusion in a clusterized repsository.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21442
2020-09-15 17:36:37 -07:00
epriestley
367cd28927 Delete some commit dead parsing code
Summary: Ref T13552. Neither "$hashes" or "$user" are used, and constructing them has no side effects.

Test Plan: Searched for these symbols.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21441
2020-09-15 17:36:37 -07:00
epriestley
3dfa89dd5d Update SES API to use AWSv4 signatures
Summary:
Ref T13570. Fixes T13235. In most cases, we use modern (v4) signatures for almost all AWS API calls, and have for several years.

However, sending email via SES currently uses an older piece of external code which uses the older (v3) signature method.

AWS is retiring v3 signatures on October 1 2020, so this pathway will stop working.

Update the pathway to use `PhutilAWSFuture`, which provides v4 signatures.

T13235 discusses poor error messages from SES. Switching to Futures fixes this for free, as they have more useful error handling.

Test Plan:
  - Configured an SES mailer, including the new `region` parameter.
  - Used `bin/mail send-test` to send mail via SES.
  - Sent invalid mail (from an unverified address); got a more useful error message.
  - Grepped for removed external, no hits.

Maniphest Tasks: T13570, T13235

Differential Revision: https://secure.phabricator.com/D21461
2020-09-15 13:03:49 -07:00
epriestley
6e1b5da112 Fix additional "xprintf()"-class static parameter lint errors
Summary: Ref T13577. After the fix in D21453, lint identifies additional static errors in Phabricator; fix them.

Test Plan: Ran `arc lint`; these messages are essentially all very obscure.

Subscribers: hach-que, yelirekim, PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13577

Differential Revision: https://secure.phabricator.com/D21457
2020-09-08 11:45:48 -07:00
epriestley
7daaaa8463 Remove obsolete write to "pid" property in "annihilateProcessGroup()" in Daemon Overseer
Summary: Ref T13579. This property was removed in D21425, but I missed this usage site. Remove the assignment; this class no longer tracks the subprocess PID directly.

Test Plan: Searched for "->pid", no further hits.

Maniphest Tasks: T13579

Differential Revision: https://secure.phabricator.com/D21452
2020-09-04 16:41:36 -07:00
epriestley
0854425d19 When printing timestamps on paper: use an absolute, context-free date format
Summary:
Ref T13573. Using the browser "Print" feature on pages produces "Thu, Aug 4, 12:22" timestamps which require context to interpret precisely (they don't have a year and don't have a timezone).

Instead, retain these timestamps in "screen" contexts but use "YYYY-MM-DD HH:MM:SS (UTC+X)" timestamps when printing.

Test Plan: Printed Maniphest tasks and other pages in Safari and Chrome using "?__print__=1" and "Print to PDF", saw absolute timestamps after this chagne in the printed documents.

Maniphest Tasks: T13573

Differential Revision: https://secure.phabricator.com/D21451
2020-09-04 16:36:34 -07:00
epriestley
72f149bf39 Require rows passed to "loadAllFromArray()" have unique keys
Summary:
See PHI1809, which identified a bug in Project search where queries with a large number of slugs could paginate improperly.

This change detects problems in this category: cases where multiple rows with the same ID are passed to "loadAllFromArray()". It's likely that all cases it detects are cases where a GROUP BY is missing.

Since this might have some false positives or detect some things which aren't fundamentally problematic, I'm planning to hold it until the next release.

Test Plan:
  - Reverted D21399, then created a project with multiple slugs and queried for one of them via "project.search". Hit this new exeception.
  - Browsed around a bit, didn't immediately catch any collateral damage.

Differential Revision: https://secure.phabricator.com/D21400
2020-08-12 09:05:14 -07:00
epriestley
429543b637 Fix some content/background overflow issues with commit graph lists
Summary: Ref T13552. There are currently some content overflow issues on the graph view where the menu height can exceed the content height and the frame is drawn on a sub-element. Make the frame draw around all the content.

Test Plan: Viewed commit graph history view, saw more sensible UI.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21440
2020-08-12 09:04:09 -07:00
epriestley
0b64092d25 Improve handle/status list display on devices in commit graph lists
Summary: Ref T13552. Provide a richer handle/status list item for commit lists.

Test Plan: Viewed commits in various interfaces, saw richer information.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21431
2020-08-12 09:04:08 -07:00
epriestley
49af92e903 Improve commit action item layout on mobile
Summary:
Ref T13552. Build the "commit list" elements so that the menu action items collapse under the element on mobile.

Also change the mobile breakpoint to 512px because my Safari window can't go any narrower than 508px. Future changes to responsive design will be more content-aware anyway.

Test Plan: Looked at commits in various interfaces, at desktop and mobile widths.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21430
2020-08-12 09:04:07 -07:00
epriestley
57f9450bcf Improve desktop and mobile layouts for new "CommitGridView"
Summary:
Ref T13552. The current layout doesn't work particularly well on desktops or devices.

We have some device/desktop table layout code, but it isn't generic. We also have property list layout code, but it isn't generic either.

Provide generic layout elements ("Fuel", from "Phabricator UI Layout" to "PHUIL"?) and narrowly specialize their display behavior. Then swap the ListItemView stuff to use it.

Test Plan:
Saw slightly better responsive behavior:

{F7637457}

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21418
2020-08-12 09:04:07 -07:00
epriestley
8aec3f916b Unify more build, property, auditor, and status information into "CommitGraphView"
Summary:
Ref T13552. In unifying the various Graph/List/Table commit views, some information was dropped -- particularly, audit status.

Restore most of it. The result isn't very pretty, but has most of the required information.

Test Plan: {F7637411}

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21417
2020-08-12 09:04:06 -07:00
epriestley
36dac46ff2 Clean up some minor commit list CSS
Summary: Ref T13552. Some of the CSS can be removed or simplified now that essentially all lists of commits are on a single rendering pathway.

Test Plan: Grepped for affected CSS, viewed commit graph.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21416
2020-08-12 09:00:09 -07:00
epriestley
57ee6649aa Remove "PhabricatorAuditListView"
Summary: Ref T13552. Remove yet another way to render a list of commits, and unify it with "CommitGraphView".

Test Plan:
  - Viewed commit search results.
  - Viewed owners package detail page.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21415
2020-08-12 09:00:02 -07:00
epriestley
2b0632b442 Remove "DiffusionHistoryTableView" and "DiffusionHistoryView"
Summary:
Ref T13552.

Currently, the "Browse" page shows a snippet of unmerged changes if you're looking at a non-default branch. Remove this for consistency with the simplified main "Browse" page. This is reachable via "Compare".

Update the "Compare" page to use the new "CommitGraphView".

Test Plan:
  - Looked at the "Browse" page of "stable".
  - Looked at the "Compare" page for "stable vs master".

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21414
2020-08-12 08:59:53 -07:00
epriestley
7087c0439a Move the view of merged changes to "DiffusionCommitGraphView"
Summary: Ref T13552. When viewing a merge commit, merged changes are currently shown inline. Update this view to use the new "GraphView" rendering pipeline.

Test Plan:
  - Viewed a merge commit, saw merges.
  - Viewed history, profile page, etc.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21413
2020-08-12 08:59:46 -07:00
epriestley
cd09ba5e19 Replace "DiffusionCommitListView" with "DiffusionCommitGraphView"
Summary:
Ref T13552. This older view mostly duplicates other code and has only two callsites:

  - The "Commits" section of user profile pages.
  - The "Ambiguous Hash" page when you visit a commit hash page which is an ambiguous prefix of two or more commit hashes.

Replace both with "DiffusionCommitGraphView".

Test Plan:
  - Visited profile page, clicked "Commits".
  - Visited an ambiguous hash page (`rPbd3c23`).

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21412
2020-08-12 08:59:39 -07:00
epriestley
9fa2525384 Improve rendering of history graph in "CommitGraphView"
Summary: Ref T13552. In the new combined "table/list" graph view, tidy up the graph rendering.

Test Plan: {F7633504}

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21411
2020-08-12 08:59:31 -07:00
epriestley
46695c76eb Introduce "DiffusionCommitGraphView", which unifies "HistoryListView" and "HistoryTableView"
Summary:
Ref T13552. Currently, commit lists are sometimes rendered as an object list and sometimes rendered as a table. There are two separate views for table rendering.

Add a fourth view ("list, with a graph") with the eventual intent of unifying all the other views. For now, this only replaces "HistoryListView" -- and needs some more work to really be a convincing replacement.

Test Plan:
  - Looked at "History" in Diffusion, saw an ugly view with all the information we want.
  - Grepped for "HistoryListView", no hits.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21410
2020-08-12 08:59:23 -07:00
epriestley
c6de7c66a3 Remove the "Graph" view as a dedicated repository view
Summary:
Ref T13552. Currently, Diffusion has two effectively identical history views, the "Graph" view and the "History" view.

These arose out of product uncertainty about the importance of the graph, but I think we can just put the graph on the "object item list" view and merge these views.

Test Plan: Looked at repositories in Diffusion, no longer saw a "Graph" tab. Grepped for "graph"-related symbols.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21409
2020-08-12 08:59:16 -07:00
epriestley
9afc5c6287 Remove "Recent Commits" from repository landing page
Summary:
Ref T13552. Currently, the repository landing page has a panel with recent commits. This is accessible by clicking "History" and usually below the fold, so it's not clearly useful.

Since I'm consolidating this code anyway to fix an issue with the import pipeline, just get rid of this history view.

Test Plan: Viewed a repository landing page, no longer saw a history panel.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21408
2020-08-12 08:59:07 -07:00
epriestley
c8a279957d Remove "DiffusionTagTableView"
Summary: Ref T13552. This older class has no callers; tag and branch listings were replaced with an "ObjectList" view.

Test Plan: Grepped for "DiffusionTagTableView", got no hits.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21407
2020-08-12 08:58:59 -07:00
epriestley
60e9f64190 Remove the "authored" subheader from commits
Summary:
Ref T13552. I'm trying to reduce the number of direct callers to commit authorship metadata. This header seems low-value enough to simply remove; this information is shown more clearly and prominently in the "Provenance" UI.

In particular, commits have multiple dates (authored, committed, pushed) but this header shows only one. It currently shows the author identity and the commit date, which isn't entirely correct. And it potentially uses an "Identity" as a timeline actor, which is conceptually fine but not entirely firm ground.

Test Plan: Viewed a commit, saw no more subheader.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21406
2020-08-12 08:58:51 -07:00
epriestley
7fd6bf26a9 Modernize "Author" and "Committer" rendering for commits
Summary:
Ref T13552. Give "Commit" objects a more modern, identity-aware way to render author and committer information.

This uses handles in a more modern way and gives us a single read callsite for raw author and committer names.

Test Plan:
  - Grepped for callers to the old methods, found none. (There are a lot of "renderAuthor()" callers in transactions, but this call takes no parameters.)
  - Viewed some commits, saw sensible lists of authors and committers.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21405
2020-08-12 08:58:43 -07:00
epriestley
81e4e5b7f9 Remove construction of "author" information from "LastModified" payload in Diffusion
Summary:
Ref T13552. When viewing a directory in Diffusion, we make an Ajax call to get the last commit for each path.

This call currently pulls author information, since an older version of this UI showed author information.

The current UI does not show author information, so this parameter is unused. Delete the code which builds it.

Test Plan: Grepped for `'author'` and references to the "pull-lastmodified" behavior. This behavior is invoked in only one place, which never generates an author placeholder.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21404
2020-08-12 08:58:32 -07:00
epriestley
5454175973 Coerce Chrome into breaking monospaced text when printing tables to PDFs
Summary:
See T13564. In Chrome only, printing tables with a cell containing an unbroken monospaced text element fails to wrap/break the cell.

Adding "overflow-wrap" appears to fix this without making anything worse. Try this until new problems arise.

Test Plan: Printed such a table to PDF in Chrome, got wrapping with all content visible in the PDF.

Differential Revision: https://secure.phabricator.com/D21439
2020-08-12 07:34:54 -07:00
epriestley
79375c6c53 Make "Quote" work properly in Pholio
Summary:
See <https://discourse.phabricator-community.org/t/quote-comment-missing-on-mock-pages/4155>. Pholio is currently missing a couple of configuration calls to make the "Quote" action work.

Moving to EditEngine is the "real" fix, but this fix is trivial and should make "Quote" work properly with no negative effects.

Test Plan: Viewed a mock, used "quote" to quote a comment.

Differential Revision: https://secure.phabricator.com/D21437
2020-08-10 13:40:25 -07:00
epriestley
ce0dc9a2ba Correct an apparent off-by-one error when adjusting inlines across revision changes
Summary:
See PHI1834. It's not obvious why this "+1" is present in the code, but it causes inlines to be adjusted incorrectly when a file is not modified across changes. See D21435.

Remove it, which appears to produce accurate adjustment behavior.

Test Plan:
  - See D21435 for instructions to build a change, where a file with lines "A-Z" is unmodified across Diff 1 and Diff 2.
  - Left inlines on lines 14, 17-19, and 16-26 (end of the file) on Diff 1.
  - Before: saw inlines incorrectly adjusted to lines 15, 18, and 17 on Diff 2. Before D21435, the last inline was culled by the rendering engine.
  - After: saw inlines correctly adjusted to lines 14, 17, and 16 (the same lines as the original), render properly, and highlight the correct lines when hovered.

Differential Revision: https://secure.phabricator.com/D21436
2020-08-05 13:12:53 -07:00
epriestley
dbdfac1e07 Recover inline comments which are "adjusted" off the end of a diff
Summary:
See PHI1834. Currently, the inline adjustment engine can sometime "adjust" an inline off the end of a diff. If it does, we lay it out on an invalid display line here and never render it.

Instead, make sure that layout never puts a comment on an invalid line, so the UI is robust against questionable decisions by the adjustment engine: no adjustment should be able to accidentally discard an inline.

Test Plan:
  - Created a two diff revision, where Diffs 1 and 2 have "alphabet.txt" with A-Z on one line each. The file is unchanged across diffs; some other file is changed.
  - Added a comment to lines P-Z of Diff 1.
  - Before: comment is adjusted out of range on Diff 2 and not shown in the UI.
  - After: comment is still adjusted out of range internally, but now corrected into the display range and shown.

Differential Revision: https://secure.phabricator.com/D21435
2020-08-05 13:12:52 -07:00
epriestley
2db1955159 In Jupyter notebooks, read strings stored in the raw as either "string" or "list<string>" more consistently
Summary:
Ref PHI1835. Generally, Jupyter notebooks in the wild may store source and markdown content as either a single string or a list of strings.

Make the renderer read these formats more consistently. In particular, this fixes rendering of code blocks stored as a single string.

This also fixes an issue where cell labels were double-rendered in diff views.

Test Plan:
Created a notebook with a code block represented on disk as a single string, rendered a diff from it.

{F7696071}

Differential Revision: https://secure.phabricator.com/D21434
2020-08-05 12:26:00 -07:00
epriestley
98e0440d45 In 1-up source diffs, retain the "No newline at end of file" on "\" lines
Summary:
See PHI1839. Currently, the "No newline at end of file" text is dropped in the 1-up diff view for changes that affect a file with no trailing newline.

Track it through the construction of diff primitivies more carefully.

Test Plan: {F7695760}

Differential Revision: https://secure.phabricator.com/D21433
2020-08-05 10:16:58 -07:00
epriestley
c1eeacd850 Move "Wait for Previous Commits to Build" out of prototype
Summary:
Although I'm not entirely thrilled about doing flow control like this (as an actual action in a build plan), I believe this build step works correctly and there's no fancy replacement mechanism on the immediate horizon, and this didn't send us down a slippery slope of Turing-complete builds encoded without real structure or context. Just kick it out of prototype.

(Other approaches which might be better in the long run are things like "this is a top-level behavior on the build plan itself" and/or "build plans are written in a DSL, not a Javascript UI".)

Test Plan: Added a new build step, saw this as an option in the "Flow Control" section.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Differential Revision: https://secure.phabricator.com/D21432
2020-07-30 12:46:47 -07:00
epriestley
017ef1927c Revert use of "user-select: all" to modify tab selection behavior
Summary:
Reverts D21419. See PHI1814. Previously, I used "user-select: all" to group sequences of spaces for selection.

However, this has a side effect: the sequence is now selected with a single click. I didn't read the docuementation on the CSS property thoroughly and missed this in testing, since I was focused on drag-selection behavior.

This behavior is enough of a net negative that I think we're in a worse state overall; revert it.

Test Plan: Straight revert.

Differential Revision: https://secure.phabricator.com/D21429
2020-07-24 13:41:26 -07:00
epriestley
a27c83757d Remove ancient "phd.trace" and "phd.verbose" configuration options
Summary:
Ref T13556. These options are very old and effectively obsoleted by "bin/phd debug [--trace]". I haven't used either option diagnostically in many years, and they aren't mentioned in the documentation.

Remove them to simplify configuration, and because "phd.trace" doesn't work anyway and likely hasn't for a long time -- it has specific issues with TTY detection (see T13556).

Test Plan: Grepped for "phd.trace" and "phd.verbose". Ran "bin/phd debug [--trace]" and saw verbose/trace output.

Maniphest Tasks: T13556

Differential Revision: https://secure.phabricator.com/D21426
2020-07-23 12:31:32 -07:00
epriestley
78d1b62bb8 Streamline handling of Futures and PIDs in daemons
Summary:
Ref T13555. Currently, the daemon future may resolve into a failure state immediately inside "start()", and not have a valid PID when we read it.

Instead, read PIDs from the current active future in all cases, using "hasPID()" to test for the presence of a valid PID.

Since we don't query the PID immediately, we no longer need to explicitly start the future.

Also fix an issue where the same future could be added to the overseer pool more than once if it threw on "resolve()". In general:

  - Before we "resolve()" a future, detach it from the DaemonHandle: we're always done with it.
  - Catch exceptions on resolution and treat them the same way as subprocess resolution errors. These aren't common, but are possible in the general case.
  - Have DaemonHandle add futures to the future pool directly when they're created.

Test Plan:
  - Ran daemons with intentional subprocess creation failures, saw clean recovery.
  - Ran daemons with intentional resolution exceptions, saw clean recovery.

Maniphest Tasks: T13555

Differential Revision: https://secure.phabricator.com/D21425
2020-07-23 11:22:31 -07:00
epriestley
5f0535934d Manage PIDs more carefully in DaemonHandle
Summary:
Ref T13555. Although these callsites may not actually impact anything, it's possible for an active handle to have no PID (e.g., if the subprocess failed to start).

Handle these cases more carefully.

Test Plan: Started daemons, saw them run fine. See also next change.

Maniphest Tasks: T13555

Differential Revision: https://secure.phabricator.com/D21424
2020-07-23 11:22:30 -07:00
epriestley
fcb75d0503 Fix an issue where prose diffing may fail after hitting the PCRE backtracking limit
Summary:
Fixes T13554. For certain prose diff inputs and PCRE backtracking limits, this regular expression may back track too often and fail.

A characteristic input is "x x x x ...", i.e. many sequences where `(.*?)\s*\z` looks like it may be able to match but actually can not.

I think writing an expression which has all the behavior we'd like without this backtracking issue isn't trivial (at least, I don't think I know how to do it offhand); just use a strategy based on "trim()" insetad, which avoids any PCRE complexities here.

Test Plan: Locally, this passes the "x x x ..." test which the previous code failed. I'm not including that test because it won't reproduce across values of "pcre.backtrac_limit", PCRE versions, etc.

Maniphest Tasks: T13554

Differential Revision: https://secure.phabricator.com/D21422
2020-07-23 07:46:15 -07:00
epriestley
8f9ba48528 Fix an issue with destruction of Revision and Diff objects with viewstates
Summary:
See <https://discourse.phabricator-community.org/t/domainexception-when-trying-to-remove-an-differentialrevision/4105>.

These queries aren't actually constructed properly, and destroying a revision or diff with viewstates currently fails.

Test Plan: Used `bin/remove destroy Dxxx` to destroy a revision with viewstates (this also destroys the associated diffs).

Differential Revision: https://secure.phabricator.com/D21421
2020-07-22 13:07:11 -07:00
epriestley
0ed5569e9f Likely, fix a warning when rendering modified coverage
Summary: See PHI1819. This structure may have `null` elements.

Test Plan: Will confirm user reproduction case.

Differential Revision: https://secure.phabricator.com/D21420
2020-07-17 19:45:26 -07:00
epriestley
37ffb71c4d In source views, wrap display tabs in "user-select: all" to improve cursor selection behavior
Summary:
Ref T2495. See PHI1814. Currently, Phabricator replaces tabs with spaces when rendering diffs.

This may or may not be the best behavior in the long term, but it gives us more control over expansion of tabs than using tab literals.

However, one downside is that you can use your mouse cursor to select "half a tab", and can't use your mouse cursor to distinguish between tabs and spaces. Although you probably shouldn't be doing this, this behavior is less accurate/correct than selecting the entire block as a single unit.

A specific correctness issue with this behavior is that the entire block is copied to the clipboard as a tab literal if you select any of it, so two different visual selection ranges can produce the same clipboard content.

This particular behavior can be improved with "user-select: all", to instruct browsers to select the entire element as a single logical element. Now, selecting part of the tab selects the whole thing, as though it were really a tab literal.

(Some future change might abandon this approach and opt to use real tab literals with "tab-size" CSS, but we lose some ability to control alignment behavior if we do that and it doesn't have any obvious advantages over this approach other than cursor selection behavior.)

Test Plan:
  - In Safari and Firefox, dragged text to select a whitespace-expanded tab literal. Saw browsers select the whole sequence as though it were a single tab.
  - In Chorme, this also mostly works, but there's some glitchiness and flickering. I think this is still a net improvement, it's just not as smooth as Safari and Firefox.

Maniphest Tasks: T2495

Differential Revision: https://secure.phabricator.com/D21419
2020-07-17 15:10:06 -07:00
epriestley
1d4d860cb5 Allow non-authors to "Request Review" of draft revisions
Summary:
See PHI1810. In situations where:

  - An author submits an urgent change for review.
  - The author pings reviewers to ask them to look at it.

...the reviewers may not be able to move the review forward if the review is currently a "Draft". They can only "Commandeer" or ask the author to "Request Review" as ways forward.

Although I'm hesitant to support review actions (particularly, "Accept") on draft revisions, I think there's no harm in allowing reviewers to skip tests and promote the revision out of draft as an explicit action.

Additionally, lightly specialize some of the transaction strings to distinguish between "request review from draft" and other state transitions.

Test Plan:
  - As an author, used "Request Review" to promote a draft and to return a change to reviewers for consideration. These behaviors are unchanged, except "promote a draft" has different timeline text.
  - As a non-author, used "Begin Review" to promote a draft.

Differential Revision: https://secure.phabricator.com/D21403
2020-07-09 14:20:51 -07:00
epriestley
6e85b521fe Don't raise the "Subscribers Won't Be Notified" draft warning if you aren't adding any non-you subscribers
Summary:
Currently, adding subscribers to a draft revision raises a warning that they won't get an email/notification.

This warning has some false positives:

  - it triggers on any subscriber change, including removing subscribers; and
  - it triggers if you're only adding yourself as a subscriber.

Narrow the scope of the warning so it is raised only if you're adding a subscriber other than yourself.

Test Plan:
  - Added a non-self subscriber, got the warning as before.
  - Added self as a subscriber, no warning (previously: warning).
  - Removed a subscriber, no warning (previously: warning).

Differential Revision: https://secure.phabricator.com/D21402
2020-07-09 14:20:51 -07:00
epriestley
b21b73b8dd Expand Revision transaction API to allow actions to vary more broadly based on the viewer and revision state
Summary:
See PHI1810. Build toward support for "Request Review" by non-authors on drafts, to forcefully pull a revision out of draft.

Currently, some action strings can't vary based on revision state or the current viewer, so this "pull out of draft" action would have to either: say "Request Review"; or be a totally separate action.

Neither seem great, so allow the labels and messages to vary based on the viewer and revision state.

Test Plan: Grepped for affected symbols, see followup changes.

Differential Revision: https://secure.phabricator.com/D21401
2020-07-09 14:20:50 -07:00
epriestley
73c4240415 Add some additional patterns to the "filter Mercurial --debug output" list
Summary:
Modern Mercurial may emit some more patterns under "--debug".

This whole list is gross and can likely now be eliminated by increasing the minimum required Mercurial version (as `arc` has), but just paper over it for now.

Test Plan:
Locally, saw some views return to functional behavior that weren't previously working on a modern version of Mercurial.

The reproduction case is likely something in the vein of "repository is not writable by webserver, look at history view".

Differential Revision: https://secure.phabricator.com/D21398
2020-07-09 10:50:13 -07:00
epriestley
56838c0e3d Fix an issue where querying for a large number of projects by slug could paginate incorrectly
Summary:
See PHI1809. This query may join the "slug" table, but each project may have multiple slugs, and the query does not "GROUP BY" when this join occurs.

This may lead to partial result sets and unusual paging behavior.

This could likely be caught categorically in `loadAllFromArray()`; I'll adjust this in a followup.

Test Plan:
A minimal reproduction case is something like:

  - Give project P slugs: a, b, c.
  - Give project Q slugs: d.
  - Query for slugs: a, b, c, d; with limit 2.
  - Order the query so P returns first.
  - Expect: P and Q.
  - Actual: P generates 3 raw rows and the final result is just P with no pagination cursor.

Differential Revision: https://secure.phabricator.com/D21399
2020-07-09 10:50:03 -07:00
epriestley
205657ac76 Allow the Fact daemon to hibernate
Summary:
A handful of Phacility production shards have run into memory pressure issues recently. Although there's no smoking gun, and at least two other plausible contributors, one possible concern is that the Fact daemon was written before hibernation and can not currently hibernate. Even if there's no memory leak, this creates unnecessary memory pressure by holding the processes in memory.

Allow the Fact daemon to hibernate, like other daemons do.

Test Plan: Ran "bin/phd debug fact", saw the Fact daemon hibernate.

Subscribers: yelirekim

Differential Revision: https://secure.phabricator.com/D21389
2020-07-01 06:33:06 -07:00
epriestley
7d496f2c6d Collapse repository URI normalization code into Arcanist
Summary: Ref T13546. Companion change to D21372. Move URI normalization code to Arcanist to we can more-often resolve remote URIs correctly.

Test Plan: Grepped for affected symbols.

Maniphest Tasks: T13546

Differential Revision: https://secure.phabricator.com/D21373
2020-06-30 15:54:33 -07:00
epriestley
a7b3ba5a6f When long monospaced character sequences appear in Remarkup tables, break rather than scrolling
Summary:
Ref PHI1798. If you put an SSH public key in a table cell with monospaced formatting and then print the table, the cell scrolls and not all of the content appears in your physical printed document.

Generally, the current scrolling behavior for monospaced text seems never-desirable: I can't imagine any cases where we want the table cell to scroll. (There's more of an argument for complex cases where a table cell has, say, an embedded paste.)

Add `line-break: anywhere` to break monospaced text inside these cells.

Test Plan: In Safari, Firefox, and Chrome, viewed a ##|`MMMMM....`|## table. Saw scrolling before and wrapping/breaking after.

Differential Revision: https://secure.phabricator.com/D21370
2020-06-29 16:18:05 -07:00
epriestley
22de618d3b When acquiring a GlobalLock, put good connections that just got unlucky back in the pool
Summary:
See PHI1794, which describes a connection exhaustion issue with a large number of webhook tasks in queue.

The "GlobalLock" mechanism manages a separate connection pool from the main pool, and webhook workers immediately try to grab a webhook lock with a 0-second wait when they start. So far, this is fine.

Prior to this change, good connections which fail to acqiure a lock are discarded. This can lead to connection exhaustion as the worker rapidly cycles through lock attempts: the connections will remain open for at least 60 seconds (since D16389) in an effort to avoid outbound port exhaustion, but they're effectively orphaned because they aren't part of the main pool and aren't part of the lock pool. We're basically leaking a connection every time we fail to lock.

Failing to lock doesn't mean we need to discard the connection: it's a completely suitable connection for reuse. Instead of dropping it on the floor, put it into the lock pool.

Test Plan:
  - Used "bin/webhook call ... --count 10000 --background" to queue a large number of webhook calls against a slow ("sleep(15);") webhook.
  - Used "bin/phd launch 32 taskmaster" to start taskmasters.
  - Observed MySQL connection behavior:
    - Before change: 2048 configured connections immediately exhausted.
    - After change: connections stable at ~160ish.
  - Ran queue for a while, saw expected single-threaded calls to webhook.

Differential Revision: https://secure.phabricator.com/D21369
2020-06-25 18:06:09 -07:00
epriestley
d91abf50f7 Add "--background" and "--count" flags to "bin/webhook call"
Summary:
See PHI1794, which reports an issue where a large number of queued webhook calls led to connection exhaustion. To make this easier to reproduce and test, add "--count" and "--background" flags to "bin/webhook call".

This primarily supports "bin/webook call ... --background --count 10000" to quickly fill the queue with a bunch of calls.

Test Plan: Ran `bin/webhook call` in foreground and background modes, with and without counts. Saw appropriate console and queue behavior.

Differential Revision: https://secure.phabricator.com/D21368
2020-06-25 18:05:58 -07:00
6139ce1841 Canned responses for bug reports
This shows a 'reply' icon in the markup form header. The config to add more
canned responses is `maniphest.canned-responses`.

Note: There is no validation (other than json), so the config is expected to
match the required fields ('name' and 'message')

Note: This shows at all markups, so technically it can be used for patch
review and other places where canned reponses would fit.

Differential Revision: D8034
2020-06-17 15:42:46 +02:00
epriestley
9ce1271805 Improve the quality of SSH error messages
Summary: See PHI1784. Currently, users who pass an invalid SSH command to Phabricator's SSH handler get an unhelpful error message. Make it more helpful.

Test Plan: Ran `./bin/ssh-exec` with no arguments (old, helpful error), invalid arguments (before: unhelpful error; after: helpful error), and valid arguments (old, helpful behavior).

Differential Revision: https://secure.phabricator.com/D21362
2020-06-16 08:59:36 -07:00
epriestley
8c7f114b4d Fix an issue where "Export Data" could fail if a user had a nonempty custom policy preference
Summary:
The "Export Data" workflow incorrectly uses the "Policy Favorites" setting to choose a default export format. This is just a copy/paste error; the correct setting exists and is unused.

If the setting value is an array (as the "Policy Favorites" value often is), we try to use it as an array index. This generates a runtime exception after D21044.

```
[2020-06-16 06:32:12] EXCEPTION: (RuntimeException) Illegal offset type in isset or empty at [<arcanist>/src/error/PhutilErrorHandler.php:263]
  #0 <#2> PhutilErrorHandler::handleError(integer, string, string, integer, array) called at [<phabricator>/src/applications/search/controller/PhabricatorApplicationSearchController.php:460]
```

  - Use the correct setting.
  - Make sure the value we read is a string.

Test Plan:
  - Used "Export Data" with a nonempty, array-valued "Policy Favorites" setting.
    - Before: runtime exception.
    - After: clean export.
  - Used "Export Data" again, saw my selection from the first time persisted.

Differential Revision: https://secure.phabricator.com/D21361
2020-06-16 06:44:23 -07:00
c38766c17f Merge branch 'master' into blender-tweaks 2020-06-16 11:41:11 +02:00
Aviv Eyal
d203a1004c Update tab completion doc
Test Plan: `aspell -c`

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley, #blessed_reviewers

Subscribers: Korvin

Differential Revision: https://secure.phabricator.com/D21359
2020-06-15 13:27:18 +00:00
epriestley
5b1dd96e40 Add an explicit "uri" to the "harbormaster.buildable.search" results
Summary: Ref T13546. This makes some "arc" tasks a little easier, and will make them more correct if "arc" ever switches to using SSH.

Test Plan: Ran "harbormaster.buildable.search" from the web UI, saw URIs in the result set.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13546

Differential Revision: https://secure.phabricator.com/D21346
2020-06-10 17:31:33 -07:00
epriestley
36075f6ce5 Correct a prose diff behavior when prose pieces include newlines
Summary:
See <https://discourse.phabricator-community.org/t/bad-regex-in-prose-diff-logic/3969>.

The prose splitting rules normally guarantee that newlines appear only at the beginning or end of blocks. However, if a prose sentence ends with text like "...x\n.", we can end up with a newline inside a "sentence".

If we do, the regular expression that breaks it into pieces will fail.

Arguably, this is an error in how sentences are split apart (we might prefer to split this into two sentences, "x\n" and ".", rather than a single "x\n." sentence) but in the general case it's not unreasonable for blocks to contain newlines, so a simple fix is to make the pattern more robust.

Test Plan: Added a failing test which includes this behavior, made it pass.

Differential Revision: https://secure.phabricator.com/D21295
2020-05-30 14:11:37 -07:00
epriestley
f686a0b827 In Phortune accounts, prevent self-removal more narrowly
Summary:
Currently, Phortune attempts to prevent users from removing themselves as account managers. It does this by checking that the new list includes them.

Usually this is sufficient, because you can't normally edit an account unless you're already a manager. However, we get the wrong result (incorrect rejection of the edit) if the actor is omnipotent and the acting user was not already a member.

It's okay to edit an account into a state which doesn't include you if you have permission to edit the account and aren't already a manager.

Specifically, this supports more formal tooling around staff modifications to billing accounts, where the actor has staff-omnipotence and the acting user is a staff member and only used for purposes of leaving a useful audit trail.

Test Plan: Elsewhere, ran staff tooling to modify accounts and was able to act as "alice" to add "bailey", even though "alice" was not herself a manager.

Differential Revision: https://secure.phabricator.com/D21288
2020-05-26 07:09:42 -07:00
epriestley
a529efa5b8 Fix an issue where inline comments with only edit suggestions are considered empty
Summary:
Ref T13513. An inline is not considered empty if it has a suggestion, but some of the shared transaction code doesn't test for this properly.

Update the shared transaction code to be aware that application comments may have more complex emptiness rules.

Test Plan:
  - Posted an inline with only an edit suggestion, comment went through.
  - Tried to post a normal empty comment, got an appropriate warning.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21287
2020-05-23 08:24:57 -07:00
epriestley
3635a11f84 When cancelling an edit of an inline with content, don't hide the inline
Summary: See PHI1753. This condition got rewritten for suggested edits and accidentally inverted.

Test Plan:
  - Create a comment, type text, save draft, edit comment, cancel.
  - Before: comment hides itself.
  - After: comment properly cancels into pre-edit draft state.

Differential Revision: https://secure.phabricator.com/D21286
2020-05-22 15:40:25 -07:00
epriestley
959a835b95 When executing a repository passthru command via CommandEngine, don't set a timeout
Summary:
Ref T13541. The passthru future does not have time limit behavior, so if we reach this code we currently fail.

Phabricator never reaches this code normally, but this code is reachable during debugging if you try to foreground a slow fetch to inspect it.

Passthru commands generally only make sense to run interactively, and the caller or control script can enforce their own timeouts (usually by pressing "^C" with their fingers).

Test Plan: Used a debugging script to run ref-by-ref fetches in the foreground.

Maniphest Tasks: T13541

Differential Revision: https://secure.phabricator.com/D21284
2020-05-22 11:54:36 -07:00
epriestley
4fd0628fae Fix two rendering issues with Jupyter notebooks
Summary:
See PHI1752.

  - Early exit of document layout can cause us to fail to populate available rows.
  - Some Jupyter documents have "markdown" cells with plain strings, apparently.

Test Plan: Successfully rendered example diff from PHI1752.

Differential Revision: https://secure.phabricator.com/D21285
2020-05-22 11:53:55 -07:00
epriestley
87fb35abb7 Prevent keyboard selection of change blocks inside edit suggestions
Summary: Ref T13513. When a revision has inlines with edit suggestions, pressing "j" and "k" can incorrectly select the blocks inside the diffs inside the inlines.

Test Plan: Used "j" to cycle through changes in a revision with inline comments with edit suggestions, didn't get jumped into the suggestion diffs.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21283
2020-05-21 15:37:51 -07:00
epriestley
66566f878d Make "Open in Editor" use the simple line number of the current selected block
Summary:
Ref PHI1749. Instead of opening files to the last unchanged line on either side of the change, open files to the "simple" line number of the selected block.

For inlines, this is the inline line number.

For blocks, this is the first new-file line number, or the first old-file line number if no new-file line number exists in the block.

This may not always be what the user is hoping for (we can't know what the state of their working copy is) but should produce more obvious behavior.

Test Plan:
  - In Diffusion, used "Open in Editor" with and without line selections. Saw same behavior as before.
  - Used "n" and "r" to leave an inline with the keyboard, saw same behavior as before.
  - Used "\" and "Open in Editor" menu item to open a file with:
    - Nothing selected or changeset selected (line: 1).
    - An inline selected (line: inline line).
    - A block selected (line: first line in block, per above).

Differential Revision: https://secure.phabricator.com/D21282
2020-05-21 15:31:16 -07:00
epriestley
d3d41324be Drop old "differential_commit" table
Summary: Ref T13276. Ref T13513. All readers and writers were removed more than a year ago; clean up the last remnants of this table.

Test Plan: Grepped for table references, found none.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13513, T13276

Differential Revision: https://secure.phabricator.com/D21281
2020-05-20 14:30:39 -07:00
epriestley
6d0dbeb77f Use the changeset parse cache to cache suggestion changesets
Summary:
Ref T13513. Syntax highlighting is potentially expensive, and the changeset rendering pipeline can cache it. However, the cache is currently keyed ONLY by Differential changeset ID.

Destroy the existing cache and rebuild it with a more standard cache key so it can be used in a more ad-hoc way by inline suggestion snippets.

Test Plan: Used Darkconsole, saw cache hits and no more inline syntax highlighting for changesets with many inlines.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21280
2020-05-20 14:29:27 -07:00
epriestley
5d0ae283a9 Put a readthrough cache in front of inline context construction
Summary: Ref T13513. Inline comment context information is somewhat expensive to construct and can be cached. Add a readthrough cache on top of it.

Test Plan: Loaded a source code changeset with many inline comments, used Darkconsole to inspect query activity. Saw caches get populated. Updated cache key, saw caches regenerate. Browsed Diffusion, nothing looked broken.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21279
2020-05-20 14:28:37 -07:00
epriestley
d2d7e7f5ff Clean up Diffusion behaviors for inline edit suggestions
Summary: Ref T13513. For now, I'm not supporting inline edit suggestions in Diffusion, although it's likely not difficult to do so in the future. Clean up some of the code so that plain ol' inlines work the same way they did before.

Test Plan:
  - Created, edited, reloaded, submitted inlines in Diffusion: familiar old behavior.
  - Created, edited, reloaded, submitted inlines with suggestions in Differential: familiar new behavior.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21278
2020-05-20 14:28:12 -07:00
epriestley
10f241352d Render inline comment suggestions as real diffs
Summary: Ref T13513. When rendering an inline suggestion for display, use highlighting and diffing.

Test Plan: {F7495053}

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21277
2020-05-20 14:27:40 -07:00
epriestley
846562158a Roughly support inline comment suggestions
Summary:
Ref T13513. This still has quite a few rough edges and some significant performance isssues, but appears to mostly work.

Allow reviewers to "Suggest Edit" on an inline comment and provide replacement text for the highlighted source.

Test Plan: Created, edited, reloaded, and submitted inline comments in various states with and without suggestion text.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21276
2020-05-20 14:26:37 -07:00
epriestley
00430fdbe1 Make server components of inline comment content handling state-oriented
Summary: Ref T13513. Introduce a formal server-side content state object so the whole state can be saved and restored to the drafts table, read from the request, etc.

Test Plan: Created and edited inlines. Reloaded drafts with edits. Submitted normal and editing comments. Grepped for affected symbols.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21275
2020-05-20 14:25:59 -07:00
epriestley
4b2a447003 Allow "has draft inlines?" queries to overheat
Summary:
Ref T13513. If your 10 most recently authored inlines have all been deleted, these queries can fail by overheating. This is silly and probably rarely happens outside of development.

For now, just let them overheat. This may create a false negative (incorrect "no draft" signal when the real condition is "drafts, but 10 most recent comments were deleted"). This could be sorted out later with a query mode like "executeAny()", perhaps.

Test Plan:
  - Created and deleted 10 inlines.
  - Submitted comments.
  - Before: overheating fatal during draft flag generation.
  - After: clean submission.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21274
2020-05-20 14:25:34 -07:00
epriestley
87bc30526b Make inline content "state-oriented", not "string-oriented"
Summary:
Ref T13513. Currently, all the inline code passes around strings to describe content. I plan to add background music, animation effects, etc., soon. To prepare for this change, make content a state object.

This does not change any user-visible behavior, it just prepares for content to become more complicated than a single string.

Test Plan: Created, edited, submitted, cancelled, etc., comments.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21273
2020-05-20 14:24:11 -07:00
epriestley
9d5b8bd14a Remove PHPMailer code which generates bogus "Message-ID" email headers
Summary:
See <https://discourse.phabricator-community.org/t/how-to-override-localhost-localdomain-in-email-message-id/3876/>.

Currently, Phabricator generates a "Message-ID" only in a subset of cases (roughly: when the message is first-in-thread and we expect the thread may have more than one message).

In cases where it does not generate a message ID, it expects the SMTP server to generate one for it. Servers will generally do this, and some ONLY do this (that is, they ignore IDs from Phabricator and replace them). Thus, several pieces of configuration control whether Phabricator attempts to generate a "Message-ID" at all.

The PHPMailer code has fallback behavior which generates a "<random>@localhost.localdomain" message ID. This is never desirable and ignores Phabricator-level configuration that Message IDs should not be generated.

For now, remove this code: it is never the desired behavior and sometimes explicitly contradicts the intent of configuration.

Possibly, a better change may be to make Phabricator always generate a message ID in cases where it isn't forbidden from doing so by configuration. However, that's a more complicated change and it's not clear if/when it would produce better behavior, so start here for now.

Test Plan: Confirmed by affected user (see linked thread).

Differential Revision: https://secure.phabricator.com/D21272
2020-05-19 11:38:58 -07:00
epriestley
4257b26abc Treat PHP7 "Throwable" exceptions like other unhandled "Exception" cases in the worker queue
Summary: See PHI1745. Under PHP7, errors raised as Throwable miss this "generic exception" logic and don't increment their failure count. Instead, treat any "Throwable" we don't recognize like any "Exception" we don't recognize.

Test Plan:
  - Under PHP7, caused a worker task to raise a Throwable (e.g., call to undefined method, see D21270).
  - Ran `bin/worker execute --id ...`.
  - Before: worker failed, but did not increment failure count.
  - After: worker fails and increments failure count as it would for other types of unknown error.

Differential Revision: https://secure.phabricator.com/D21271
2020-05-19 10:41:28 -07:00
epriestley
43a8d8763d Update out-of-date API calls when rendering diffs inline in email
Summary: See PHI1745. This callsite for "ChangesetParser" was not properly updated for recent changes.

Test Plan:
  - Set `metamta.differential.inline-patches` to 100.
  - Created a new revision with a small (<100 line) diff, with at least one reviewer.
  - Ran `bin/phd debug` and observed outbound mail queue with `bin/mail list-outbound`.
  - Before: fatal when trying to generate the inline changes for mail.
  - After: clean mail generation.

Differential Revision: https://secure.phabricator.com/D21270
2020-05-19 10:39:58 -07:00
epriestley
86d6abe9db Fix an issue where builds with no initiator failed to render in build plans
Summary: See PHI1743. If a build has no initiator PHID, the rendering pathway incorrectly tries to access a handle for it anyway.

Test Plan:
  - Set a build to have no initiator PHID.
  - Viewed the build plan for the build.
  - Before: fatal when trying to access the `null` handle.
  - After: clean build plan rendering.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Differential Revision: https://secure.phabricator.com/D21269
2020-05-19 09:46:18 -07:00
epriestley
770a5c8412 Fix "r" and "R" both replying with quote on inline comments
Summary: Ref T13513. The code which added "r" and "R" to the inline menu accidentally discarded the difference between the keystrokes.

Test Plan:
  - Clicked an inline, pressed "r", got new empty inline (previously: inline with quote).
  - Clicked an inline, pressed "R", got a new quoted inline.
  - Repeated steps with the menu items, got the expected behaviors.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21268
2020-05-19 09:28:36 -07:00
epriestley
6cf017d680 Fix an unusual issue with intradiff highlighting of files with uncommon end-of-file modifications
Summary:
Fixes T13539. See that task for discussion and a reproduction case.

This algorithm currently counts "\ No newline at end of file" lines as though they were normal source lines. This can cause offset issues in the rare case that a diff contains two of these lines (for each side of the file) and has changes between them (because the last line of the file was modified between the diffs).

Instead, don't count "\" as a display line.

Test Plan:
  - See T13539 and PHI1740.
  - Before: got fatals on the "wild" diff and the synthetic simplified version.
  - After: clean intradiff rendering in both cases.

Maniphest Tasks: T13539

Differential Revision: https://secure.phabricator.com/D21267
2020-05-19 09:06:19 -07:00
epriestley
0c51885cf7 Survive importing Git commits with no commit message and/or no author
Summary:
Ref T13538. See PHI1739. Synthetic Git commits with no author and/or no commit message currently extract `null` and then fail to parse.

Ideally, we would carefully distinguish between `null` and empty string. In practice, that requires significant schema changes (these columns are non-nullable and have indexing requirements) and these cases are degenerate. These commits are challenging to build and can not normally be constructed with `git commit`.

At least for now, merge the `null` cases into the empty string cases so we can survive import.

Test Plan:
  - Constructed a commit with no author and no commit message using the approach described in T13538; pushed and parsed it.
  - Before: fatals during identity selection and storing the commit message (both roughly NULL inserts into non-null columns).
  - After: clean import.

This produces a less-than-ideal UI in Diffusion, but it doesn't break anything:

{F7492094}

Maniphest Tasks: T13538

Differential Revision: https://secure.phabricator.com/D21266
2020-05-18 20:02:21 -07:00
epriestley
f86d822a37 Update MySQL schema inspection code for deprecation of integer display widths
Summary:
Fixes T13536. See that task for discussion.

Older versions of MySQL (roughly, prior to 8.0.19) emit "int(10)" types. Newer versions emit "int" types. Accept these as equivalent.

Test Plan: Ran `bin/storage upgrade --force` against MySQL 8.0.11 and 8.0.20. Got clean adjustment lists on both versions.

Maniphest Tasks: T13536

Differential Revision: https://secure.phabricator.com/D21265
2020-05-18 12:10:31 -07:00
epriestley
7b0db3eb54 Fix an email address validation UI feedback issue when creating new users
Summary: On the "New User" web workflow, if you use an invalid email address, you get a failure with an empty message.

Test Plan:
  - Before: Tried to create a new user with address "asdf". Got no specific guidance.
  - After: Got specific guidance about email address formatting and length.

Differential Revision: https://secure.phabricator.com/D21264
2020-05-18 07:35:02 -07:00
epriestley
93b08f0e83 Fix an out-of-order access issue with inlines
Summary: Ref T13513. On `secure`, I caught an issue where inlines may be accessed directly before they're constructed. Instead, access them through the relevant accessor.

Test Plan: Will deploy.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21263
2020-05-15 13:55:58 -07:00
epriestley
e959f93489 Use a more consistent inline highlighting style with fewer redraws
Summary:
Ref T13513. The on-hover-inline reticle has switched over to have cell-based behavior. Switch the on-hover-line-number reticle to use the same behavior.

Also, clean up the dirty/redraw loop slightly: we no longer need to dirty on resize, and we don't need to redraw if the range isn't actually dirty.

Test Plan: Highlighted lines and line ranges. Hovered over inlines.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21262
2020-05-15 12:44:40 -07:00
epriestley
c666cb9f0b Reduce the frequency of DOM scans to rebuild inlines when scrolling revisions
Summary:
Ref T13513. See PHI1734, which raises a concern about the performance of large revisions near the 100-change threshold.

Currently, `getInlines()` is called whenever the scroll position transitions between two changesets, and it performs a relatively complicated DOM scan to lift inlines out of the document.

This shows up as taking a small but nontrivial amount of time in Firefox profiles and should be safely memoizable.

Test Plan:
  - Under Firefox profiling, scrolled through a large revision.
  - Before change: `getInlines()` appeared as the highest-cost thing we're explicitly doing on profiles.
  - After change: `getInlines()` was no longer meaningfully represented on profiles.
  - Created inlines, edited inlines, etc. Didn't identify any broken behavior.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21261
2020-05-15 09:37:41 -07:00
epriestley
b1351d0fdb Remove code which overrides "diffusion.ssh-username" when instanced
Summary:
Ref T13529. Now that instances can be renamed, an instance may have multiple valid SSH usernames and the preferred SSH username may not be the intenal instance name.

`PhacilitySiteSource` should already always set `diffusion.ssh-username` correctly, to the current preferred SSH username (which may be "new-name" after a rename from "old-name"), so we should never be able to reach this code without an accurate `diffusion.ssh-username` value available.

The code to resolve names into instances also already works for both "ssh old-name@..." and "ssh new-name@...".

So I believe this code has no beneficial effects and only causes harm: it may force us to return "old-name" when falling through would correctly return "new-name".

Test Plan:
  - Previously: renamed an instance, then SSH'd to it using both the old and new names. Both work.
  - Previously: verified that `diffusion.ssh-username` is set correctly after a rename.
  - Verified that Diffusion "Clone" UI now shows "new-name" after an instance rename.
  - The real question here is: does this break something I'm not thinking of? And the change probably has to go to production to answer that.

Maniphest Tasks: T13529

Differential Revision: https://secure.phabricator.com/D21259
2020-05-15 07:45:06 -07:00
epriestley
3ee6b5393c Improve offset/range inline behavior for rich diffs and unified diffs
Summary:
Ref T13513. The way I'm highlighting lines won't work for Jupyter notebooks or other complex content blocks, and I don't see an obvious way to make it work that's reasonably robust.

However, we can just ignore the range behavior for complex content and treat the entire block as selected. This isn't quite as fancy as the source behavior, but pretty good.

Also, adjust unified diff behavior to work correctly with highlighting and range selection.

Test Plan:
  - Used range selection in a Jupyter notebook, got reasonable behavior (range is treated as "entire block").
  - Used range selection in a unified diff, got equivalent behavior to 2-up diffs.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21257
2020-05-14 16:02:32 -07:00
epriestley
fbd57ad832 Give selected inline comments are more obvious selected state
Summary:
Ref T13513. Give selected inlines a selection state and visual cues which are similar to the changeset selection state.

Also fix a couple of minor issues with select interactions and offset comments.

Test Plan: Selected inlines, saw obvious visual cues.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21256
2020-05-14 14:35:55 -07:00
epriestley
b021da71a5 When users click headers to select diff UI elements, don't eat the events
Summary: Ref T13513. Currently, clicking inline or changeset headers eats the click events. This doesn't serve any clear purpose, and means these clicks do not clear text selections from the document, which is unusual.

Test Plan:
  - Selected some text in a diff.
  - Clicked a changeset header to select it.
  - Before patch: text selection and context menu were retained.
  - After patch: text selection was cleared and context menu was dismissed.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21255
2020-05-14 14:34:38 -07:00
epriestley
42f1a95a12 Fix a flash of document selection when "oncopy" and "inline on range" behaviors interact
Summary:
Ref T13513. In Safari, do this:

  - view a 2-up diff with content on both sides;
  - select more than one line on the right side; and
  - use your mouse to click "New Inline Comment" in the context menu that pops up.

The mousedown event for the "New Inline Comment" click removes the "copy selection" behavior and creates a flash where both sides of the diff are selected.

This doesn't happen with (most) normal clicks because mouse down on a non-grabbable element clears the document selection.

To avoid this, don't reset the copy selection behavior if the user mouses down on an "<a />". This might not be robust, but seems simple and plausible as a fix.

Test Plan:
  - See above.
  - Before patch: flash of overbroad selection when clicking "New Inline Comment".
  - After patch: no selection flash.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21254
2020-05-14 14:29:46 -07:00
epriestley
f45519c060 When cancelling an inline comment edit, exit the edit state after the response arrives
Summary: Ref T13513. This fixes a bug where clicking a line number, then clicking "Cancel" causes the paths panel to briefly update with an extra inline comment counted on the file.

Test Plan:
  - Clicked a line number.
  - Typed some text.
  - Clicked "Cancel".
  - Before patch: paths panel flashes "1".
  - After patch: paths panel stays stable.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21253
2020-05-14 14:28:20 -07:00
epriestley
cfb5de6fa7 Distinguish more carefully between "null" inline offsets and "0" inline offsets
Summary:
Ref T13513. Currently, when creating an inline by selecting a line range, slightly careless handling leads to an inline with "0" offsets (by passing "undefined" to the server). This causes the block to highlight every line except the last one as fully bright, which is incorrect.

An inline with "0" offsets and an inline with no offsets are different. Be more careful about passing offsets around and rendering them.

Test Plan:
  - Used the line numbers to add an inline to lines 4-8 of a change.
  - Hovered the inline.
  - Saw all four lines marked as "dull"-highlighted (previously: three bright lines, one dull line).

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21252
2020-05-14 14:26:54 -07:00
epriestley
2f5398796e Store inline comment offset information and show it when highlighting comments
Summary:
Ref T13513. When a user selects a text range and uses "New Inline Comment" to create a comment directly from a range, store the offset information alongside the comment.

When hovering the comment, highlight the original range.

Test Plan: {F7480926, size=full}

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21250
2020-05-13 17:21:53 -07:00
epriestley
ebef22ccc1 Improve select-to-comment behavior in Firefox and on unified diffs
Summary:
Ref T13513.

  - Firefox represents multiple selected rows as a discontinuous range. Accommodate this.
  - Unified diffs don't have a "copy" marker. Do something sort-of-reasonable for them.

Test Plan:
  - Selected multiple lines of content in Firefox, got an option to add a comment.
  - Selected content in unified mode, got an option to add a comment.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21249
2020-05-13 17:19:53 -07:00
epriestley
42378ea393 Allow users to create inline comments by directly selecting text directly
Summary: Ref T13513. Support direct text selection for inlines. This is currently just an alternate way to get to the same place as using line numbers, but can preserve offset/range information in the future.

Test Plan:
  - Selected some text, hit "c", clicked "New Inline Comment", got sensible comments on both sides of a diff in Safari, Chrome, and (with limitations) Firefox.
  - Caveats: no unified support, doesn't work across lines in Firefox.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21248
2020-05-13 17:15:18 -07:00
epriestley
c063e0e5ec Add "View Raw Remarkup" to inline comments
Summary: Ref T13513. Ref T11401. Support viewing raw remarkup for inlines.

Test Plan: Viewed raw remarkup on inlines.

Maniphest Tasks: T13513, T11401

Differential Revision: https://secure.phabricator.com/D21246
2020-05-13 17:14:20 -07:00
epriestley
419b7ceebb Move inline comment actions into a dropdown menu
Summary: Ref T11401. Ref T13513. This paves the way for more comment actions, particularly an edit-after-submit action.

Test Plan: Took all actions from menus, via mouse and via keyboard (where applicable).

Maniphest Tasks: T13513, T11401

Differential Revision: https://secure.phabricator.com/D21244
2020-05-13 17:13:18 -07:00
epriestley
1da54837ea Improve line breaking behavior in Firefox and Chrome under complex conditions
Summary: See <https://github.com/phacility/phabricator/pull/854>. In some situations, `line-break: anywhere` produces better behavior than `word-break: break-all`. It never appears to produce worse behavior.

Test Plan:
  - Break behavior changes if a line contains "<span />" elements caused by syntax highlighting. This CSS adjustment only appears to apply to text with internal "<span />" elements.
  - This specifically impacts certain internal breakpoints adjacent to punctuation, so the test case is highly specific. Generic test cases with latin word characters do not evidence any behavioral changes.
  - This change appears to have no impact on Safari, which uses the better behavior in all cases.
  - Before Patch: In Firefox and Chrome, this specific change breaks awkwardly. There is more room for text to fit on the broken line:

Firefox

{F7480567}

Chrome

{F7480568}

  - After Patch: Firefox and Chrome break the line better. Here's Firefox:

{F7480569}

  - Additional context:

Safari Behavior (Unchanged)

{F7480570}

Chrome with no highlighting (desirable behavior). Firefox does the same thing.

{F7480571}

Also tested other cases, which seem never-worse in any browser.

{F7480574}

Differential Revision: https://secure.phabricator.com/D21247
2020-05-13 11:54:42 -07:00
epriestley
3dea92081b Fix an issue where passphrase-protected private keys were stored without discarding passphrases
Summary:
Ref T13454. See <https://discourse.phabricator-community.org/t/newly-created-ssh-private-keys-with-passphrase-not-working-anymore/3883>.

After changes to distinguish between invalid and passphrase-protected keys, SSH private key management code incorrectly uses "-y ..." ("print public key") when it means "-p ..." ("modify input file, removing passphrase"). This results in the command having no effect, and Passphrase stores the raw input credential, not the stripped version.

We can't recover the keys because we don't store the passphrase, so no migration here is really possible. (We could add more code to detect this case, but it's presumably rare.)

Also, correct the behavior of the "Show Public Key" action: this is available for users who can see the credential and does not require edit permission.

Test Plan:
  - Created a new credential with a passphrase, then showed the public key.

Maniphest Tasks: T13006, T13454

Differential Revision: https://secure.phabricator.com/D21245
2020-05-13 08:14:37 -07:00
epriestley
df139f044b Render proper "Show Context" links in DocumentEngine diffs, not just bullets
Summary:
Ref T13513. Currently, viewing a Jupyter document, hidden context just gets a plain "* * *" facade with no way to expand it.

Support click-to-expand, like source changes.

Test Plan:
  - Clicked to expand various Jupyter diffs.
  - Clicked to expand normal source changes.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21243
2020-05-12 16:09:22 -07:00
epriestley
e8109e4a92 When an inline was left on a rendered DocumentEngine document, don't include an email context patch
Summary:
Ref T13513. If you leave an inline on line 20 of a Jupyter document, we currently render context around *raw* line 20, which is inevitably some unrelated piece of JSON.

Instead, drop this context. (Ideal behavior would be to render context around Jupyter block 20, but that's a whole lot of work.)

Test Plan:
  - On Jupyter changes and normal source changes, made and submitted inline comments, then viewed text and HTML mail.
  - Saw no context on Jupyter comments (instead of bad context), and unchanged behavior (useful context) on normal source changes.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21242
2020-05-12 14:34:33 -07:00
epriestley
acc1fa1655 Make "View as Document Type..." only show valid options
Summary:
Ref T13513. Currently, "View as Document Type..." lists every available engine.

This is hard to get completely right because we can't always rebuild the document ref accurately in the endpoint, but try harder to fake something reasonable.

Test Plan: Used "View as Document Type..." on Jupyter notebooks, was given "Jupyter" and "Source" as options.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21241
2020-05-12 14:25:37 -07:00
epriestley
0cca40db3b When creating an inline, save the current document engine
Summary:
Ref T13513. As part of inline metadata, save the document engine the change is being rendered with.

This will allow other parts of the UI to detect that an inline was created on a Jupyter notebook but is being rendered on raw source, or whatever else.

The immediate goal is to fix nonsensical inline snippet rendering in email on Jupyter notebooks.

Test Plan:
  - Created inlines and replies on normal soure code, saw no document engine annotated in the database.
  - Created inlines and replies on a Jupyter notebook rendered in Jupyter mode, saw "jupyter" annotations in the database.
  - Swapped document engines between Jupyter and Source, etc.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21240
2020-05-12 14:25:09 -07:00
epriestley
6dc20d1e2e Fix an issue where storage inlines are fed to InlineAdjustmentEngine
Summary:
Ref T13513. If an intradiff has at least one unchanged file ("hasSameEffectAs()") or more than 100 files ("Large Change"), we hit this block and don't upcast storage inlines to runtime inlines. I missed this in testing.

Add the conversion step.

Test Plan: Viewed an intradiff with at least one unchanged file and at least one inline comment, saw correct rendering instead of fatal.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21239
2020-05-08 16:55:54 -07:00
epriestley
e7ebd5d9d1 Make "Delete" from inline comment previews function correctly while editing comments
Summary: Ref T13513. Currently, if you're editing a comment, "delete" doesn't put the comment into the correct state. This action is normally only reachable from comment previews, since an editing inline has no "delete" button.

Test Plan:
  - Started editing an inline, clicked "Delete", got a deletion.
  - Created an inline, typed text,
  - Deleted a normal comment via preview.
  - Deleted a normal comment via the on-inline action.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21238
2020-05-08 08:54:48 -07:00
epriestley
b804e8cffa Make "View" from inline comment previews correctly jump to "isEditing" inlines
Summary:
Ref T13513. Currently, clicking "View" from the inline comment preview (below the "add comment" area at the bottom of the page) only works if the inline isn't being edited.

Update this behavior so it works on inlines in either "Viewing" or "Editing" states.

Test Plan:
  - Clicked "View" on a normal inline, got jumped/selected.
  - Clicked "View" on an editing inline, got jumped/selected.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21237
2020-05-08 08:52:42 -07:00
epriestley
24ba66f106 Persist "Show Changeset" and improve path text selection
Summary:
Ref T13513. Currently:

  - If you click the "Show Changeset" button, your state change doesn't actually get saved on the server.
  - It's hard to select a changeset path name for copy/paste because the "highlight the header" code tends to eat the event.

Instead: persist the former event; make the actual path text not be part of the highlight hitbox.

Test Plan:
  - Clicked "Show Changeset", reloaded, saw changeset visibility persisted.
  - Selected changeset path text without issues.
  - Clicked non-text header area to select/deselect changesets.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21236
2020-05-08 06:59:10 -07:00
epriestley
fa2d30ee36 Lift inline comment draft behaviors to "InlineController"
Summary:
Ref T13513. Currently, if you:

  - click a line to create an inline;
  - type some text;
  - wait a moment; and
  - close the page.

...you don't get an "Unsubmitted Draft" marker in the revision list.

Lift all the draft behavior to "InlineController" and make saving a draft dirty the overall container draft state.

Test Plan:
  - Took the steps described above, got a draft state marker.
  - Created, edited, submitted, etc., inlines in Diffusion and Differential.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21235
2020-05-08 06:52:29 -07:00
epriestley
94a95efa05 Replace "loadUnsubmittedInlineComments()" with a modern "DiffQuery"
Summary: Ref T13513. All queries now go through a reasonably minimal set of pathways and should have consistent behavior.

Test Plan:
- Loaded a revision with inlines.
- Created a new empty inline, reloaded page, saw it vanish.
- Created a new empty inline, typed draft text, did not save, reloaded page, saw draft present.
- Created a new empty inline, typed draft text. Submitted feedback, got prompt, answered "Y", saw draft text submit.
- Created a new empty inline, typed draft text, scrolled down to bottom of page, typed non-draft text, saw preview include draft text.
- Marked and submitted "Done".
- Used hide/show on inlines, verified state persisted.
- Did much of the same stuff in Diffusion, where it all works the same way (except: there's no prompt when submitting draft is-editing inlines).

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21234
2020-05-07 16:11:02 -07:00
epriestley
79107574a7 Remove "DifferentialInlineCommentQuery"
Summary: Ref T13513. Replaces "DifferentialInlineCommentQuery" with the similar but more modern "DifferentialDiffInlineCommentQuery".

Test Plan: Viewed comments in timeline, changesets. Created, edited, and submitted comments. Hid and un-hid comments, reloading (saw state preserved).

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21233
2020-05-07 16:07:55 -07:00
epriestley
983d77848b Move the "Inline List" view to "DiffInlineCommentQuery"
Summary: Ref T13513. Continue removing usage sites for the obsolete "DifferentialInlineCommentQuery".

Test Plan: Viewed the inline list in Differential, saw sensible inlines.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21232
2020-05-07 16:07:23 -07:00
epriestley
af5b94b234 Lift most "InlineController" querying to the base class
Summary: Ref T13513. Move querying to "DiffInlineCommentQuery" classes and lift them into the base Controller.

Test Plan: In Differential and Diffusion, created, edited, and submitted inline comments.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21231
2020-05-07 16:04:51 -07:00
epriestley
949b9163d0 Replace remaining pseudo-query methods on AuditInlineComment
Summary: Ref T13513. Another step closer to the light.

Test Plan: Created, edited, deleted, replied to, and submitted inline comments in Diffusion.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21230
2020-05-07 16:04:27 -07:00
epriestley
6c59db20a3 Replace "loadDraftComments()" with a Query
Summary: Ref T13513. Take another step toward coherent query pathways for inlines.

Test Plan:
  - Created, previewed, and submitted inlines in Diffusion.
  - Got a (mostly) appropriate draft state.
  - Got proper comment peristence, preview behavior, and submission behavior.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21229
2020-05-07 16:02:35 -07:00
epriestley
d0593a5a78 Replace "loadDraftAndPublishedComments()" with a Query
Summary: Ref T13513. Continue marching toward coherent query pathways for all access to inline comments.

Test Plan:
  - Viewed a commit and a path within that commit, as a user with unpublished inlines and a different user.
  - Saw appropriate inlines in all cases (published inlines, plus undeleted unpublished inlines authored by the current viewer).
  - Grepped for "loadDraftAndPublishedComments()".

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21228
2020-05-07 16:02:10 -07:00
epriestley
c1f1345cc0 Make InlineCommentQueries more robust/consistent
Summary:
Ref T13513. Improve consistency and robustness of the "InlineComment" queries.

The only real change here is that these queries now implicitly add a clause for selecting inlines ("pathID IS NULL" or "changesetID IS NULL").

Test Plan: Browed, created, edited, and submitted inlines.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21227
2020-05-07 16:00:28 -07:00
epriestley
1656a2ff08 Allow inline comment storage objects to generate their own runtime objects
Summary:
Ref T13513. Currently, inline storage objects ("TransactionComment") can't directly generate a runtime object ("InlineComment").

Allow this transformation to be performed in a genric way so clunky code which does it per-object-type can be removed, lifted, or simplified.

Simplify an especially gross callsite in preview code.

Test Plan: Previewed inline comments.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21226
2020-05-07 15:57:49 -07:00
epriestley
397648855f Make the "attach_inlines" parameter to "differential.createcomment" a no-op
Summary: Ref T13513. See that task for some discussion. This prepares to lift "loadUnsubmittedInlineComments(...)" into shared code.

Test Plan: Grepped for callers, found none in the upstream. This is a backward compatibilty break. See T13513.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21225
2020-05-07 15:55:37 -07:00
epriestley
0067f1a521 Remove the obsolete "DiffusionInlineCommentPreviewController"
Summary: Ref T13513. This controller was obsoleted by EditEngine and appears unreachable without explicitly typing the URL.

Test Plan:
  - Grepped for the route, didn't find any hits.
  - Deleted the controller, successfully previewed comments in Diffusion.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21224
2020-05-06 10:13:28 -07:00
epriestley
a590db28b2 Fix an issue where non-ID changeset state keys were used as changeset IDs
Summary:
Ref T13519. This is a little fuzzy, but I think the workflow here is:

  - View an intradiff, generating an ephemeral comparison changeset with no changeset ID. This produces a state key of "*".
  - Apply "hidden" state changes to the changeset.
  - View some other intradiff and/or diff view.
  - The code attempts to use "*" as a changset ID?

I'm not entirely sure this is accurate; this was observed in production and I couldn't get a clean reproduction case locally.

Optimistically, try making changeset IDs explicit rather than relying on state keys to be "usually changeset-ID-like".

Test Plan: Used "hidden" locally across multiple intradiffs, but I wasn't cleanly able to reproduce the initial issue.

Maniphest Tasks: T13519

Differential Revision: https://secure.phabricator.com/D21223
2020-05-04 16:05:05 -07:00
epriestley
6b69102990 Fix a JS issue when the anchor element on a page has no container
Summary: See D21213. If there's no matching element, `findAbove()` throws. Handle these cases correctly.

Test Plan: Visited `#toc` on a revision, no longer saw a JS error.

Differential Revision: https://secure.phabricator.com/D21222
2020-05-04 15:57:31 -07:00
epriestley
6430d6d638 Fix an intradiff error when the newer changeset does not exist
Summary: Ref T13523. If a file hasn't been touched in the newer changeset, we can currently hit an error in the interdiff.

Test Plan:
  - Touched "moo.txt" in Diff 1.
  - Reverted the changes to "moo.txt" in Diff 2.
  - Diffed 2 vs 1.
  - Before patch: fatal (call to getFilename() on null).
  - After patch: clean interdiff.

Maniphest Tasks: T13523

Differential Revision: https://secure.phabricator.com/D21220
2020-05-04 15:42:34 -07:00
epriestley
07e160bde1 When cancelling an unsaved editing inline after a reload, don't cancel into an empty state
Summary:
Ref T13513. Overloading "original text" to get "edit-on-load" comments into the right state has some undesirable side effects.

Instead, provide the text when the editor opens. This fixes a cancel interaction.

Test Plan:
  - Create an inline, type text, don't save.
  - Reload page.
  - Cancel.
  - Before: cancelled into empty state.
  - After: cancelled into deleted+undo state.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21219
2020-05-04 15:20:31 -07:00
epriestley
7fa47408a3 When a user submits "isEditing" inlines and chooses to publish them, publish their current draft state as-shown
Summary: Ref T13513. When users choose to publish inlines, we want to publish the visible text, not the last "checkpointed" state.

Test Plan:
  - Created an inline ("AAA").
  - Edited it into "BBB", did not save.
  - Submitted.
  - Confirmed that I want to publish the unsaved inline.
  - Saw "BBB" publish.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21218
2020-05-04 15:16:18 -07:00
epriestley
3a76248071 When loading a page with inlines, don't select/focus inlines which we immediately upgrade to "editing"
Summary:
Ref T13513. This is a bit clumsy, but the cleanest way to implement "isEditing" inlines today is to send them down as normal inlines and then simulate clicking "edit" on them.

When we do, don't focus the resulting editor: focusing it makes the page scroll around and highlight things in essentially random order as the editors load in.

Test Plan: Reloaded a page with some open editors, wasn't scrolled to them.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21217
2020-05-04 15:15:27 -07:00
epriestley
fe501bd7f7 Save drafts for inline comments currently being edited
Summary:
Ref T13513. As users type text into inline comments, save the comment state as a draft on the server.

This has some rough edges, particularly around previews, but mostly works. See T13513 for notes.

Test Plan: Started an inline, typed some text, waited a second, reloaded the page, saw an editing inline with the saved text.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21216
2020-05-04 13:19:42 -07:00
epriestley
27b7ba814a Don't consider empty inlines when considering whether a revision has draft comments or not
Summary: Ref T13513. When computing whether a revision has draft comments or not, ignore empty inlines.

Test Plan: Added empty inlines to a revision, no longer saw a yellow "draft" bubble in the list UI.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21215
2020-05-04 13:17:20 -07:00
epriestley
9307f57747 When rendering changesets, discard empty draft inline comments
Summary: Ref T13513. When you load a changeset, discard all empty inlines. This is likely a more desirable behavior than keeping empty editors around, even though the rest of the pipeline generally handles them fairly well now.

Test Plan:
  - Started an inline, didn't type any text or save, reloaded page.
    - Before: page restores empty editor in the same place.
    - After: we just discard this likely-pointless empty inline.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21214
2020-05-04 13:16:42 -07:00
epriestley
63bfad0ff4 Refine unusual inline comment client interactions
Summary: Ref T13513. Refine some inline behaviors, see test plan.

Test Plan:
  - Edit a comment ("A"), type text ("AB"), cancel, edit.
    - Old behavior: edit and undo states (wrong, and undo does not function).
    - New behavior: edit state only.
  - Edit a comment ("A"), type text ("AB"), cancel. Undo ("AB"), cancel. Edit.
    - Old behavior: "AB" (wrong: you never submitted this text).
    - New behavior: "A".
  - Create a comment, type text, cancel.
    - Old behavior: counter appears in filetree (wrong, comment is undo-able but should not be counted).
    - New behavior: no counter.
  - Cancel editing an empty comment with no text.
    - Old behavior: Something buggy -- undo, I think?
    - New behavior: it just vanishes (correct behavior).

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21212
2020-05-04 13:15:01 -07:00
epriestley
f5ef341c9e Don't publish "empty" inline comments
Summary:
Ref T13513. Currently, if you start an inline and then submit overall comments, we publish an empty inline. This is literally faithful to what you did, but almost certainly not the intent.

Instead, simply ignore empty inlines at publishing time (and ignore "done" state changes for those comments).

We could delete them outright, but if we do, they'll break if you have another window open with the empty inline (since the stored comment won't exist anymore). At least for now, leave them in place.

Test Plan: Created empty inlines, submitted comments, no longer saw them publish.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21211
2020-05-04 13:14:04 -07:00
epriestley
67da18e374 When users submit "editing" inlines, warn them that their inlines will be saved
Summary: Ref T13513. This slightly expands the existing-but-hacky "warning" workflow to cover both "mentions on draft" and "submitting inlines being edited".

Test Plan:
  - Submitted changes to a revision with mentions on a draft, inlines being edited, both, and neither.
  - Got sensible warnings in the cases where warnings were appropriate.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21191
2020-05-04 13:13:15 -07:00
epriestley
468aabd4ef When draft inline comments are submitted, disengage the editor
Summary:
Ref T13513. If you submit top-level comments while an inline comment editor is open, kick the comment out of the editing state.

(An improvement to this behavior would be to warn the user that we're going to do this first, but this is currently less straightforward.)

Test Plan:
  - Clicked a line number to create an inline.
  - Type text, save, click edit.
  - (Optional: reload page.)
  - Save changes overall using the form at the bottom of the page.
  - Outcome: published inline is no longer in an "editing" state.

Weirdness:

  - If you click a line number (and, optionally, type text), then submit without using "Save", the server-side version of the inline has no content.
    - This gives you a no-effect warning. Instead, these inlines should probably just be marked as deleted somewhere in the pipeline.
  - This saves the last "Saved" copy of the inline. That's (probably?) desired, but somewhat destructive without a warning.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21188
2020-05-04 13:12:04 -07:00
epriestley
b2ce0844b6 When a user clicks "Cancel" on an inline comment to leave the "Editing" state, save the state change
Summary:
Ref T13513. Now that the "currently being edited" state of inlines is saved on the server side, clear the flag when the user clicks "Cancel" to leave the "editing" state on the client.

This also serves to delete empty comments.

Test Plan:
  - Clicked a line number to create a new comment. Then:
    - Clicked "Cancel". Reloaded page, saw no more comment.
    - Typed text, saved. Reloaded page, saw non-editing draft. Clicked "Edit", reloaded page, saw editing draft. Clicked "Cancel", reloaded page, saw non-editing draft.
    - Typed text, saved. Clicked "Edit", deleted text, saved. Reloaded page, saw no more comment.

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21187
2020-05-04 13:11:23 -07:00
epriestley
b48a22bf50 Make "editing" state persistent for inline comments
Summary:
Ref T13513. This is mostly an infrastructure cleanup change.

In a perfect world, this would be a series of several changes, but they're tightly interconnected and don't have an obvious clean, nontrivial partition (or, at least, I don't see one). Followup changes will exercise this code repeatedly and all of these individual mutations are "obviously good", so I'm not too worried about the breadth of this change.

---

Inline comments are stored as transaction comments in the `PhabricatorAuditTransactionComment` and `DifferentialTransactionComment` classes.

On top of these two storage classes sit `PhabricatorAuditInlineComment` and `DifferentialInlineComment`. Historically, these were an indirection layer over significantly different storage classes, but nowadays both storage classes look pretty similar and most of the logic is actually the same. Prior to this change, these two classes were about 80% copy/pastes of one another.

Part of the reason they're so copy/pastey is that they implement a parent `Interface`. They are the only classes which implement this interface, and the interface does not provide any correctness guarantees (the storage objects are not actually constrained by it).

To simplify this:

  - Make `PhabricatorInlineCommentInterface` an abstract base class instead.
  - Lift as much code out of the `Audit` and `Differential` subclasses as possible.
  - Delete methods which no longer have callers, or have only trivial callers.

---

Inline comments have two `View` rendering classes, `DetailView` and `EditView`. They share very little code.

Partly, this is because `EditView` does not take an `$inline` object. Historically, it needed to be able to operate on inlines that did not have an ID yet, and even further back in history this was probably just an outgrowth of a simple `<form />`.

These classes can be significantly simplified by passing an `$inline` to the `EditView`, instead of individually setting all the properties on the `View` itself. This allows the `DetailView` and `EditView` classes to share a lot of code.

The `EditView` can not fully render its content. Move the content rendering code into the view.

---

Prior to this change, some operations need to work on inlines that don't have an inline ID yet (we assign an ID the first time you "Save" a comment). Since "editing" comments will now be saved, we can instead create a row immediately.

This means that all the inline code can always rely on having a valid ID to work with, even if that ID corresponds to an empty, draft, "isEditing" comment. This simplifies more code in `EditView` and allows the "create" and "reply" code to be merged in `PhabricatorInlineCommentController`.

---

Client-side inline events are currently handled through a mixture of `ChangesetList` listeners (good) and ad-hoc row-level listeners (less good). In particular, the "save", "cancel", and "undo" events are row-level. All other events are list-level.

Move all events to list-level. This is supported by all inlines now having an ID at all stages of their lifecycle.

This allows some of the client behavior to be simplified. It currently depends on binding complex ad-hoc dictionaries into event handlers in `_drawRows()`, but it seems like almost all of this code can be removed. In fact, no more than one row ever seems to be drawn, so this code can probably be simplified further.

---

Finally, save an "isEditing" state. When we rebuild a revision on the client, click the "edit" button if it's in this state. This is a little hacky, but simpler to get into a stable state, since the row layout of an inline depends on a "view row" followed by an "edit row".

Test Plan:
  - Created comments on either side of a diff.
  - Edited a comment, reloaded, saw edit stick.
  - Saved comments, reloaded, saw save stick.
  - Edited a comment, typed text, cancelled, "unedited" to get state back.
  - Created a comment, typed text, cancelled, "unedited" to get state back.
  - Deleted a comment, "undeleted" to get state back.

Weirdness / known issues:

  - Drafts don't autosave yet.
  - Fixed in D21187:
    - When you create an empty comment then reload, you get an empty editor. This is a bit silly.
    - "Cancel" does not save state, but should, once drafts autosave.
  - Mostly fixed in D21188:
    - "Editing" comments aren't handled specially by the overall submission flow.
    - "Editing" comments submitted in that state try to edit themselves again on load, which doesn't work.

Subscribers: jmeador

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21186
2020-05-04 13:10:30 -07:00
epriestley
5ff0ae7d48 Add generic "attributes" storage to inline comment tables
Summary: Ref T13513. This plans for "currently editing", character range comments, code suggestions, document engine tracking. And absolutely nothing else.

Test Plan:
  - Ran `bin/storage upgrade -f`, got a clean upgrade.
  - Created and submitted some inline comments; nothing exploded.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21184
2020-05-04 13:09:55 -07:00
epriestley
54ec566281 Restore highlighting when jumping to transactions using URI anchors
Summary:
At some point, the highlighting behavior for the timeline broke. When you follow a link to a particular timeline story, the story should be highlighted.

Prior to this change, the `<a />` tag itself highlights, but there's no associated CSS and it's too deep in the tree to do anything useful.

(Since this change is fairly straightforward, I gave up digging for the root cause before finding it.)

Test Plan:
  - Clicked a timeline story anchor, saw the story highlight.

Differential Revision: https://secure.phabricator.com/D21213
2020-05-04 10:04:04 -07:00
epriestley
17426a60f0 Fix an issue where text intradiff bodies may not render
Summary:
Ref T13523. In the caching layer, there's a tricky clause about filetypes that skips some body rendering behavior.

Provide file type information which at least has a better chance of representing all changes (e.g., an image file may be replaced with a text file, but this can not be represented by a single file type).

Formalize "hasSourceTextBody()", to mean the changeset parser should engage the change as source text.

Test Plan: Intradiffed text changes, saw the body render properly.

Maniphest Tasks: T13523

Differential Revision: https://secure.phabricator.com/D21210
2020-05-04 07:40:47 -07:00
epriestley
dade977307 Provide a hint about how to quote search terms containing literal colons
Summary: See <https://phabricator.wikimedia.org/T243483>. Provide a more direct path forward if users hit the "unknown function" error but are trying to search for a term with a colon in it.

Test Plan:
{F7414068}

{F7414067}

Differential Revision: https://secure.phabricator.com/D21209
2020-05-03 10:14:47 -07:00
epriestley
6f09edeb91 Fix an issue where the "%%%" parser could match too many lines in unterminated blocks
Summary: Fixes T13530. The block parser could match too many lines in an unterminated "%%%" literal block. Adjust the logic to stop doing this (and hopefully be a little easier to read).

Test Plan: Added a failing test, made it pass.

Maniphest Tasks: T13530

Differential Revision: https://secure.phabricator.com/D21208
2020-05-03 09:25:41 -07:00
epriestley
b2cfcda114 Provide detailed information about reviewer changes in "transaction.search"
Summary:
See PHI1722, which requests transaction details about reviewer changes.

This adds them; they're structured to be similar to "projects" and "subscribers" transactions and the "reviewers" attachment on revisions.

Test Plan: {F7410675}

Differential Revision: https://secure.phabricator.com/D21207
2020-05-01 14:18:12 -07:00
epriestley
b89e6c0fa9 Add "idea://" to the upstream editor whitelist
Summary: This supports the IntelliJ IDEA editor.

Test Plan:
  - Looked at the editor settings panel, saw "idea://".
  - Set my editor pattern to "idea://a?b".
  - (Did not actually install IntelliJ IDEA.)

Differential Revision: https://secure.phabricator.com/D21206
2020-05-01 12:56:35 -07:00
epriestley
1205070687 Use underlines instead of background color to show file moves/renames
Summary: Ref T13520. This is a style tweak that I think looks a little cleaner.

Test Plan: {F7410424}

Maniphest Tasks: T13520

Differential Revision: https://secure.phabricator.com/D21205
2020-05-01 12:23:59 -07:00
epriestley
eab561bb87 Add "uri" to the API results for File objects
Summary: Ref T13528. This supports "arc upload --browse ...".

Test Plan: Called "file.search", saw URIs in results.

Maniphest Tasks: T13528

Differential Revision: https://secure.phabricator.com/D21204
2020-05-01 09:11:59 -07:00
epriestley
fbbd2e35cb Make content more prominent in Files and move some details to the curtain
Summary: Ref T13528. Now that we're hinting users into Files, put the content first and move the detail panel under it. Move the most-useful details (author, size, dimensions) into the curtain.

Test Plan: {F7409925}

Maniphest Tasks: T13528

Differential Revision: https://secure.phabricator.com/D21201
2020-05-01 09:11:33 -07:00
epriestley
1edca1ee2a Put application curtain panels above extension curtain panels
Summary:
Ref T13528. The original rationale here was that it's easier to find items at the bottom of the curtain than somewhere in the middle, since they're in a more clearly predictable visual location.

This might be true in some sense, but user feedback about this has fairly consistently indicated that the layout is surprising. Try the other order. See also D20967 for some discussion.

In practice, this primarily moves "Author / Assigned" above other panel elements in Maniphest.

Test Plan: Looked at tasks, aw "Author / Assigned" above "Tags / Subscribers".

Maniphest Tasks: T13528

Differential Revision: https://secure.phabricator.com/D21200
2020-05-01 09:11:04 -07:00
epriestley
d6928a3c26 When creating a File storage object for a Paste, try to give it the same name as the Paste
Summary:
Ref T13528. Paste data is stored in files, but the files are always named "raw.txt".

Now that Paste provides a hint to use Files for "DocumentEngine" rendering, try to use the same name as the paste instead.

Test Plan:
  - Created a paste named "staggering-insight.ipynb".
  - Clicked "View as Jupyter Notebook" from Paste.
  - Saw a file named "staggering-insight.ipynb", not "raw.txt".
  - Created a paste with no name, saw a file named "raw-paste-data.txt" get created.

Maniphest Tasks: T13528

Differential Revision: https://secure.phabricator.com/D21197
2020-05-01 09:10:31 -07:00
epriestley
3c1f393c81 When a Paste has a useful alternative rendering in Files, provide a hint
Summary: Ref T13528. When a file in Paste (like a Jupyter notebook) has a good/useful document engine, provide a link to Files.

Test Plan: {F7409881}

Maniphest Tasks: T13528

Differential Revision: https://secure.phabricator.com/D21196
2020-05-01 09:09:42 -07:00
epriestley
65a2b5e219 Route hard-coded "/favicon.ico" requests to a favicon resource
Summary:
See PHI1719. User agents making hard-coded requests to "/favicon.ico" currently 404. This is a mild source of log noise, and we can reasonably route this request.

Limitations:

  - This only routes the "PlatformSite". Other sites (custom Phame blogs, third-party sites, Phurl redirectors) won't route here for now.
  - This returns a "Location:" redirect to the correct resource rather than icon data directly. This produces the right icon with the right caching behavior, and returning icon data directly is difficult in the general case. However, it won't perform/cache as well as a direct response would.

Test Plan:
  - Visted `/favicon.ico`.
  - Before: 404.
  - After: redirect to favicon.

Differential Revision: https://secure.phabricator.com/D21195
2020-05-01 05:23:00 -07:00
epriestley
304467feb2 Stabilize fatals when a build has a build plan the viewer can't see because of policy restrictions
Summary:
Ref T13526. Currently, if a build plan is restricted, viewers may fatal when trying to view related builds.

The old behavior allowed them to see the build even if they can not see the build plan. This is sort of incoherent, but try to stabilize things before fixing this.

Test Plan:
This is a muddy change.

  - Created a build with a build plan that Alice can't see.
  - As Alice, viewed the build page (restricted before, restricted after); the buildable page (fatal before, works after).
  - Also viewed a revision page (works before and after, but user-reported fatal).

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13526

Differential Revision: https://secure.phabricator.com/D21194
2020-04-30 07:57:23 -07:00
epriestley
186a12ef7f Replace nonexistent "withPHIDs()" in ChangesetQuery with "withIDs()"
Summary:
Ref T13519. See <https://discourse.phabricator-community.org/t/error-call-to-undefined-method-differentialchangesetquery-withphids/3816/>.

Changesets do not have PHIDs, and the Query has no "withPHIDs()" method. The keys in the viewstate storage are (usually) IDs.

Test Plan:
  - On a revision with Diff 1 and Diff 2 affecting the same file:
    - Viewed Diff 1.
    - Hid file A.
    - Viewed Diff 2.
  - Before patch: exception about call to "withPHIDs()", which does not exist for ChangesetQuery.
  - After patch: no exception. Also, file actually unhid, which is the correct behavior!

Maniphest Tasks: T13519

Differential Revision: https://secure.phabricator.com/D21189
2020-04-29 14:47:47 -07:00
epriestley
b648a85841 Fix an issue where the Maniphest burnup chart was trying to render a non-View object
Summary:
See PHI1714. This code is incorrectly rendering the chart panel twice, sort of, and passing a non-View object to rendering.

After D21044, this fatals by raising an exception in rendering.

Test Plan: Loaded page, no more exception.

Differential Revision: https://secure.phabricator.com/D21185
2020-04-29 05:06:03 -07:00
epriestley
f21f1d8ab9 Update the diff table of contents to use hierarchical views and edit distance renames
Summary:
Ref T13520. Generally, make the table of contents look and more like the paths panel:

  - Show a hierarchy, with compression for single-sibling children.
  - Use the same icons, instead of "M D" and "(img)" stuff.
  - Use EditDistanceMatrix to do a piece-by-piece diff of paths changes.
  - Show path changes within the path list.

I'm not entirely sold on this, but it was complicated to write and I've never heard the term "sunk cost fallacy". I think this is mostly a net improvement, but may need some adjustments and followup.

Test Plan: Viewed various changes in Differential and Diffusion, saw a more usable table of contents.

Maniphest Tasks: T13520

Differential Revision: https://secure.phabricator.com/D21183
2020-04-28 12:27:37 -07:00
epriestley
a7b2327c34 Fix an issue with "Auditors:" where an edge edit was used as a PHID list
Summary:
See <https://discourse.phabricator-community.org/t/runtimeexception-during-import-of-commit/3801>. When importing commits with "Auditors:", a raw transaction new value (with an edge edit map using a "+" key) may be passed as an unmentionable PHID list.

Instead, pass an actual PHID list.

Test Plan:
  - Pushed a commit with "Auditors: duck".
  - Ran daemons.
    - Before patch: umentionable PHID exception.
    - After patch: clean commit import.
  - Verified "duck" was added as an auditor.

Differential Revision: https://secure.phabricator.com/D21181
2020-04-28 05:49:03 -07:00
epriestley
befeb17f6f Improve the construction of synthetic "comparison/intradiff" changesets
Summary:
Ref T13523. Currently, when building a "comparison" changeset, metadata is taken from the left changeset. This is somewhat arbitrary.

This means that intradiffs of images don't work properly because the rendered changeset has only the left (usually "old") information.

Later, some of the code attempts to ignore the file data stored on the changeset and reconstruct the correct file data, which is how the result ends up not-completely-wrong.

Be more careful about building sensible-ish metadata, and then just use it directly later on. This fixes the "spooky" code referencing D955 + D6851.

There are some related issues, where "change type" and "file type" are selected arbitrarily and then used to determine whether the change has an "old/new" state or not (i.e., is the left side of the diff empty, since the change creates the file)?

In many cases, neither of the original changesets have a "change type" which will answer this question correctly. Separate this concept from "has state" from "change type", and make more of the code ask narrower questions about the specific conditions or states it cares about, rather than "change type".

Test Plan:
  - Created a revision with Diff 1, Diff 2, and Diff 3. Diff 1 takes an image from "null -> A". Diff 2 takes the same image from "null -> B". Diff 3 takes the same image from "A -> B'.
  - Intradiffed 1v2 and 1v3.
  - Before patch:
    - Left side usually missing, which is incorrect (should always be "A").
    - Change properties are a mess ("null -> image/png" for MIME type, e.g.)
    - Uninteresting/incorrect "unix:filemode" stuff.
  - After patch;
    - Left side shows state "A".
    - Change properties only show size changes (which is correct).

{F7402012}

Maniphest Tasks: T13523

Differential Revision: https://secure.phabricator.com/D21180
2020-04-28 05:09:22 -07:00
epriestley
5eaa0f24e7 Use "@" to silence "GC list" warnings from "apc_store()" and "apcu_store()"
Summary:
Fixes T13525. Since D21044, the intermittent GC list warnings are treated more severely and can become user-visible errors.

Silence them, since this seems to be the only realistic response in most versions of APC/APCu.

Test Plan: Will deploy.

Maniphest Tasks: T13525

Differential Revision: https://secure.phabricator.com/D21179
2020-04-28 04:13:37 -07:00
epriestley
aa20faeaa5 Fix an invalid index access for synthetic lint inline comments from Harbormaster
Summary:
Ref T13524. If a Harbormaster lint message has no line number (which is permitted), we try to access an invalid index here. This is an exception after D21044.

Treat comments with no line number as unchanged. These comments do not have "ghost" behavior and do not port across diffs.

Test Plan:
  - Used "harbormaster.sendmessage" to submit lint with no line number on a changeset.
  - Viewed changeset.
    - Before patch: "Undefined index: <null>" error.
    - After patch: Clean changeset with lint message.

{F7400072}

Maniphest Tasks: T13524

Differential Revision: https://secure.phabricator.com/D21178
2020-04-27 14:20:55 -07:00
epriestley
6617005365 Make Conduit "www-form-urlencoded" parsing of "true" and "false" case-insensitive
Summary:
See PHI1710. Python encodes `True` as `True` (with an uppercase "T") when building URLs.

We currently do not accept this as a "truthy" value, but it's reasonable and unambiguous. Accept "True", "TRUE", "tRuE", etc.

Test Plan: Made a cURL conduit call with "True" and "tRuE". Before patch: failure to decoded booleans; after patch: successful interpretation of "true" variations.

Differential Revision: https://secure.phabricator.com/D21177
2020-04-27 13:28:47 -07:00
epriestley
b5bed7b0fa Make omitting "value" from a transaction description an explicit error
Summary: See PHI1710. Until D21044, some transactions could omit "value" and apply correctly. This now throws an exception when accessing `$xaction['value']`. All transactions are expected to have a "value" key, so require it explicitly rather than implicitly.

Test Plan: Submitted a transaction with a "type" but no "value". After D21044, got a language-level exception. After this change, got an explicit exception.

Differential Revision: https://secure.phabricator.com/D21176
2020-04-27 13:16:00 -07:00
epriestley
604811bfc5 Fix a Diffusion issue where commits that do not show changesets would incorrectly try to render changesets
Summary:
See <https://discourse.phabricator-community.org/t/loading-certain-svn-commits-cause-unhandled-exception/3795/>.

Commits with no changesets (for example, deleted commits) don't generate a "$changesets".

Test Plan: Viewed a commit with no changesets. Before change: exception. After change: saw unusual commit state.

Differential Revision: https://secure.phabricator.com/D21175
2020-04-27 10:36:09 -07:00
epriestley
c12db28251 For changesets that affect binaries, use the new binary file content hash as an effect hash
Summary: Ref T13522. When changesets update an image, we currently compute no effect hash. A content hash of the image (or other binary file) is a reasonable effect hash, and enalbes effect-hash-based behavior, including hiding files in intradiffs.

Test Plan:
  - Created a revision affecting `cat.png` and `quack.txt` (currently, there must be 2+ changesets to trigger the hide logic).
  - Updated it with the exact same changes.
  - Viewed revision:
    - Saw the image renderered in the interdiff.
  - Applied patch.
  - Ran `bin/differential rebuild-changesets ...`.
  - Viewed revision:
    - Saw both changesets collapse as unchanged.

Maniphest Tasks: T13522

Differential Revision: https://secure.phabricator.com/D21174
2020-04-27 08:34:55 -07:00
epriestley
b0c295e545 Fix some PHP 7.4 array index access issues
Summary: Ref T13518. See <https://discourse.phabricator-community.org/t/more-exceptions-when-viewing-diffs/3789/>. Under PHP 7.4, accessing an array index of values like `false` and `null` is no longer valid. This is great, but we occasionally do it.

Test Plan:
  - Upgraded to PHP 7.4.
  - Loaded revisions with added/changed lines, inlines, and Asana support configured.
  - Before patch: saw various fatals around accessing indexes of booleans and nulls.
  - After patch: clean revision.

Maniphest Tasks: T13518

Differential Revision: https://secure.phabricator.com/D21172
2020-04-26 08:35:06 -07:00
epriestley
7355bb7f29 Skip "null" lines when constructing raw documents for DocumentEngine rendering
Summary: See <https://discourse.phabricator-community.org/t/more-exceptions-when-viewing-diffs/3789>. This is a similar issue with the same datastructure.

Test Plan: Works correctly under PHP 7.3, but this may not be the end of things.

Differential Revision: https://secure.phabricator.com/D21171
2020-04-25 17:34:45 -07:00
epriestley
a226d74133 Use "rest/api/3/myself" to retrieve JIRA profile details, not "rest/auth/1/session"
Summary:
Ref T13493. At time of writing, the old API method no longer functions: `1/session` does not return an `accountId` but all calls now require one.

Use the modern `3/myself` API instead. The datastructure returned by `2/user` (older appraoch) and `3/myself` (newer approach) is more or less the same, as far as I can tell.

Test Plan: Linked an account against modern-at-time-of-writing Atlassian-hosted JIRA.

Maniphest Tasks: T13493

Differential Revision: https://secure.phabricator.com/D21170
2020-04-25 14:05:22 -07:00
epriestley
40d2346f29 Add a missing "null" check when rebuilding old/new diff content
Summary:
See <https://discourse.phabricator-community.org/t/exceptions-when-viewing-diffs/3787>. This list may include `null` values.

Until PHP 7.4, `$x = null; echo $x['y'];` does not emit a warning. Sneaky!

Test Plan:
  - Traced `null` values from `reparseHunksForSpecialAttributes()`, saw them no longer incorporated into corpus bodies.
  - This has some amount of test coverage.

Differential Revision: https://secure.phabricator.com/D21169
2020-04-25 09:23:05 -07:00
epriestley
454ecb56e3 When proxying HTTP repository responses from repository nodes, discard content description headers
Summary:
Ref T13517. See that task for details about the underlying issue here.

Currently, we may decode a compressed response, then retransmit it with leftover "Content-Encoding" and "Content-Length" headers. Instead, strip these headers.

Test Plan:
  - In a clustered repository setup, cloned a Git repository over HTTP.
  - Before: Error while processing content unencoding: invalid stored block lengths
  - After: Clean clone.

Maniphest Tasks: T13517

Differential Revision: https://secure.phabricator.com/D21167
2020-04-25 07:51:46 -07:00
epriestley
6f7147376f Parse "multipart/form-data" bodies even if "enable_post_data_reading" is on
Summary:
Ref T4369. During T13507, I set my "max_post_size" to a very small value, like 7 (i.e., 7 bytes). This essentially disables "enable_post_data_reading" even if the setting is technically on.

This breaks forms which use "multipart/form-data", which are rare but not nonexistent. Notably, forms in Config use this setting (because of `ui.header` stuff?) although perhaps they should not or no longer need to.

This can be fixed by parsing the raw input.

Since the only reason we don't parse the raw input is concern that we may not be able to read it (per documentation, but never actually observed), and we do a `strlen()` test anyway, just read it unconditionally.

This should fix cases where POST data wasn't read because of "max_post_size" without impacting anything else.

Test Plan: With very small "max_post_size", updated "ui.footer-items" in Config. Before: form acted as a no-op. After: form submitted.

Maniphest Tasks: T4369

Differential Revision: https://secure.phabricator.com/D21165
2020-04-24 11:25:57 -07:00
epriestley
5a460e4ea5 Stick the page footer in the right place on Formation View pages
Summary: Ref T13516. This isn't terribly clean, but get the page footer into the bottom of the content page on FormationView pages so it doesn't overlap into the side panel.

Test Plan: With and without a footer, viewed normal and FormationView pages. Saw footers in appropriate places at appropriate times.

Maniphest Tasks: T13516

Differential Revision: https://secure.phabricator.com/D21166
2020-04-24 11:25:31 -07:00
epriestley
d05d8f6558 Add a very forgiving GC for Differential viewstate information
Summary:
Ref T13455. Viewstates are fairly small and will probably grow less quickly than the changeset table, but the data is also not important to retain in the long term: if you revisit a change several months after hiding some files, it's fine if we've forgotten that you adjusted the view parameters.

Add a GC with a long default collection policy (180 days) so installs can manage the size of this table if it becomes necessary.

Test Plan: Ran via `bin/garbage` to adjust the GC policy and collect viewstates.

Maniphest Tasks: T13455

Differential Revision: https://secure.phabricator.com/D21164
2020-04-23 14:17:48 -07:00
epriestley
e20feeeee9 Update static resource package definitions
Summary: Ref T13516. Differential got some new UI elements and behaviors, so update static resource package definitions.

Test Plan:
  - Saw JS requests drop from 17 to 4.
  - Saw CSS requests drop from 9 to 3.

(These won't quite match production since some JS/CSS is for DarkConsole.)

Maniphest Tasks: T13516

Differential Revision: https://secure.phabricator.com/D21163
2020-04-23 13:50:19 -07:00
epriestley
4793bfcb7c Don't show the "file tree" view on tablets/phones
Summary: Ref T13516. Hide this UI on devices without the screen width to reasonably support it.

Test Plan: Viewed a revision at various window widths, saw the elements vanish at device widths and reappear at desktop widths.

Maniphest Tasks: T13516

Differential Revision: https://secure.phabricator.com/D21162
2020-04-23 13:40:42 -07:00
epriestley
d2572f8b33 Refine more Differential review state behaviors
Summary:
Ref T13516.

- Add an "Add Comment" navigation anchor.
- Make selection state more clear.
- Make hidden state tidier and more clear.
- Hide "View Options" in the hidden state to dodge all the weird behaviors it implies.
- Click to select/deselect changesets.
- When you open the view dropdown menu, then press "h", close the dropdown menu.

Test Plan: Fiddled with all these behaviors.

Maniphest Tasks: T13516

Differential Revision: https://secure.phabricator.com/D21161
2020-04-23 10:14:52 -07:00
epriestley
0ede616f31 Update the "View Options" menu for recent filetree changes
Summary:
Ref T13516. Minor improvements here:

  - Show key commands in the "View Options" dropdown.
  - Organize it slightly better.
  - Improve disabled item behaviors a little bit.
  - Add a "Browse Directory" action.
  - Rename "...in Diffusion" to "...in Repository".
  - Make "d", "D", and "h" use the same targeting rules as "\".
  - When you hide a file with the "h" menu item, select it.

Test Plan: Poked at the menu a lot, ran into less questionable behavior.

Maniphest Tasks: T13516

Differential Revision: https://secure.phabricator.com/D21160
2020-04-23 08:23:12 -07:00
epriestley
befbea2f00 Don't pass "No newline at end of file." annotations to DocumentEngines as literal diff text
Summary: See PHI1707, which has a Jupyter notebook which fails to diff nicely when modified. The root cause seems to be that the document does not end in a newline.

Test Plan: Applied patch, diffed the file, got a Jupyter diff out of it.

Differential Revision: https://secure.phabricator.com/D21159
2020-04-22 20:22:33 -07:00
epriestley
60de1506fe Make "hidden" changesets sticky, and show hidden state in the filetree
Summary:
Ref T13455. Make "hidden" a changeset property similar to other changeset properties.

We don't need to render this on the server, so we make a request (to update the setting) and just discard the response.

Test Plan: {F7375468}

Maniphest Tasks: T13455

Differential Revision: https://secure.phabricator.com/D21158
2020-04-22 16:12:42 -07:00
epriestley
a72a66caa8 Mark "low importance" and "owned" changes in the filetree
Summary: Ref T13516. Mark low-importance changes (generated code, deleted files) and owned-with-authority changes in the filetree.

Test Plan: {F7375327}

Maniphest Tasks: T13516

Differential Revision: https://secure.phabricator.com/D21157
2020-04-22 11:22:34 -07:00
epriestley
ff88eb588e Show change information in file icons in the filetree
Summary: Ref T13516. Restores "deleted"/"added" information to the tree icons.

Test Plan: {F7375145}

Maniphest Tasks: T13516

Differential Revision: https://secure.phabricator.com/D21156
2020-04-22 08:38:29 -07:00
epriestley
9550ae6984 When a directory has a single directory child, collapse them into a single "a/b/" path entry
Summary:
Ref T13516. Instead of rendering trees like this:

  - a/
    - b/
      - c.txt

...render:

  - a/b/
    - c.txt

Test Plan: {F7374205}

Maniphest Tasks: T13516

Differential Revision: https://secure.phabricator.com/D21155
2020-04-22 08:37:03 -07:00
epriestley
12eddb18fb Entirely replace the old filetree UI with the "flank" UI
Summary:
Ref T13516. Deletes all old filetree / flex / active / collapse nav code in favor of the new code.

Restores the inline tips in the path tree.

Test Plan: {F7374175}

Maniphest Tasks: T13516

Differential Revision: https://secure.phabricator.com/D21154
2020-04-22 08:32:02 -07:00
epriestley
ba8071bbef Roughly style the new "flank" paths UI
Summary: Ref T13516. Apply basic UI styling to the new UI and make some more interaction work.

Test Plan: {F7374096}

Maniphest Tasks: T13516

Differential Revision: https://secure.phabricator.com/D21153
2020-04-22 08:31:40 -07:00
epriestley
8cd1f9a309 Generate file trees from changesets in the new flank UI
Summary: Ref T13516. Generate a tree structure based on the page changesets. Still missing styles and a whole lot of behavior.

Test Plan: {F7373967}

Maniphest Tasks: T13516

Differential Revision: https://secure.phabricator.com/D21152
2020-04-22 08:31:17 -07:00
epriestley
646280972b Glue the new FormationView on top of the older Filetree view in Differential
Summary: Ref T13516. This glues "FormationView" to "ChangesetList". The actual tree is not functional in any meaningful way yet.

Test Plan: {F7373838}

Maniphest Tasks: T13516

Differential Revision: https://secure.phabricator.com/D21151
2020-04-22 08:29:04 -07:00
epriestley
fef2cdabfe Add a "FormationView" to support dynamic flank panels
Summary:
Ref T13516. Currently, the "File Tree" element is a semi-dynamic side panel that's implemented as a special mode of a side nav panel.

This implementation is fairly clunky, and arose from organic growth out of the side nav. As such, it has some weird behaviors, doesn't have builtin support for show/hide, and can't generalize easily.

Introduce a "FormationView" which supports loading a page up with piles of side panels in various modes.

Test Plan: No callers and no user-visible impact.

Maniphest Tasks: T13516

Differential Revision: https://secure.phabricator.com/D21150
2020-04-22 08:23:21 -07:00
epriestley
ef69c7969f Restore editor behavior to Diffusion and support "\" shortcut
Summary:
Ref T13515. This restores the "Open in Editor" behavior to Diffusion, and makes "\" work there.

The URI pattern is now sent as a structured template to the client, so the code will work properly if a file path contains "%l".

Test Plan:
  - Clicked "Open in Editor" and pressed "\" in Diffusion when viewing a file.
  - Clicked a line, hit "\", got the file opened to that line.

Maniphest Tasks: T13515

Differential Revision: https://secure.phabricator.com/D21149
2020-04-19 09:41:37 -07:00
epriestley
537ff68edd In Differential, make the "Open in Editor" keystroke work with no selection, or a change or inline selected
Summary:
Ref T13515. Currently, "Open in Editor" only works with a file-level selection.

  - If we have a change-level or inline-level selection, open the parent changeset.
  - If we have no selection, but the banner is showing something, open the fine shown in the banner.

Test Plan: With files, inlines, changes, and no selection, pressed "\". Saw files pop open in my external editor.

Maniphest Tasks: T13515

Differential Revision: https://secure.phabricator.com/D21148
2020-04-19 09:41:03 -07:00
epriestley
f02024615a Add "short name", "id", and "phid" variables for external editor URIs
Summary: Ref T13515. External editor URIs currently depend on repositories having callsigns, but callsigns are no longer required. Add some variables to support configuring this feature for repositories that do not have callsigns.

Test Plan: Changed settings to use new variables, saw links generate appropriately.

Maniphest Tasks: T13515

Differential Revision: https://secure.phabricator.com/D21147
2020-04-19 09:37:53 -07:00
epriestley
c79094d7a8 Add static errors, supported protocols, and a dynamic function listing to external editor settings page
Summary:
Ref T13515.

  - Previously valid editor URIs may become invalid without being changed (if an administrator removes a protocol from the list, for example), but this isn't explained very well. Show an error on the settings page if the current value isn't usable.
  - Generate a list of functions from an authority in the parser.
  - Generate a list of protocols from configuration.

Test Plan: {F7370872}

Maniphest Tasks: T13515

Differential Revision: https://secure.phabricator.com/D21146
2020-04-19 09:37:31 -07:00
epriestley
7a79131bf2 Replace old hard-coded URI-based "changes saved" jank with new overgeneralized cookie-based "changes saved" jank
Summary:
Ref T13515. Settings currently has some highly specialized code for rendering "Changes saved." messages. The "saved" state is communicated across a redirect-after-POST by adding `/saved/` to the end of the URI.

This isn't great. It needs a lot of moving pieces, including special accommodations in routing rules. It's user-visible. It has the wrong behavior if you reload the page or navigate directly to the "saved" URI.

Try this scheme, which is also pretty sketchy but seems like an upgrade on the balance:

  - Set a cookie on the redirect which identifies the form we just saved.
  - On page startup: if this cookie exists, save the value and clear it.
  - If the current page started with a cookie identifying the form on the page, treat the page as a "saved" page.

This supports passing a small amount of state across the redirect-after-POST flow, and when you reload the page it doesn't keep the message around. Applications don't need to coordinate it, either. Seems somewhat cleaner?

Test Plan: In Firefox, Safari, and Chrome: saved settings, saw a "Saved changes" banner without any URI junk. Reloaded page, saw banner vanish properly.

Maniphest Tasks: T13515

Differential Revision: https://secure.phabricator.com/D21144
2020-04-19 09:04:31 -07:00
epriestley
3984c14260 Tokenize external editor links so they can be safely materialized on the client
Summary:
Ref T13515. Currently, opening a file to a particular line in an external editor relies on replacing "%l" with "%l" (which is escaped as "%25l") on the server, and then replacing "%25l" with the line number on the client. This will fail if the file path (or any other variable) contains "%l" in its unencoded form.

The parser also can't identify invalid variables.

Pull the parser out, formalize it, and make it generate an intermediate representation which can be sent to the client and reconstituted.

(This temporarily breaks Diffusion and permanently removes the weird, ancient integration in Dark Console.)

Test Plan:
  - Added a bunch of tests for the actual parser.
  - Used "Open in Editor" in Differential.

Maniphest Tasks: T13515

Differential Revision: https://secure.phabricator.com/D21143
2020-04-19 09:02:49 -07:00
epriestley
4168335619 Remove the "Edit Multiple Files" external editor setting
Summary: Ref T13515. No callsites actually use this, most editors don't support it, it doesn't seem terribly useful for the ones that do, it makes template-based APIs for line-number substitution complicated, and we can probably just loop on `window.open()` anyway.

Test Plan: Grepped for affected symbols, found no more references. Loaded settings page, saw no more setting.

Maniphest Tasks: T13515

Differential Revision: https://secure.phabricator.com/D21142
2020-04-19 09:02:03 -07:00
epriestley
8bdc713352 Make the "Keyboard Shortcuts" dialog in Differential less hideous
Summary:
Ref T13515. Adding "\" ("Open in External Editor") made this slighlty worse, but it was already pretty bad.

Long ago the keys had a special style on them, but this got changed and dropped somewhere around D16568 -- although at the time, I think they still had a grey background (see T11654).

Some later change removed this background.

Put the background back and separate the keystrokes into groups.

Test Plan: {F7370615}

Maniphest Tasks: T13515

Differential Revision: https://secure.phabricator.com/D21141
2020-04-19 09:01:07 -07:00
epriestley
84cd4a3854 Move "External Editor" settings to a separate settings group
Summary:
Ref T13515. It's not intuitive that these settings are "Display Preferences", even thought they're intenrally related to some of the other display preferences.

Give them a separate group.

Test Plan: {F7370500}

Maniphest Tasks: T13515

Differential Revision: https://secure.phabricator.com/D21140
2020-04-19 08:59:43 -07:00
epriestley
c3c55d82ae Make "renderer", "engine", and "encoding" sticky across reloads in Differential and Diffusion
Summary:
Ref T13455. Update the other "view state" properties to work like "highlight" now works.

Some complexity here arises from these concerns:

  - In "View Standalone", we render the changeset inline. This is useful for debugging/development, and desirable to retain.
  - In all other cases, we render the changeset with AJAX.

So the client needs to be able to learn about the "state" properties of the changeset on two different flows. Prior to this change, each pathway had a fair amount of unique code.

Then, some bookkeeping issues:

  - At inital rendering time, we may not know which renderer will be selected: it may be based on the client viewport dimensions.
  - Prior to this change, the client didn't separate "value of the property for the changeset as rendered" and "desired value of the property".

Test Plan:
  - Viewed changes in Differential, Diffusion, and in standalone mode.
  - Toggled renderer, character sets, and document engine (this one isn't terribly useful). Reloaded, saw them stick.
  - Started typing a comment, cancelled it, hit the undo UI.

Maniphest Tasks: T13455

Differential Revision: https://secure.phabricator.com/D21138
2020-04-19 08:59:09 -07:00
epriestley
8aac55cc57 Make "Highlight As..." sticky across reloads in Diffusion and Differential
Summary:
Ref T13455. Add container-level storage for persistent view state, and persist "Highlight As..." inside it.

The storage generates a "PhabricatorChangesetViewState" configuration object as an output.

When preferences are expressed on a diff and that diff is later attached to a revision, we attempt to copy the preferences.

The internal storage tracks per-changeset settings, but currently always uses "last update wins" to apply the settings in the UI.

Test Plan:
  - Viewed revisions, changed highlighting, reloaded. Saw highlighting stick in revision view and standalone view.
  - Viewed commits, changed highlighting, reloaded. Saw highlighting stick.
  - Created a diff, changed highlighting, turned it into a revision, saw highlighting persist.

Subscribers: jmeador, PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13455

Differential Revision: https://secure.phabricator.com/D21137
2020-04-19 08:58:39 -07:00
epriestley
3adf082002 When inlines would disable a file shield in a diff, still apply the shield if all the comments are collapsed
Summary:
Ref T13515. We "shield" some changesets, including generated code and intradiffs with no intermediate changes.

These files don't get shielded if they have inline comments.

But, if the viewer has collapsed all the comments, we can shield the file again.

Test Plan:
  - Created a change affecting files A and B, with three diffs:
    - Touch A and B.
    - Touch B only.
    - Touch nothing.
  - Added an inline to A and collapsed it.
  - Viewed Diff 1 vs Diff 2:
    - Saw A collapse with a note about inlines.
    - Saw B changes, normally.
  - Viewed Diff 2 vs Diff 3:
    - Saw A collapse with a note about inlines.
    - Saw B collapse normally.
  - Uncollapsed the inline, viewed 1v2 and 2v3, saw A expand in both cases.

Maniphest Tasks: T13515

Differential Revision: https://secure.phabricator.com/D21136
2020-04-17 10:52:40 -07:00
epriestley
3d966d8a41 Add an "Open in External Editor" keystroke to Differential
Summary: Ref T13515. See PHI1661. If a file is selected, add a keystroke to click the "Open in External Editor" link.

Test Plan: In Safari, Chrome, and Firefox: used "J" to select a file, then "\" to open it in an external editor. (In Safari and Chrome, this prompts.)

Maniphest Tasks: T13515

Differential Revision: https://secure.phabricator.com/D21135
2020-04-17 10:06:46 -07:00
epriestley
83eb7447a1 When Owners packages are archived, annotate them in tokenizer results
Summary: Fixes T13512. Archived packages in Owners are missing hinting, but should have it.

Test Plan:
Before:

{F7369122}

After:

{F7369128}

Maniphest Tasks: T13512

Differential Revision: https://secure.phabricator.com/D21134
2020-04-17 06:18:04 -07:00
epriestley
925d2b051c Fix a "flickering" behavior with the menu bar transition animations in Chrome
Summary:
Fixes T13508. The "Notification" and "Messages" icons in the menu bar have a CSS transition animation on hover.

In Chrome, when this element moves up 2px, you can get a flicker in and out of the hover state if the user's cursor is at the very bottom of the element, since the bounding box for the element is rapidly sliding in and out of the area under the cursor.

To fix this: as we move the element up, also make it taller.

Test Plan: In Safari, Chrome, and Firefox: put my cursor at the very bottom of the element, no longer saw any animation flickering.

Maniphest Tasks: T13508

Differential Revision: https://secure.phabricator.com/D21133
2020-04-17 06:03:36 -07:00
epriestley
3e676bce9e No-op an ancient Paste edge migration which no longer functions after a database rename
Summary:
Fixes T13510. This migration currently fails because it tries to affect the "paste" database, but when it runs this database will be named "pastebin".

Since the cost of fixing it in place or moving it past the rename migration both seem relatively high (and the cost of throwing it away is plausibly zero) just throw it for now.

Test Plan: Looked at file, saw no more code that can execute.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13510

Differential Revision: https://secure.phabricator.com/D21132
2020-04-17 05:38:31 -07:00
Austin McKinley
ef1340bd32 Add Ferret support to Paste
Summary:
Ref PHI1292. Enable fulltext searchs in paste. Maybe this should only index a snippet instead of the entire content?

Also updates table names in `PhabricatorPasteQuery`.

Test Plan: Created some pastes, indexed them, searched for them.

Reviewers: amckinley

Subscribers: codeblock, Korvin, PHID-OPKG-gm6ozazyms6q6i22gyam

Differential Revision: https://secure.phabricator.com/D20650
2020-04-16 14:10:23 -07:00
epriestley
2748f83e12 Modularize Ferret fulltext functions
Summary: Ref T13511. Currently, Ferret fulltext field functions (like "title:") are hard-coded. Modularize them so extensions may define new ones.

Test Plan: Added a new custom field which emits data for the indexer, searched for "animal-noises:moo", "animal-noises:-", etc., in global search and application search.

Maniphest Tasks: T13511

Differential Revision: https://secure.phabricator.com/D21131
2020-04-16 13:41:13 -07:00
epriestley
894d9b6587 Remove Ferret function aliases and overrides
Summary:
Ref T13511. Ferret functions currently define "aliases", and some applications override the default aliases.

This probably isn't really the right model, since it means the available function aliases in global search depend on the types of documents you're searching for. This isn't fundamentally unworkable but is kind of weird.

Regardless, these don't actually work. Searching for "description:x" is a syntax error.

Since they don't work, it's a good bet no one is relying on them. Just get rid of them until there's a clearer argument for the feature.

Test Plan: Grepped for "getFunctionMap", got no other hits. Ran some queries with the alias functions, got syntax errors.

Maniphest Tasks: T13511

Differential Revision: https://secure.phabricator.com/D21130
2020-04-16 13:40:17 -07:00
epriestley
9bdf477f2f Combine the two different ngram-splitting algorithms into a single engine
Summary:
Ref T13501. Depends on D21127. With the "prefix" behavior removed in D21127, we now have two virtually identical copies of the same code.

The newer one in Ferret is better: it slices utf8 correctly and is slightly more efficient on large inputs. Pull it out and make all callers call into it.

Test Plan:
  - Grepped for all affected symbols.
  - Ran `bin/search index --force ...` to reindex various objects (tasks, files).
  - Searched for things in the UI.

Maniphest Tasks: T13501

Differential Revision: https://secure.phabricator.com/D21128
2020-04-16 09:45:00 -07:00
epriestley
fb3f423279 Remove broken and unfixable "prefix" ngram behavior
Summary:
Ref T13501. The older ngram code has some "prefix" behavior that tries to handle cases where a user issues a very short (one or two character) query.

This code doesn't work, presumably never worked, and can not be made to work (or, at least, I don't see a way, and am fairly sure one does not exist).

If the user searches for "xy", we can find trigrams in the form "xy*" using the index, but not in the form "*xy". The code makes a misguided effort to look for " xy", but this will only find "xy" in words that begin with "xy", like "xylophone".

For example, searching Files for "om" does not currently find "random.txt".

Remove this behavior. Without engaging the trigram index, these queries fall back to an unidexed "LIKE" table scan, but that's about the best we can do.

Test Plan: Searched for "om", hit "random.txt".

Maniphest Tasks: T13501

Differential Revision: https://secure.phabricator.com/D21127
2020-04-16 09:44:37 -07:00
epriestley
b1b9c844ac Remove unused "getAllFunctionFields()" from Ferret
Summary: Ref T13511. This function does nothing interesting and has no callers.

Test Plan: Grepped for callers.

Maniphest Tasks: T13511

Differential Revision: https://secure.phabricator.com/D21126
2020-04-16 09:43:25 -07:00
epriestley
3573170dfa Compress file downloads if the client sends "Accept-Encoding: gzip" and we guess the file might compress alright
Summary:
Ref T13507. We currently compress normal responses, but do not compress file data responses because most files we serve are images and already compressed.

However, there are some cases where large files may be highly compressible (e.g., huge XML files stored in LFS) and we can benefit from compressing responses.

Make a reasonable guess about whether compression is beneficial and enable compression if we guess it is.

Test Plan:
  - Used `curl ...` to download an image with `Accept-Encoding: gzip`. Got raw image data in the response (as expected, because we don't expect images to be worthwhile to recompress).
  - Used `curl ...` to download a text file with `Accept-Encoding: gzip`. Got a compressed response. Decompressed the response into the original file.

Maniphest Tasks: T13507

Differential Revision: https://secure.phabricator.com/D21125
2020-04-15 11:53:35 -07:00
epriestley
d86506052c Update a very old Phriction migration which incorrectly uses "save()"
Summary:
See <https://discourse.phabricator-community.org/t/storage-upgrade-error/3748>.

It is broadly unsafe for migrations to use "save()". If the object gains new fields later, the query will include "SET newField = X", which will fail against the old schema which is in the process of being upgraded.

Instead, migrations must issue raw SQL against the schema as it is expected to exist at the time the migration executes.

Migrations have followed this rule for a long time, but this ~6 year old migration was overlooked. Update it to issue a raw query to perform the policy update.

Test Plan: This is somewhat flimsy since rebuilding a genuine reproduction case is messy, but used "bin/storage --apply ..." to at least get the new query to execute against modern Phabricator without issues.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Differential Revision: https://secure.phabricator.com/D21124
2020-04-15 08:06:30 -07:00
epriestley
45665dd3b4 Hide "notification.servers" configuration and don't follow redirects from Aphlict
Summary:
See <https://hackerone.com/reports/850114>.

An attacker with administrator privileges can configure "notification.servers" to connect to internal services, either directly or with chosen parameters by selecting an attacker-controlled service and having it issue a "Location" redirect.

Generally, we allow this attack to occur. The same administrator can use an authentication provider or a VCS repository to perform the same attack, and we can't reasonably harden these workflows without breaking things that users expect to be able to do.

There's no reason this particular variation of the attack needs to be allowable, though, and the current behavior isn't consistent with how other similar things work.

  - Hide the "notification.servers" configuration, which also locks it. This is similar to other modern service/server configuration.
  - Don't follow redirects on these requests. Aphlict should never issue a "Location" header, so if we encounter one something is misconfigured. Declining to follow this header likely makes the issue easier to debug.

Test Plan:
  - Viewed configuration in web UI.
  - Configured a server that "Location: ..." redirects, got a followed redirect before and a failure afterward.

{F7365973}

Differential Revision: https://secure.phabricator.com/D21123
2020-04-15 07:00:51 -07:00
epriestley
b52fa96238 Disable automatic decoding of "Content-Encoding" responses during "Accept-Encoding" setup test
Summary:
Ref T13507. Now that we handle processing of "Content-Encoding: gzip" headers by default, this setup check can get a decompressed body back. Since it specifically wants a raw body back, disable this behavior.

Also, "@" a couple things which can get in the way if they fail now that error handling is more aggressive about throwing on warnings.

Test Plan: Ran setup check after other changes in T13507, got clean result.

Maniphest Tasks: T13507

Differential Revision: https://secure.phabricator.com/D21122
2020-04-15 06:28:29 -07:00
epriestley
0ea6d131e0 In Conduit responses, assert that Phabricator supports a "gzip" capability
Summary: Ref T13507. If we believe the server can accept "Content-Encoding: gzip" requests, make the claim in an "X-Conduit-Capabilities" header in responses. Clients can use request compression on subsequent requests.

Test Plan: See D21119 for the client piece.

Maniphest Tasks: T13507

Differential Revision: https://secure.phabricator.com/D21120
2020-04-14 16:51:03 -07:00
epriestley
6b05d2be28 Add a setup warning to detect "SetInputFilter DEFLATE" and other "Content-Encoding" request mangling
Summary: Ref T13507. See that task for discussion.

Test Plan: Faked different response behaviors and hit both variations of this error.

Maniphest Tasks: T13507

Differential Revision: https://secure.phabricator.com/D21116
2020-04-14 14:48:43 -07:00
epriestley
99cbc20778 Reduce the verbosity of the "Aphlict" log
Summary:
See PHI1692. Currently, the Aphlict log is ridiculously verbose. As an initial pass at improving this:

  - When starting in "debug" mode, pass "--debug=1" to Node.
  - In Node, separate logging into "log" (lower-volume, more-important messages) and "trace" (higher-volume, less-important messages).
  - Only print "trace" messages in "debug" mode.

Test Plan: Ran Aphlict in debug and non-debug modes. Behavior unchanged in debug mode, but log has more sensible verbosity in non-debug mode.

Differential Revision: https://secure.phabricator.com/D21115
2020-04-14 13:24:44 -07:00
epriestley
59c855276b Provide a "--local" flag to "bin/conduit call" to force in-process execution
Summary:
See PHI1692. Currently, it's hard to get a local profile or "--trace" of some Diffusion API methods, since they always proxy via HTTP -- even if the local node can serve the request.

This always-proxy behavior is intentional (so we always go down the same code path, to limit surprises) but inconvenient when debugging. Allow an operator to connect to a node which can serve a request and issue a `--local` call to force in-process execution.

This makes it straightforward to "--trace" or "--xprofile" the call.

Test Plan: Ran `bin/conduit call ...` with and without `--local` using a Diffusion method on a clustered repository. Without `--local`, saw proxy via HTTP. With `--local`, saw in-process execution.

Differential Revision: https://secure.phabricator.com/D21114
2020-04-14 13:24:26 -07:00
epriestley
4655a5f059 Document the "field present" and "field absent" operators in Ferret
Summary: Ref T13509. Adds documentation for the new operators.

Test Plan: Read documentation, tried examples, got sensible-seeming results.

Maniphest Tasks: T13509

Differential Revision: https://secure.phabricator.com/D21112
2020-04-14 11:08:34 -07:00
epriestley
b3a8754013 Make the Ferret query compiler keep functions sticky across non-initial quoted tokens
Summary: Ref T13509. In `title:big "red" dog`, keep "title" sticky across all three terms, since this seems like it's probably the best match for intent.

Test Plan: Added unit tests; ran unit tests.

Maniphest Tasks: T13509

Differential Revision: https://secure.phabricator.com/D21111
2020-04-14 11:00:20 -07:00
epriestley
0511b2a012 Implement the "present" and "absent" operators in the Ferret execution engine
Summary:
Ref T13509. Now that the compiler can parse these queries, actually implement them.

These are fairly easy to implement:

  - For present, just "JOIN". If it works, the field is present.
  - For absent, we "LEFT JOIN" and then "WHERE any_column IS NULL".

Test Plan: Searched for various documents with and without fields present, got sensible results in Maniphest. For example, "body:-" finds tasks with no body, "body:- duck" finds tasks with no body and "duck" elsewhere in the content, and so on.

Maniphest Tasks: T13509

Differential Revision: https://secure.phabricator.com/D21110
2020-04-14 10:55:30 -07:00
epriestley
143f86d60b Tighten query compiler rules around spaces inside and after operators
Summary:
Ref T13509. Since `title:- cat` is now ambiguous, forbid spaces after operators.

Also, forbid spaces inside operators, although this has no effect today.

Test Plan: Added unit tests, ran unit tests.

Maniphest Tasks: T13509

Differential Revision: https://secure.phabricator.com/D21109
2020-04-14 10:51:55 -07:00
epriestley
8fa8d0e648 Make Ferret query functions sticky only if their values are not quoted
Summary:
Ref T13509. Currently, functions are "sticky", but this stickness is in the query execution layer.

Instead:

  - move stickiness to the query compiler; and
  - make it so that functions are not sticky if their arguments are quoted.

For example:

  - `title:x y` previously meant `title:x title:y` (and still does). The "title:" is sticky.
  - `title:"x" y` previously meant `title:x title:y`. It now means `title:x all:y`. The "title:" is not sticky because the argument is quoted.

Test Plan: Added unit tests, ran unit tests.

Maniphest Tasks: T13509

Differential Revision: https://secure.phabricator.com/D21108
2020-04-14 10:47:51 -07:00
epriestley
f31b9987ba Add "absent" and "present" field operators to the Ferret query compiler
Summary: Ref T13509. Parse "xyz:-" as "xyz is absent" and "xyz:~" as "xyz is present". These are new operators which the compiler emits separately from "not" and "substring".

Test Plan: Added unit tests, ran unit tests.

Maniphest Tasks: T13509

Differential Revision: https://secure.phabricator.com/D21107
2020-04-14 10:47:20 -07:00
epriestley
5c30a60e30 Tighten Ferret query parsing of empty tokens and empty functions
Summary:
Ref T13509. Certain query tokens like `title:=""` are currently accepted by the parser but discarded, and have no impact on the query. This isn't desirable.

Instead, require that tokens making an assertion about field content must be nonempty.

Test Plan: Added unit tests, made them pass.

Maniphest Tasks: T13509

Differential Revision: https://secure.phabricator.com/D21106
2020-04-14 10:32:46 -07:00
epriestley
471e89a8b7 Add "uri" to "paste.search" API output
Summary: Ref T13490. This simplifies some client behavior in the general case.

Test Plan: Called API method, saw URIs.

Maniphest Tasks: T13490

Differential Revision: https://secure.phabricator.com/D21105
2020-04-13 16:17:33 -07:00
epriestley
19e0abcb27 Fix an issue where raw diffs that are not attached to revisions could skip repository policy checks
Summary:
See PHI1697. If a diff is not attached to a revision (for example, if it was created with "arc diff --only"), but is attached to a repository, it is supposed to be visible only to users who can see that repository.

It currently skips this extended policy check and may incorrectly be visible to too many users.

(Once a diff is attached to a revision, this rule is enforced properly via the revision policy.)

Test Plan:
  - Set repository R to be visible only to Alice.
  - As Alice, created a diff from a working copy of repository R with "arc diff --only".
  - As Bailey, viewed the diff.
    - Before: visible diff.
    - After: policy exception (as expected).

Differential Revision: https://secure.phabricator.com/D21103
2020-04-13 12:08:35 -07:00
epriestley
5597f4e6f2 Add "uri" to the fields returned by "differential.revision.search"
Summary: Ref T13490. This simplifies mostly-theoretical cases where you're accessing Phabricator via arc-over-ssh and the Conduit protocol + domain may differ from the production protocol + domain.

Test Plan: Called API via web UI, saw sensible URI values in results.

Maniphest Tasks: T13490

Differential Revision: https://secure.phabricator.com/D21102
2020-04-13 12:06:39 -07:00
epriestley
c3be82fe6e Fix an out-of-date API call on the destruction pathway for Pholio mocks
Summary:
See <https://discourse.phabricator-community.org/t/destroying-a-mock-using-bin-remove-destroy-mx-gives-an-error/3728>.

Currently, Pholio calls an older API method on the mock destruction pathway. This call was introduced in D19911 but the callsite was only partially updated in D19914.

Test Plan: Ran "bin/remove destroy Mx" to destroy a mock. Before: fatal with a bad call; after: clean destruction.

Differential Revision: https://secure.phabricator.com/D21081
2020-04-10 08:01:34 -07:00
epriestley
a2fb91b8af Remove the (hopefully) obsolete "post_max_size" check during startup
Summary:
Ref T13507. See that task for discussion. This check appears to be obsolete in all common cases and misfires if the client submits compressed requests.

Since the cases where it could still trigger correctly are extremely rare and should still have plausible behavior, just remove it.

Test Plan: Grepped for calls.

Maniphest Tasks: T13507

Differential Revision: https://secure.phabricator.com/D21077
2020-04-09 13:33:24 -07:00
f9637502ee Merge branch 'master' into blender-tweaks 2020-04-08 15:57:07 +02:00
epriestley
58fbf64a27 Refine handling of "@task" attributes in Diviner
Summary: Ref T13505. See that task for details. When a class has exactly one "@task" block, this API returns a string. Some day, this should be made more consistent.

Test Plan: Viewed a class with exactly one "@task", no more fatal. Viewed classes with zero and more than one "@task" attributes, got clean renderings.

Maniphest Tasks: T13505

Differential Revision: https://secure.phabricator.com/D21062
2020-04-06 11:51:33 -07:00
epriestley
271e104c7e Update DivinerAtomController for a long-ago change to the docblock parser API
Summary: Ref T13505. See that task for discussion.

Test Plan: Ran `diviner generate` locally, found a page fataling on this `strlen()`, applied patch, got a sketchy but not-broken page.

Maniphest Tasks: T13505

Differential Revision: https://secure.phabricator.com/D21061
2020-04-06 11:31:31 -07:00
epriestley
f1d1ec3d77 Add an "isDone" flag to "transaction.search" for Differential inline comments
Summary: See PHI1684. Expose the published state of the "Done" checkbox to the API.

Test Plan: Made API calls on a comment in all four states, got correct published states via the API in all cases.

Differential Revision: https://secure.phabricator.com/D21059
2020-04-05 09:36:15 -07:00
epriestley
33b73d887a If daemon running-as-user setup check fails its query, don't bother with it
Summary:
See <https://discourse.phabricator-community.org/t/upgrade-from-sep-30-2016/3702/>. A user performing an upgrade from 2016 to 2020 ran into an issue where this setup query is overheating.

This is likely caused by too many rows changing state during query execution, but the particulars aren't important since this setup check isn't too critical and will catch the issue eventually. It's fine to just move on if this query fails for any reason.

Test Plan: Forced the query to overheat, loaded setup issues, got overheating fatal. Applied patch, no more fatal.

Differential Revision: https://secure.phabricator.com/D21057
2020-04-03 16:18:55 -07:00
epriestley
1e7cc72cd8 Improve performance when marking commits as unreachable after multiple ref deletions
Summary:
See PHI1688. If many refs with a large amount of shared ancestry are deleted from a repository, we can spend much longer than necessary marking their mutual ancestors as unreachable over and over again.

For example, if refs A, B and C all point near the head of an obsolete "develop" branch and have about 1K shared commits reachable from no other refs, deleting all three refs will lead to us performing 3,000 mark-as-unreachable operations (once for each "<ref, commit>" pair).

Instead, we can stop exploring history once we reach an already-unreachable commit.

Test Plan:
  - Destroyed 7 similar refs simultaneously.
  - Ran `bin/repository refs`, saw 7 entries appear in the `oldref` table.
  - Ran `bin/repository discover` with some debugging statements added, saw sensible-seeming behavior which didn't double-mark any newly-unreachable refs.

Differential Revision: https://secure.phabricator.com/D21056
2020-04-03 13:28:42 -07:00
epriestley
1a59cae743 Update some Phabricator behaviors for changes to Futures
Summary:
Depends on D21053. Ref T11968. Three things have changed:

  - Overseers can no longer use FutureIterator to continue execution of an arbitrary list of futures from any state. Use FuturePool instead.
  - Same with repository daemons.
  - Probably (?) fix an API change in the Harbormaster exec future.

Test Plan:
  - Ran "bin/phd debug task" and "bin/phd debug pull", no longer saw Future-management related errors.
  - The Harbormaster future is easiest to test by just seeing if production works once this change is deployed there.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T11968

Differential Revision: https://secure.phabricator.com/D21054
2020-04-03 12:28:16 -07:00
epriestley
067b04aaf1 If HTTP response headers are already sent, don't fiddle with "zlib.output_compression"
Summary:
We write some synthetic HTTP responses inside unit tests. Some responses have an indirect side effect of adjusting "zlib.output_compression", but this adjustment fails if headers have already been output. From a CLI context, headers appear to count as already-output after we write anything to stdout:

```
<?php

echo headers_sent() ? "Y" : "N";
echo "\n";
echo headers_sent() ? "Y" : "N";
echo "\n";
```

This script prints "N", then "Y".

Recently, the default severity of warnings was increased in libphutil; this has been a long-standing warning but now causes test failures.

This behavior is sort of silly but the whole thing is kind of moot anyway. Just skip it if "headers_sent()" is true.

Test Plan: Ran "arc unit --everything", got clean results.

Differential Revision: https://secure.phabricator.com/D21055
2020-04-03 12:24:58 -07:00
Nathan Letwory
0ddfe6fcfb Fix missing duplication xaction data.
Both 'mergedinto' and 'mergedfrom' are now
realized 'transaction.search'.

Test Plan:
- Check output of 'transaction.search' for
  a task marked as duplicate of another
- Check output of 'transaction.search' for
  a task that has another task set as its
  duplicate

Differential Revision: https://developer.blender.org/D7090
2020-03-11 07:23:19 +02:00
Arturas Moskvinas arturas@uber.com
62f5bdbbd2 According to Jira Project keys must start with an uppercase letter, followed by one or more uppercase alphanumeric characters
Summary: Jira allows creating projects which contain number in names, phabricator will not allow such projects but it should

Test Plan: Pasted URL with Jira project which contain number in project name and it was parsed and resolved properly in phabricator

Reviewers: epriestley, Pawka, #blessed_reviewers

Reviewed By: epriestley, #blessed_reviewers

Subscribers: Korvin

Differential Revision: https://secure.phabricator.com/D21040
2020-03-09 22:04:23 +02:00
Nathan Letwory
dc57652085 Fix subtype transaction data missing.
Test plan: change type of a task, check
with transaction.search the transaction
data exists.
2020-02-28 15:45:48 +02:00
epriestley
d0f4554dbe Read both email addresses and Google Account IDs from Google OAuth
Summary:
Ref T13493. Google returns a lower-quality account identifier ("email") and a higher-quality account identifier ("id"). We currently read only "email".

Change the logic to read both "email" and "id", so that if Google ever moves away from "email" the transition will be a bit easier.

Test Plan: Linked/unlinked a Google account, looked at the external account identifier table.

Maniphest Tasks: T13493

Differential Revision: https://secure.phabricator.com/D21028
2020-02-24 13:26:42 -08:00
epriestley
785f3c98da Extract raw commit messages from Git more faithfully across Git versions
Summary:
Fixes T5028. Older versions of Git (apparently, from before 2010) did not provide a way to extract the raw body of a commit message from "git log", so we approximate it with "subject" and "wrapped body".

In newer versions of Git, the raw body can be extracted exactly.

Adjust how we extract messages based on the version of Git, and try to be more faithful to edge cases: particularly, be more careful to extract the correct number of trailing newlines.

Test Plan:
  - Added "var_dump()" + "die(1)" later in this method, then pushed various commit messages. Used "&& false" to force execution down the old path (either path should work in modern Git).
  - Observed more faithful extraction of messages, including a more faithful extraction of the number of trailing newlines. Extraction is fully faithful if we can go down the "%B" path, which we should be able to in nearly all modern cases.
  - Not all messages extract faithfully or consistently across the old and new versions, but the old extraction is destructive so this is likely about as close as we can realistically ever get.

Maniphest Tasks: T5028

Differential Revision: https://secure.phabricator.com/D21027
2020-02-24 12:37:45 -08:00
epriestley
d3f4af4a3a Add more layout constraints to tokenizer CSS to prevent layout issues with Chinese glyphs in Firefox 73
Summary:
Fixes T13495. See that task for details.

Tokenizer tokens which contain Chinese glyphs are slightly taller than normal tokens in Firefox 73, and at some non-100% zoom levels in other browsers.

This cauess the tokenizer list to layout and line break oddly.

Fix this by clamping tokenizer sizes more aggressively. Specifying a `max-height` means they can no longer line wrap, so this also requires more specification of overflow behavior.

Test Plan:
Before:

{F7216435}

After:

{F7216439}

Maniphest Tasks: T13495

Differential Revision: https://secure.phabricator.com/D21026
2020-02-24 08:00:44 -08:00
epriestley
e58ef418c7 Read both older "key" and newer "accountId" identifiers from JIRA during authentication
Summary:
Depends on D21022. Ref T13493. The JIRA API has changed from using "key" to identify users to using "accountId".

By reading both identifiers, this linkage "just works" if you run against an old version of JIRA, a new version of JIRA, or an intermediate version of JIRA.

It also "just works" if you run old JIRA, upgrade to intermediate JIRA, everyone refreshes their link at least once, then you upgrade to new JIRA.

This is a subset of cases and does not include "sudden upgrade to new JIRA", but it's strictly better than the old behavior for all cases it covers.

Test Plan: Linked, unlinked, and logged in with JIRA. Looked at the "ExternalAccountIdentifier" table and saw a sensible value.

Maniphest Tasks: T13493

Differential Revision: https://secure.phabricator.com/D21023
2020-02-22 17:49:47 -08:00
epriestley
802b5aca05 Remove all readers and writers of "accountID" on "ExternalAccount"
Summary: Depends on D21019. Ref T13493. There are no more barriers to removing readers and writers of "accountID"; the new "ExternalAccountIdentity" table can replace it completely.

Test Plan: Linked and unlinked OAuth accounts, logged in with OAuth accounts, tried to double-link OAuth accounts, grepped for affected symbols.

Maniphest Tasks: T13493

Differential Revision: https://secure.phabricator.com/D21022
2020-02-22 17:49:22 -08:00
epriestley
84b5ad09e6 Remove all readers and all nontrivial writers for "accountType" and "accountDomain" on "ExternalAccount"
Summary:
Depends on D21018. Ref T13493. Ref T6703. The "ExternalAccount" table has a unique key on `<accountType, accountDomain, accountID>` but this no longer matches our model of reality and changes in this sequence end writes to `accountID`.

Remove this key.

Then, remove all readers of `accountType` and `accountDomain` (and all nontrivial writers) because none of these callsites are well-aligned with plans in T6703.

This change has no user-facing impact today: all the rules about linking/unlinking/etc remain unchanged, because other rules currently prevent creation of more than one provider with a given "accountType".

Test Plan:
- Linked an OAuth1 account (JIRA).
- Linked an OAuth2 account (Asana).
- Used `bin/auth refresh` to cycle OAuth tokens.
- Grepped for affected symbols.
- Published an Asana update.
- Published a JIRA link.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13493, T6703

Differential Revision: https://secure.phabricator.com/D21019
2020-02-22 17:48:46 -08:00
epriestley
b8f0613b30 Update Asana feed publishing integration for "ExternalAccountIdentifier"
Summary: Depends on D21017. Ref T13493. Update the Asana integration so it reads the "ExternalAccountIdentifier" table instead of the old "accountID" field.

Test Plan: Linked an Asana account, used `bin/feed republish` to publish activity to Asana.

Maniphest Tasks: T13493

Differential Revision: https://secure.phabricator.com/D21018
2020-02-22 17:48:16 -08:00
epriestley
faf9f06e0a Migrate all "accountID" values to "ExternalAccountIdentifier" objects
Summary: Depends on D21016. Ref T13493. This copies existing external account "accountID" values into the "ExternalAccountIdentifier" table, preparing for an authority switch.

Test Plan: Ran migration several times, looked at the data that came out of it, saw sensible results. Logged out / in with external accounts.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13493

Differential Revision: https://secure.phabricator.com/D21017
2020-02-22 17:47:37 -08:00
epriestley
bcaf60015a Write ExternalAccountIdentifiers when interacting with external authentication providers
Summary:
Depends on D21015. When we sync an external account and get a list of account identifiers, write them to the database.

Nothing reads them yet and we still write "accountId", this just prepares us for reads.

Test Plan: Linked, refreshed, unlinked, and re-linked an external account. Peeked at the database and saw a sensible-looking row.

Differential Revision: https://secure.phabricator.com/D21016
2020-02-22 17:46:51 -08:00
epriestley
0872051bfa Make AuthProvider, ExternalAccount, and ExternalAccountIdentifier all Destructible
Summary: Depends on D21014. Ref T13493. Make these objects all use destructible interfaces and destroy sub-objects appropriately.

Test Plan:
  - Used `bin/remove destroy --trace ...` to destroy a provider, a user, and an external account.
  - Observed destruction of sub-objects, including external account identifiers.

Maniphest Tasks: T13493

Differential Revision: https://secure.phabricator.com/D21015
2020-02-22 17:46:29 -08:00
epriestley
05eb16d6de Update unusual handling of external accounts in "Password" auth provider
Summary:
Depends on D21013. Ref T13493. When users log in with most providers, the provider returns an "ExternalAccount" identifier (like an Asana account GUID) and the workflow figures out where to go from there, usually a decision to try to send the user to registration (if the external account isn't linked to anything yet) or login (if it is).

In the case of password providers, the password is really a property of an existing account, so sending the user to registration never makes sense. We can bypass the "external identifier" indirection layer and just say "username -> internal account" instead of "external GUID -> internal mapping -> internal account".

Formalize this so that "AuthProvider" can generate either a "map this external account" value or a "use this internal account" value.

This stops populating "accountID" on "password" "ExternalAccount" objects, but this was only an artifact of convenience. (These records don't really need to exist at all, but there's little harm in going down the same workflow as everything else for consistency.)

Test Plan: Logged in with a username/password. Wiped the external account table and repeated the process.

Maniphest Tasks: T13493

Differential Revision: https://secure.phabricator.com/D21014
2020-02-22 17:46:04 -08:00
epriestley
e43ecad8af Make external account identifier APIs return multiple identifiers
Summary:
Depends on D21012. Ref T13493. Currently, auth adapters return a single identifier for each external account.

Allow them to return more than one identifier, to better handle cases where an API changes from providing a lower-quality identifier to a higher-quality identifier.

On its own, this change doesn't change any user-facing behavior.

Test Plan: Linked and unlinked external accounts.

Maniphest Tasks: T13493

Differential Revision: https://secure.phabricator.com/D21013
2020-02-22 17:45:45 -08:00
epriestley
4094624828 Remove an ancient no-op check for duplicated external accounts
Summary:
Ref T13493. This check was introduced in D4647, but the condition can never be reached in modern Phabricator because the table has a unique key on `<accountType, accountDomain, accountID>` -- so no row can ever exist with the same value for that tuple but a different ID.

(I'm not entirely sure if it was reachable in D4647 either.)

Test Plan: Used `SHOW CREATE TABLE` to look at keys on the table and reasoned that this block can never have any effect.

Maniphest Tasks: T13493

Differential Revision: https://secure.phabricator.com/D21012
2020-02-22 17:45:19 -08:00
epriestley
70845a2d13 Add an "ExternalAccountIdentifier" table
Summary:
Depends on D21010. Ref T13493. External accounts may have multiple different unique identifiers, most often when v1 of the API makes a questionable choice (and provies a mutable, non-unique, or PII identifier) and v2 of the API uses an immutable, unique, random identifier.

Allow Phabricator to store multiple identifiers per external account.

Test Plan: Storage only, see followup changes.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13493

Differential Revision: https://secure.phabricator.com/D21011
2020-02-22 17:44:13 -08:00
epriestley
fbf050167e Stop exposing raw "accountID" values directly in the web UI
Summary:
Ref T13493. The "AuthAccountView" UI element currently exposes raw account ID values, but I'm trying to make these many-to-one.

This isn't terribly useful as-is, so get rid of it. This element could use a design refresh in general.

Test Plan: Viewed the UI element in "External Accounts".

Maniphest Tasks: T13493

Differential Revision: https://secure.phabricator.com/D21010
2020-02-22 17:41:55 -08:00
epriestley
149155ee20 Whitelist "vscode://" as an allowed Editor protocol
Summary:
See PHI1647, which asks for "vscode://" to be a configurable protocol on hosted Phacility instances.

I made the configuration editable in D21008, but this can reasonably just come upstream too.

Test Plan: Viewed config in Config, set my editor URI to `vscode://blahblah`.

Differential Revision: https://secure.phabricator.com/D21009
2020-02-20 12:45:35 -08:00
epriestley
29923cc71a Remove old code for sending email to external users who create objects via inbound mail
Summary:
Ref T13493. I'm updating callers to `getAccountID()` to prepare to move it to a separate table.

This callsite once supported this flow:

  - External users with no accounts send mail to `bugs@`.
  - This creates tasks in Maniphest.
  - They're CC'd when the tasks are updated.

However, after T12237 we never actually send this mail (since their addresses are necessarily unverified).

I left this code in in case this needed to be revisited, but it hasn't been an issue. Just remove it and treat these users as undeliverable.

Test Plan: As a cursory test for nothing being horribly broken, sent some object mail.

Maniphest Tasks: T13493

Differential Revision: https://secure.phabricator.com/D21007
2020-02-20 12:41:51 -08:00
epriestley
64cc4fe915 Add a test to verify that all routing maps are plausibly valid, and remove some dead routes
Summary:
Previously, see D20999. See also <https://discourse.phabricator-community.org/t/the-phutil-library-phutil-has-not-been-loaded/3543/>.

There are a couple of dead "Config" routes after recent changes. Add test coverage to make sure routes all point somewhere valid, then remove all the dead routes that turned up.

Test Plan: Ran tests, saw failures. Removed dead routes, got clean tests.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Differential Revision: https://secure.phabricator.com/D21000
2020-02-14 18:06:24 -08:00
epriestley
4790a3d94b Stop trying to version-check libphutil in "Config"
Summary:
Ref T13395. No library with this name loads any more, so we can't version check it.

(Ideally, the version check stuff would be more graceful when it fails now, since it's required to load "Config" after I moved it off a separate page.)

Test Plan: Loaded "Config".

Maniphest Tasks: T13395

Differential Revision: https://secure.phabricator.com/D20995
2020-02-14 08:44:50 -08:00
epriestley
356d9e8e19 Update a Phabricator -> Arcanist include path for scripts in Phabricator
Summary: Ref T13395. Since there's very little code which really makes sense in "scripts/", I've moved most of it to other places.

Test Plan: Ran `bin/phd`.

Maniphest Tasks: T13395

Differential Revision: https://secure.phabricator.com/D20994
2020-02-14 08:32:26 -08:00
epriestley
dc35ce79e4 Unprototype "Draft" mode in Differential
Summary: Fixes T2543. This mode has been a stable prototype for a very long time now; promote it so "--draft" can promote out of "experimental" in Arcanist.

Test Plan: See T2543 for discussion.

Maniphest Tasks: T2543

Differential Revision: https://secure.phabricator.com/D20983
2020-02-12 16:20:39 -08:00
epriestley
35a18146a2 Merge a small amount of remaining "libphutil/" code with Phabricator, break libphutil dependency
Summary: Ref T13395. Moves a small amount of remaining "libphutil/" code into "phabricator/" and stops us from loading "libphutil/".

Test Plan: Browsed around; there are likely remaining issues.

Maniphest Tasks: T13395

Differential Revision: https://secure.phabricator.com/D20981
2020-02-12 15:17:36 -08:00
epriestley
f9b3e3360b Continue moving classes with no callers in libphutil or Arcanist to Phabricator
Summary: Ref T13395. Move cache classes, syntax highlighters, other markup classes, and sprite sheets to Phabricator.

Test Plan: Attempted to find any callers for any of this stuff in libphutil or Arcanist and couldn't.

Maniphest Tasks: T13395

Differential Revision: https://secure.phabricator.com/D20977
2020-02-12 13:14:04 -08:00
Arturas Moskvinas arturas@uber.com
8cc6fe465c Fix diffusion.branchquery returning dictionary instead of array when branches are filtered out
Summary:
`diffusion.branchquery` can return dictionary instead of array if some branches are filtered out.
Eg.:
```
{
  "result": [
    {
      "shortName": "master",
      "commitIdentifier": "2817b0d8f79748ddfad0220c46d9b20bea34f460",
      "refType": "branch",
      "rawFields": {
        "objectname": "2817b0d8f79748ddfad0220c46d9b20bea34f460",
        "objecttype": "commit",
```
might become:
```
{
  "result": {
    "1": {
      "shortName": "master",
      "commitIdentifier": "2817b0d8f79748ddfad0220c46d9b20bea34f460",
      "refType": "branch",
      "rawFields": {
        "objectname": "2817b0d8f79748ddfad0220c46d9b20bea34f460",
        "objecttype": "commit",

```
Reproduction - find repository which has couple of branches, setup to track only some of them, execute `diffusion.branchquery` API call - result is dictionary instead of array

Test Plan: Apply patch, execution `diffusion.branchquery` call - result is no longer dictionary if it was one before

Reviewers: #blessed_reviewers, epriestley

Reviewed By: #blessed_reviewers, epriestley

Subscribers: Korvin, epriestley

Differential Revision: https://secure.phabricator.com/D20973
2020-02-12 11:50:22 -08:00
epriestley
af84f215f9 Move lingering "Aphront" classes to Phabricator
Summary: Ref T13395. Moves some Aphront classes from libphutil to Phabricator.

Test Plan: Grepped for symbols in libphutil and Arcanist.

Maniphest Tasks: T13395

Differential Revision: https://secure.phabricator.com/D20975
2020-02-12 11:50:14 -08:00
epriestley
2327578adc Respect linebreaks in full HTML tables in Remarkup
Summary:
Fixes T5427. See PHI1630. See also T13160 and D20568.

In the full HTML table syntax with "<table>", respect linebreaks as literals inside "<td>" cells.

Test Plan: Previewed some full-HTML tables with and without linebreaks, saw what seemed like sensible rendering behavior.

Maniphest Tasks: T5427

Differential Revision: https://secure.phabricator.com/D20971
2020-02-06 15:01:16 -08:00
epriestley
9d1af762d5 In summary interfaces, don't render very large inline remarkup details for unit test messages
Summary: Ref T10635. An install with large blocks of remarkup (4MB) in test details is reporting slow page rendering. This is expected, but I've mostly given up on fighting this unless I absolutely have to. Degrade the interface more aggressively.

Test Plan:
  - Submitted a large block of test details in remarkup format.
  - Before patch: they rendered inline.
  - After patch: degraded display.
  - Verified small blocks are not changed.

{F7180727}

{F7180728}

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T10635

Differential Revision: https://secure.phabricator.com/D20970
2020-02-05 14:26:38 -08:00
epriestley
fd46c597ae When sorting subscriber references for display in the curtain UI, sort without case sensitivity
Summary: Ref T13486. Currently, "Zarbo" sorts above "alice", but this isn't expected for a list of (mostly) human usernames.

Test Plan: Loaded a task with subscribers with mixed-case usernames.

Maniphest Tasks: T13486

Differential Revision: https://secure.phabricator.com/D20969
2020-02-04 15:26:05 -08:00
epriestley
fdbe9ba149 Improve Remarkup parsing performance for certain large input blocks
Summary: Fixes T13487. In PHI1628, an install has a 4MB remarkup corpus which takes a long time to render. This is broadly expected, but a few reasonable improvements fell out of running it through the profiler.

Test Plan:
  - Saw local cold-cache end-to-end rendering time drop from 12s to 4s for the highly secret input corpus.
  - Verified output has the same hashes before/after.
  - Ran all remarkup unit tests.

Maniphest Tasks: T13487

Differential Revision: https://secure.phabricator.com/D20968
2020-02-04 15:07:00 -08:00
epriestley
0e82bd024a Use the new "CurtainObjectRefList" UI element for subscribers
Summary:
Depends on D20966. Ref T13486. Curtains currently render subscribers in a plain text list, but the new ref list element is a good fit for this.

Also, improve the sorting and ordering behavior.

This makes the subscriber list take up a bit more space, but it should make it a lot easier to read at a glance.

Test Plan: Viewed object subscriber lists at varying limits and subscriber counts, saw sensible subscriber lists.

Maniphest Tasks: T13486

Differential Revision: https://secure.phabricator.com/D20967
2020-02-04 12:38:41 -08:00
epriestley
2a92fef879 Improve wrapping and overflow behavior for curtain panels containing long usernames
Summary:
Ref T13486. When a curtain element like "Author" in Maniphest has a very long username, the wrapping and overflow behavior is poor: the date is obscured.

Adjust curtain elements which contain lists of references to other objects to improve wrapping behavior (put the date on a separate line) and overflow behavior (so we get a "..." when a name overflows).

Test Plan: {F7179376}

Maniphest Tasks: T13486

Differential Revision: https://secure.phabricator.com/D20966
2020-02-04 12:31:18 -08:00
epriestley
84fd5cd5bb Fix an issue where intracontent empty lines were incorrectly trimmed in quoted blocks
Summary: Fixes T13335. When processing quoted blocks, we remove leading empty lines. This logic incorrectly continued after encountering a nonempty line.

Test Plan: Added a test, made it pass. Previewed blocks in web UI.

Maniphest Tasks: T13335

Differential Revision: https://secure.phabricator.com/D20965
2020-02-04 08:09:50 -08:00
epriestley
0f1acb6cef Update GitHub API calls to use "Authorization" header instead of "access_token" URI parameter
Summary: Fixes T13485. GitHub has deprecated the "access_token" URI parameter for API authentication. Update to "Authorization: token ...".

Test Plan: Linked and unlinked a GitHub account locally.

Maniphest Tasks: T13485

Differential Revision: https://secure.phabricator.com/D20964
2020-02-04 07:58:03 -08:00
epriestley
6d4c6924d6 Update Herald rule creation workflow to use more modern UI elements
Summary: Ref T13480. Creating a rule in Herald currently uses the older radio-button flow. Update it to the "clickable menu" flow to simplify it a little bit.

Test Plan: Created new personal, object, and global rules. Hit the object rule error conditions.

Maniphest Tasks: T13480

Differential Revision: https://secure.phabricator.com/D20956
2020-02-04 07:37:54 -08:00
epriestley
4904d7711e When publishing a commit, copy "Related Tasks" from the associated revision (if one exists)
Summary:
Fixes T13463. Currently, if you use the web UI to set "Related Tasks" for a revision, the resulting commit does not link to the tasks.

If you use "Ref ..." in the message instead, the resulting commit does link to the tasks.

Broadly, this should all be cleaner (see T3577) but we can step toward better behavior by just copying these edges when commits are published.

Test Plan:
  - Created a revision.
  - Used the web UI to edit "Related Tasks".
  - Landed the revision.
  - Saw the commit link to the tasks as though I'd used "Ref ..." in the message.

Maniphest Tasks: T13463

Differential Revision: https://secure.phabricator.com/D20961
2020-02-04 07:05:09 -08:00
epriestley
530145ba3b Give "Config" a full-width, hierarchical layout
Summary:
Depends on D20933. Ref T13362. This reorganizes Config a bit and attempts to simplify it.

Subsections are now in a landing page console and groupings have been removed. We "only" have 75 values you can edit from the web UI nowadays, which is still a lot, but less overwhelming than it was in the past. And the trend is generally downward, as config is removed/simplified or moved into application settings.

This also gets rid of the "gigantic blobs of JSON in the UI".

Test Plan: Browsed all Config sections.

Maniphest Tasks: T13362

Differential Revision: https://secure.phabricator.com/D20934
2020-02-04 06:59:51 -08:00
epriestley
26c2a1ba68 Move existing "Console" interfaces away from "setFixed(...)" on "TwoColumnView"
Summary: Depends on D20931. Ref T13362. Move all "Console"-style interfaces to use a consistent layout based on a new "LauncherView" which just centers the content.

Test Plan: Viewed all affected interfaces.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13362

Differential Revision: https://secure.phabricator.com/D20933
2020-02-04 06:52:23 -08:00
epriestley
cb481f36c5 Carve out a separate "Services" section of Config
Summary: Depends on D20930. Ref T13362. Put all the "Services" parts of Config in their own section.

Test Plan: Clicked through each section. This is just an organization / UI change with no significant behavioral impact.

Maniphest Tasks: T13362

Differential Revision: https://secure.phabricator.com/D20931
2020-02-04 06:47:31 -08:00
epriestley
a72ade9475 Carve out a separate "Modules/Extensions" section of Config
Summary:
Ref T13362. Config is currently doing a ton of stuff and fairly overwhelming. Separate out "Modules/Extensions" so it can live in its own section.

(This stuff is mostly useful for development and normal users rarely need to end up here.)

Test Plan: Visited seciton, clicked around. This is just a visual change.

Maniphest Tasks: T13362

Differential Revision: https://secure.phabricator.com/D20930
2020-02-04 06:41:55 -08:00
5ba0687123 Show number of query result for advanced queries 2019-12-24 11:18:19 +01:00
fef207bf00 Fix task counter query
Was doing wrong count when multiple projects were involved.
Also pagination was not ignored as it should have.

This technically increases load on the database, so maybe
we need to upgrade server to handle real thing we want to be
displayed.
2019-12-23 18:15:35 +01:00
5ef3552549 Correct link to new addons 2019-12-23 11:52:02 +01:00
0f6ca4be21 Tweaked wording and links on the front page 2019-12-23 10:42:55 +01:00
4653348b13 Maniphest: Switch from custom type field to task subtype 2019-12-18 10:33:12 +01:00
6826284108 Maniphest: Remove blender-specific type rendering
Will be rendered using task's subtype.
2019-12-18 09:53:53 +01:00
c93fdca80d Merge branch 'master' into blender-tweaks 2019-12-17 13:02:09 +01:00
432b6d61a6 Show correct command to destroy users 2019-10-17 11:25:53 +02:00
4c2b7d7573 Merge branch 'master' into blender-tweaks 2019-10-17 11:23:01 +02:00
d8e25a6226 Correct shebang to be usable in the deployed environment 2019-10-03 15:27:02 +02:00
db6f4eaab1 Merge branch 'master' into blender-tweaks 2019-10-01 14:48:19 +02:00
4555f18c41 Add auth provider which uses Phabricator
The idea is to use it instead of a custom field approach.
2019-09-30 17:23:36 +02:00
788d8a8b98 Remove local hack preserving line breaks
This seems to be addressed by upstream nowadays.
2019-09-30 17:21:03 +02:00
55f22f74f5 Revert "Fix for missing updates of hidden properties"
More correct fix is being worked on.

Reverting to an older state, to be able to deploy other changes.

This reverts commit 8efe191897.
2019-09-30 17:17:19 +02:00
de2f7e857d Fix wrong method visibility
Was discovered by `arc unit`, caused by wrong merge conflict resolution.
2019-09-30 16:59:37 +02:00
7c869ab033 Update arc libreate
Seems there is no need to run it for extensions.

Solves error reported by `arc unit`.
2019-09-30 16:59:29 +02:00
f9c680ddc0 Merge branch 'upstream_master' into blender-tweaks 2019-09-30 10:52:47 +02:00
8efe191897 Fix for missing updates of hidden properties 2019-09-30 10:50:51 +02:00
8d6f1a6128 Proper commenting style. 2019-09-24 00:44:08 +03:00
81bae35d2f Render milestones as original getDisplayName. 2019-09-24 00:44:08 +03:00
fc5744c7f0 Show entire path for all projects. 2019-09-24 00:44:08 +03:00
7aea82d526 Clean up ancestor path implementation.
* mpull for cleaner ancestor access
* move implementation to PhabricatorProject
  * as new function getDisplayNameWithAncestorPath
  * no pht usage, since there are no localisable items
2019-09-24 00:44:08 +03:00
cb29054e66 Show ancestor path for sub-projects and milestones.
To understand better to what projects a sub-project or
milestone belongs show the entire ancestor path.

Solves part of T69306.

Differential Revision: https://developer.blender.org/D5656
2019-09-24 00:44:08 +03:00
0416648b39 Merge branch 'master' into blender-tweaks 2019-09-18 10:42:01 +02:00
01fea69352 Limit some workflow elements to moderators
Allows to streamline triaging and keeping track of reports status,
together with removing confusing elements which non-moderators can
not use anyway.
2019-09-03 15:38:59 +02:00
c7a2133639 Differential: fill author field from user account
Do it unless this information was provided by `arc diff`.
2019-09-03 14:43:43 +02:00
a162f188c2 Differential: exclude fields from commit message to follow our guidelines
Do it as a tweaks to render due to:

- Those fields are considered permanent.
- We still want to be able to specify them when creating new revisions
  (at least for subscribers/reviewers).
2019-09-03 14:36:00 +02:00
2b54804c7b Merge branch 'master' into blender-tweaks 2019-09-03 14:24:06 +02:00
a8fef30871 Merge branch 'master' into blender-tweaks 2019-09-02 10:19:50 +02:00
9dc4d903c7 Ignore disabled users for Git/SVN synchronization 2019-08-01 16:51:02 +02:00
b8b18995c5 Merge branch 'master' into blender-tweaks 2019-06-20 10:02:20 +02:00
971b397133 Merge branch 'master' into blender-tweaks 2019-06-19 10:37:08 +02:00
aee7dc94bc More fixes for MFA 2019-06-18 16:18:29 +02:00
feb8db1212 Fix non-vector sorting used on a vector 2019-06-18 10:44:00 +02:00
f9f7f005f2 Fix task count query not working 2019-06-18 09:05:19 +02:00
2321a886ae Merge branch 'master' into blender-tweaks 2019-06-18 08:29:51 +02:00
72858c6746 Wrap task count selection part to a query string 2019-06-18 08:22:30 +02:00
79ca5a3026 Update top bar to match other websites 2019-06-18 08:17:32 +02:00
3d37998174 Update diff guidelines, login and welcome text 2019-06-17 16:11:02 +02:00
ce29e40b57 Merge branch 'master' into blender-tweaks 2019-04-25 14:19:49 +02:00
ef26d6afdc Merge branch 'master' into blender-tweaks 2019-04-04 11:21:47 +02:00
dc84bc9391 Allow committing to SVN branches 2019-03-15 16:45:19 +01:00
321eb18cd7 Fix SVN rules synchronization script 2019-02-05 12:40:35 +01:00
6936b8c4f3 Ignore hidden fields in the feed 2019-02-04 15:15:43 +01:00
26f43ee9b8 Merge branch 'master' into blender-tweaks 2019-02-04 14:17:46 +01:00
e19d318af9 Merge branch 'master' into blender-tweaks 2019-01-25 15:35:33 +01:00
dfc7f764de Merge branch 'master' into blender-tweaks 2018-12-26 11:44:27 +01:00
331b4cc42c Use better suitable policy for the previous commits 2018-12-11 10:07:43 +01:00
674d537f91 Similar tweaks for priority and status 2018-12-11 10:01:28 +01:00
a442a019d9 Allow assigning task to anyone who can view the task
Matches old behavior prior to a recent update.
2018-12-11 09:58:32 +01:00
774a554405 Post-merge fix for API usage 2018-12-09 17:21:40 +01:00
90f56e07ff Attempt to fix usage of wrong class 2018-12-09 13:28:47 +01:00
4b428ffb8a Merge branch 'master' into blender-tweaks 2018-12-09 13:25:53 +01:00
bcd0caecd1 Merge branch 'master' into blender-tweaks 2018-12-04 10:29:38 +01:00
78c32afa4e Merge branch 'master' into blender-tweaks 2018-10-22 12:31:30 +02:00
f3302fe1b9 Grant fiel editing and deletion to admins 2018-08-03 11:58:08 +02:00
7eba06c43d Merge branch 'master' into blender-tweaks 2018-08-02 14:26:08 +02:00
713f8e2c6a Merge branch 'master' into blender-tweaks 2018-04-24 12:56:12 +02:00
ea0100310b Fix issue with double-slash added to maniphest URLs 2018-04-16 12:36:11 +02:00
4332bfe537 Merge branch 'master' into blender-tweaks 2018-04-16 12:29:33 +02:00
25ce2bdd92 Merge branch 'master' into blender-tweaks 2018-02-09 11:03:26 +01:00
dc17ef4f7c Merge branch 'master' into blender-tweaks 2018-01-29 09:40:46 +01:00
8097508909 Merge branch 'master' into blender-tweaks 2018-01-29 09:39:57 +01:00
8addc33734 Add state of the art fax defence into the source tree 2017-12-07 11:24:51 +01:00
990136a337 Grant profile editing to administrators 2017-12-07 09:32:02 +01:00
d14b204aae Merge branch 'master' into blender-tweaks 2017-12-06 16:58:17 +01:00
4067829d2c Fix/workaround for code review not working for logged out users 2017-10-19 07:50:39 +02:00
8bc4c8c485 Merge branch 'master' into blender-tweaks 2017-10-18 13:29:21 +02:00
bf8aec2a14 Merge branch 'master' into blender-tweaks 2017-10-02 10:51:32 +02:00
ace2787868 Fix T52676: Phabricator 'To Do' URL format and search error 2017-09-18 14:13:39 +02:00
08dde1da13 Merge branch 'master' into blender-tweaks 2017-09-08 15:50:36 +02:00
4cde7a4b06 Correct previous commit 2017-04-27 16:27:47 +02:00
42ce8a82af Fix hidden fields were isible in conduit 2017-04-27 16:16:35 +02:00
6d6e446f6f Merge branch 'github-facebook-master' into blender-tweaks 2017-04-27 16:14:16 +02:00
26bf667fec Fix unwanted caption for real now
Hopefully.
2017-03-29 13:25:16 +02:00
18b9c9b0b1 Attempt to remove Summary from differencial template 2017-03-29 12:12:47 +02:00
f78ca8833b Update generator script to the latest changes in Phabricator 2017-03-27 10:12:55 +02:00
19337de8f0 Merge branch 'master' into blender-tweaks 2017-03-20 09:28:31 +01:00
c2a4fbc98c Followup commit, allow changing task project tags 2017-03-15 16:04:22 +01:00
87c0e13efd Attempt to solve regressions in our policy rules
Users weher able to triage other's reports.
2017-03-15 12:13:18 +01:00
3442097b31 Attempt to fix arcanist diff 2017-03-14 15:31:10 +01:00
c7764ed1d0 Quick hack to restore old main page title 2017-03-14 13:58:08 +01:00
6e39022af9 Merge branch 'master' into blender-tweaks 2017-03-09 12:13:16 +01:00
d88716feba Merge branch 'blender-tweaks' of git.blender.org:phabricator into blender-tweaks 2017-01-20 14:13:04 +01:00
678347d836 Merge branch 'master' into blender-tweaks 2017-01-20 14:12:45 +01:00
890ccea846 Use separate form for addons bug 2016-12-02 09:48:16 +01:00
405525357d Fix T50155: Maniphest could provide better information on task reject
Use bug report form when reporting bug from the project page in maniphest
2016-12-02 09:39:48 +01:00
3eee2a3311 Fix typo from previous commit 2016-07-18 14:48:56 +02:00
55d1f0081d Fix links in the diff submission guidelines 2016-07-18 14:46:06 +02:00
ea73d9bf86 Fix bug accessing user manage form 2016-07-07 16:20:29 +02:00
3eab15180c Fix bugs accessing user preferences 2016-07-07 16:13:07 +02:00
2210be45fe Make it possible to create new tasks with Blender's policies 2016-07-07 12:17:07 +02:00
5bdc3dfad8 Merge branch 'master' into blender-tweaks 2016-07-01 09:16:15 +02:00
c22440fe30 Merge branch 'master' into blender-tweaks 2016-05-20 11:26:25 +02:00
b41844290b Merge branch 'master' into blender-tweaks 2016-04-06 09:39:20 +02:00
32a3440156 Merge branch 'blender-tweaks' of git.blender.org:phabricator into blender-tweaks 2016-03-15 18:25:38 +05:00
630eb3c143 Post-merge blender tweaks 2016-03-15 18:25:26 +05:00
534b628ad2 Update mobile 'home' page links
If you use phabricator with a mobile phone with small screen space
you get only the sidebar at http://developer.blender.org/.

If you the click the 'Home - Command Center' Button available for
mobile devices you get the site http://developer.blender.org/home/

But there, the short-cut links will result in a 404 page (or login,
if you are not logged in) so better make the links there absolute.

Patch by Leon (Leon95), thanks!

Reviewers: sergey

Subscribers: sergey

Projects: #phabricator, #infrastructure:_websites

Differential Revision: https://developer.blender.org/D1843
2016-03-14 11:37:40 +05:00
8a463524b4 Use own Phab install for arc
This way we can apply patches submitted to our tracker.
2016-03-14 11:36:24 +05:00
b50eefbd5b Merge branch 'master' into blender-tweaks 2016-03-05 16:19:51 +05:00
3a2ebeca11 Merge branch 'github-facebook-master' into blender-tweaks 2015-12-24 22:50:19 +05:00
95f7da61a9 Remove unneeded custom configuration options 2015-12-18 19:55:22 +05:00
0d04beb777 Switch maniphest to sue new phab forms instead of some hardcoded fields etc 2015-12-18 15:46:18 +05:00
7ab26ff7af Bring back comment about drag-drop in the re-markup edit field. 2015-12-18 15:29:49 +05:00
6800fc6103 Preserve line breaks for the instruction fields 2015-12-18 15:15:59 +05:00
9077c1f24f Merge branch 'master' into blender-tweaks
Still some work to be done bring back some removed customizations,
but they can be done using new awesome phab features.
2015-12-18 15:04:15 +05:00
2327f2af6f Show maniphest task type in the list 2015-12-17 23:06:46 +05:00
41fff9c930 Fix alignment in the maniphest list
"Assigned To" and "By" fields were too short.
2015-11-13 18:11:21 +05:00
cfcef40c65 Merge branch 'master' into blender-tweaks 2015-11-13 17:46:32 +05:00
29d158a3bc Don't show list of members in the project's details page
This only adds clutter, plus we've got dedicated page for members.
2015-11-13 17:34:41 +05:00
411cf324dd Linking to project will go to project's details page, not to the board
Board is not so much helpful to see for average users.
2015-11-13 17:30:36 +05:00
52230a803f Merge branch 'github-facebook-master' into blender-tweaks 2015-11-13 17:20:03 +05:00
ddf6f275db Show login in the user monogram
This is actually appeared handy after discussion with campbell.
2015-06-03 15:13:29 +05:00
8c65ee54d0 Make user monograms surviving cases when there's no real name 2015-06-03 14:59:41 +05:00
d07d44fcc7 Remove debug-only code from the previous commit 2015-06-03 14:34:39 +05:00
772d59f0ab Make real name more primary way of communication 2015-06-03 14:32:32 +05:00
f7da3f88f9 Fix T44841: Error when clicking Maniphest in the phabricator sidebar 2015-05-26 13:12:07 +05:00
e4f6cf9993 Attempt to fix issues with task counter returning several rows 2015-05-26 12:58:32 +05:00
dd34c93656 Put gruping back to the task counter sql 2015-05-26 12:35:44 +05:00
320ffa2308 Fix buildbot application
Was using deprecated class.
2015-05-18 15:27:46 +05:00
b5e7136f68 Update celerity map 2015-05-18 01:31:51 +05:00
e2b7664451 Use different widths for dashboard and regular maniphest task 2015-05-18 01:31:22 +05:00
725ad07e6f Attempt to make both welcome screen and maniphest task list happy with word wrapping 2015-05-18 01:27:59 +05:00
75cbd69d85 One more tweak, sorry for the trouble 2015-05-18 01:02:59 +05:00
a9a71ef011 Tweak to previous commit
Previous commit made it so task title is cropper far too soon.
Still not really ideal solution, but should be good enough for now.
2015-05-18 01:00:29 +05:00
362ba36695 Fix authorship not fitting into maniphest task in the list 2015-05-18 00:39:58 +05:00
38d687ede2 Attempt to make default repositories sorting by name 2015-05-17 23:39:55 +05:00
78cd1cf494 Remove results of wrong merge 2015-05-17 22:49:57 +05:00
0b990d42cd Add Blender Next to always visible projects 2015-05-17 22:14:53 +05:00
74571de5b6 Deduplicate some code, saved/unsaved query title is handling in getDescriptionForQuery 2015-05-17 21:47:22 +05:00
01ce61f6e2 Update text of addon patch submission 2015-05-17 21:40:14 +05:00
09c9aa078d Tweaks to the home file to make it wrap nicely 2015-05-17 21:36:00 +05:00
73441d690e Merge branch 'master' into blender-tweaks
Conflicts:
	resources/celerity/map.php
	src/applications/config/option/PhabricatorCoreConfigOptions.php
	src/applications/differential/controller/DifferentialDiffCreateController.php
	src/applications/files/storage/PhabricatorFile.php
	src/applications/maniphest/query/ManiphestTaskQuery.php
	src/applications/maniphest/query/ManiphestTaskSearchEngine.php
	src/applications/repository/query/PhabricatorRepositorySearchEngine.php
	src/applications/search/controller/PhabricatorApplicationSearchController.php
2015-05-17 21:24:32 +05:00
afdc21ee04 Merge branch 'master' into blender-tweaks
Conflicts:
	resources/celerity/map.php
2015-03-06 20:28:53 +05:00
fc317b7f28 Move extension applications to where they are expected to be 2015-03-06 20:21:53 +05:00
b3f2b7faf4 Use a bit closer to previous installation icon for buildbot application 2015-03-06 20:19:45 +05:00
7ea70dcb53 Make home page much closer to the upstream
Mainly get rid of hardcoded feed panel, this can be done with
dashboards now.
2015-03-06 19:27:42 +05:00
d0b90fdef7 Replace all favicon files with blender logo 2015-03-06 19:13:16 +05:00
3c05d31d2e Merge branch 'master' into blender-tweaks
Conflicts:
	.gitignore
	resources/celerity/map.php
	src/__phutil_library_map__.php
	src/applications/home/controller/PhabricatorHomeMainController.php
	src/applications/maniphest/controller/ManiphestSubscribeController.php
	src/applications/maniphest/event/ManiphestActionMenuEventListener.php
	src/applications/maniphest/query/ManiphestTaskQuery.php
	webroot/rsrc/css/application/base/main-menu-view.css
	webroot/rsrc/css/sprite-menu.css
2015-02-27 13:23:38 +05:00
11f5eb21af Merge branch 'master' into blender-tweaks
Conflicts:
	resources/celerity/map.php
	src/applications/differential/controller/DifferentialDiffCreateController.php
2014-11-29 18:37:00 +05:00
c90665645e Gitosis sync fixes and improvements
- Fixed some remaining issues caused by the update process.
- Support custom rules for the repositories.
  Currnetly only supports allowing users.
2014-11-10 01:38:58 +05:00
fb3c71820b Fix rebuild gitadmin using old user typeconst class 2014-11-10 01:16:32 +05:00
e533774ac0 Some post-merge tweaks, regenerated celerity map 2014-11-08 18:38:00 +05:00
647f8de9bb Merge branch 'master' into blender-tweaks
Conflicts:
	resources/celerity/map.php
	webroot/favicon.ico
	webroot/rsrc/css/application/base/main-menu-view.css
	webroot/rsrc/css/sprite-menu.css
2014-11-08 18:37:40 +05:00
599bf57cd8 Merge branch 'master' into blender-tweaks
Conflicts:
	resources/celerity/map.php
	src/applications/maniphest/controller/ManiphestTaskListController.php
2014-11-01 17:23:50 +05:00
50c506ea2d Merge branch 'master' into blender-tweaks
Conflicts:
	conf/default.conf.php
	resources/celerity/map.php
2014-10-11 20:22:36 +06:00
4cc7990b1a Attempt to fix bad url to To Do tasks in maniphest 2014-10-03 02:35:19 +06:00
4ef5fdefb2 Experiment with showing both real and login names
This way it's still easy to reference people with @ and makes
it more natural communication using real name if needed.
2014-10-03 02:32:57 +06:00
a018837214 Revert "Show real name rather than the nick name in the interface all over the place"
This reverts commit e74f0e1558.

Showing just a name makes it impossible to reference people in a comments using @.
2014-10-01 11:27:36 +06:00
e74f0e1558 Show real name rather than the nick name in the interface all over the place 2014-09-30 18:06:15 +06:00
fd882b5167 Merge branch 'master' into blender-tweaks
Conflicts:
	resources/celerity/map.php
2014-09-30 14:31:44 +06:00
15bda62b89 Fix shouldAllowPublic method redeclaration 2014-09-23 13:34:54 +06:00
5e4ce96266 Fix some more issues after the merge 2014-09-23 00:57:46 +06:00
dbd2f0d06f Fix bad conflict resolve
Also fix wrong push capability check.
2014-09-23 00:23:56 +06:00
c87e657412 Fix wrong navigation from custom query to hidden project 2014-09-22 19:29:02 +06:00
6275e3f08f Merge branch 'master' into blender-tweaks
Conflicts:
	resources/celerity/map.php
	src/__phutil_library_map__.php
2014-09-21 19:41:03 +06:00
bc2e4a2452 Always Blender Regressions project in the maniphest projects list 2014-09-14 16:14:53 +06:00
ba7d9fb724 Merge branch 'master' into blender-tweaks
Conflicts:
	resources/celerity/map.php
	src/__phutil_library_map__.php
	src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
	src/applications/project/controller/PhabricatorProjectListController.php
2014-09-14 16:12:27 +06:00
c50ab96abb tweak to hidden custom field 2014-07-28 01:39:05 +06:00
fa68c6a0fa Preserve line breaks in projects descriptions
This is slightly better fix than previous commit, but
we might totally revisit it as a part of

  https://secure.phabricator.com/D10014
2014-07-22 21:17:35 +06:00
d1b60fec48 Revert "Preserve lone breaks in projects description"
This reverts commit 87e0a26d24.
2014-07-22 20:51:24 +06:00
99a839605c Fix wrong source for projects
Also made code lcoser to master.
2014-07-22 20:31:31 +06:00
08f5b3e920 Merge branch 'master' into blender-tweaks 2014-07-22 20:20:57 +06:00
87e0a26d24 Preserve lone breaks in projects description 2014-07-22 15:12:17 +06:00
91c07e9f99 Merge branch 'master' into blender-tweaks
Conflicts:
	resources/celerity/map.php
	src/applications/maniphest/controller/ManiphestTaskEditController.php
2014-07-22 13:33:45 +06:00
9b2b75e4b4 Workaround for regular users not being able to report new tasks
This is caused by the self-locking check which fails in our usecase.
2014-07-22 13:03:23 +06:00
77c913c70a Correct key name usage in the previous commit 2014-07-20 16:48:47 +06:00
1a1bea527c Attempt to fix issue with sending new differencial revision to review 2014-07-20 16:43:32 +06:00
46f8f6d90d Pin buildbot and documentation by default 2014-07-20 03:59:29 +06:00
dbad3b6489 Quick fix for the maniphest navigation 2014-07-20 03:40:17 +06:00
1821cedbc4 Show unconfirmed project link 2014-07-20 03:14:05 +06:00
1448eafd64 Use HTTPS link to the buildbot 2014-07-20 03:11:59 +06:00
8f4047b36a Merge branch 'master' into blender-tweaks
Conflicts:
	resources/celerity/map.php
	src/applications/home/controller/PhabricatorHomeMainController.php
	src/applications/maniphest/controller/ManiphestTaskEditController.php
	src/applications/maniphest/query/ManiphestTaskQuery.php
	src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php
2014-07-17 01:33:33 +06:00
2683fde42a Remove hacks around custom task status
Seems it's no longer needed since statuses are actually
configurable via UI configurator.
2014-06-06 18:12:12 +06:00
02522d877d Tweak for storage migration script to update status for archived tasks 2014-06-06 18:06:13 +06:00
769ffa2982 Restore hack which allowed importing projects.b.o passwords 2014-06-06 17:29:39 +06:00
33eed14141 Page title got lost 2014-06-06 15:33:08 +06:00
a0bd47a012 Correction to hidden custom fields 2014-06-06 15:12:55 +06:00
8c14ef97bf Get rid of custom code in home page
Seems it's no longer needes.
2014-06-06 15:03:48 +06:00
5bdfbe8250 Attempt to fix maniphest tak editing form 2014-06-06 14:50:09 +06:00
f2319c9689 Don't show archived projects in maniphest sidebar 2014-06-06 14:34:38 +06:00
c3138c6497 Merge branch 'master' into blender-tweaks
Conflicts:
	resources/celerity/map.php
	src/__phutil_library_map__.php
	src/applications/auth/application/PhabricatorApplicationAuth.php
	src/applications/base/controller/PhabricatorController.php
	src/applications/differential/customfield/DifferentialLintField.php
	src/applications/differential/customfield/DifferentialUnitField.php
	src/applications/differential/field/specification/DifferentialManiphestTasksFieldSpecification.php
	src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php
	src/applications/feed/application/PhabricatorApplicationFeed.php
	src/applications/home/controller/PhabricatorHomeController.php
	src/applications/home/controller/PhabricatorHomeMainController.php
	src/applications/maniphest/constants/ManiphestTaskStatus.php
	src/applications/maniphest/controller/ManiphestController.php
	src/applications/maniphest/controller/ManiphestTaskDetailController.php
	src/applications/maniphest/controller/ManiphestTaskEditController.php
	src/applications/maniphest/controller/ManiphestTaskListController.php
	src/applications/maniphest/event/ManiphestActionMenuEventListener.php
	src/applications/maniphest/query/ManiphestTaskQuery.php
	src/applications/maniphest/query/ManiphestTaskSearchEngine.php
	src/applications/maniphest/view/ManiphestTaskProjectsView.php
	src/applications/people/storage/PhabricatorUser.php
	src/applications/project/controller/PhabricatorProjectListController.php
	src/applications/project/controller/PhabricatorProjectProfileController.php
	src/applications/search/controller/PhabricatorApplicationSearchController.php
	src/applications/settings/controller/PhabricatorSettingsMainController.php
	src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php
	src/view/phui/PHUIPropertyListView.php
	webroot/rsrc/css/sprite-menu.css
2014-06-05 17:07:20 +06:00
56e8308621 SVN auth script: enable commit access to /tags folder 2014-03-27 18:18:56 +06:00
cb6a8756b6 Migration: remove files and changes from gforge migration.
These were moved to blender-migration branch.
2014-02-03 13:35:20 +01:00
e7242143a0 Merge: more tweaks to project page for when there is no description or no members. 2014-02-03 13:19:26 +01:00
c12898b63a Merge: restore project description to more prominent location again. 2014-02-03 13:07:10 +01:00
9453f29deb Merge branch 'master' into blender-tweaks
Conflicts:
	scripts/celerity_mapper.php
	src/__celerity_resource_map__.php
	src/applications/differential/controller/DifferentialDiffCreateController.php
	src/applications/diffusion/controller/DiffusionBrowseFileController.php
	src/applications/diffusion/controller/DiffusionRepositoryEditPolicyController.php
	src/applications/files/storage/PhabricatorFile.php
	src/applications/maniphest/controller/ManiphestTaskListController.php
	src/applications/project/controller/PhabricatorProjectController.php
	src/applications/project/controller/PhabricatorProjectProfileController.php
	src/applications/repository/storage/PhabricatorRepository.php
	webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js
2014-02-03 15:42:43 +06:00
9093816113 Phabricator: restore reviewers and CCs field in commit messages, they are needed
for arc diff.
2014-01-24 17:58:08 +01:00
3471c1b87e Fix T37772: Tab indentation missing in Diffusion
This seems to be a specific of how browsers are dealing with
spaces/tabs. Multiple spaces works just fine, but multiple
tabs were treating as a single space which breaks indentation.

Now made it so tabs are replaced with 4 spaces. Not ideal but
still better than fully unreadable code.
2014-01-17 17:05:14 +06:00
30f0de9427 Fix T38210: phabricator file downloads not working for non-logged in users. 2014-01-15 15:53:50 +01:00
5278e94566 Further arc commit messages tweak to follow guidelines, leave out CCs,
Reviewers (not Reviewed By), Maniphest Tasks and Summary label.
2014-01-04 05:37:58 +01:00
08b1e9d764 Fix buildbot link not working after move to https, redirect instead now. 2014-01-04 05:13:51 +01:00
30427e0626 Leave reviewers and CCs out of commit message. 2014-01-04 05:13:28 +01:00
752a5b9bc3 Further tweak to bug report page descriptions. 2013-12-29 18:25:34 +01:00
a5390d5461 Improve bug report page descriptions. 2013-12-29 18:23:02 +01:00
a02fa19d5a Fixed typo in previous commit, no functional changes. 2013-12-25 17:44:36 +06:00
f88bdcd4ff Truncate file to 10 sec on display with diffusion
Displaying huge files like .po used loads of CPU power
and might have easily run out of time or memory.

Now file will be truncated after 10 sec of view generation.
2013-12-25 17:34:24 +06:00
b98f8a570c Added a way to generate login=>(email, real name) map
Also fixed a typo in custom field in user change password function.
2013-11-29 22:58:33 +06:00
644070340d Add list of all archived repositories 2013-11-28 15:39:25 +06:00
93fecfe7e9 Added list of archived repositories which RO access to them
Currently only bf-extensions, rest of the repos will arrive soon.
2013-11-28 15:19:32 +06:00
53bb3ac361 Make sure all the users have RO access to the repo 2013-11-28 15:09:37 +06:00
137df8c6d3 Make sure there's no trailing slash in svn's subpath
This gives issues with svn access file, leading to
access denied by the server.
2013-11-28 14:57:24 +06:00
9863285bf0 Fix missing project action list on mobile. 2013-11-27 20:47:30 +01:00
a392b4a14f Fix typo in svn authentification generator script 2013-11-28 00:21:59 +06:00
d03569e908 Correction for the previous commit 2013-11-27 22:20:55 +06:00
b1097e9c61 Fix for crash when pushable project have unknown users
Apparently we've got Unknown Object (Phabricator User) in
translation project now.

Not sure how it became invalid, but automated scripts
better be robust for this.
2013-11-27 22:14:07 +06:00
5bda6bf48b Attempt to make SVN happy with untrusted server certificate
Need this to fetch changes from our svn.b.o which have untrusted
issuer of the certificate.
2013-11-27 19:36:13 +06:00
a03cdacd9e Tweaks to svn access rights importer
- Make it work with repositories outside of svn.b.o.
  Useful for local debugging.

- Make it aware of svn subpath repository setting.
  So now it's possible to restrict commit rights
  to a subpath in svn repository.

- Make SVN repositories always available for
  read-only access;
2013-11-27 18:37:37 +06:00
c3fe33f5a0 Remove debug-only code from svn auth script 2013-11-27 02:25:47 +06:00
5d823ff1a7 Fix T37436: Add a method to show the bug count
Now query header will show text like:

  N total tasks for query "Foo"
2013-11-22 00:24:14 +06:00
63df9dd1f4 Undo test commit. 2013-11-21 19:10:36 +01:00
0c80c9fead Add "Open Revisions" builtin query for differential, and have links for both
code reviews and patches on welcome page.
2013-11-21 19:05:38 +01:00
b8d42a7cc6 Test commit for disabling merge commits. 2013-11-21 19:05:38 +01:00
4e7dc30fea Fix typo in method name 2013-11-21 23:17:13 +06:00
99c68b1091 Fix T37529: show up to 4 project tags for maniphest tasks instead of 2. 2013-11-19 16:14:03 +01:00
d48979091e Maniphest: order open tasks by date created and don't group by priority, to
make it more like the old bug tracker. Also makes count bugs fairly easy by
just looking at the number on the second or third page.
2013-11-17 10:21:38 +01:00
785caa5b6c Test commit for mail hooks, would prefer to do not it on live install but
that seems to be the only place it fails.
2013-11-16 04:22:04 +01:00
261d7331f4 Remove some unnecessary code from last commit. 2013-11-16 02:49:55 +01:00
28bd8409df Remove unnecessary duplicate buildbot crumb. 2013-11-16 02:44:17 +01:00
47008a157c Browse Patches pointed to a wrong project 2013-11-16 00:00:02 +06:00
fc1e8681ef Add application name in crumbs display. 2013-11-15 17:30:28 +01:00
17085b4379 Try to clarify that the select projects listed on the home page are not all
projects.
2013-11-15 17:30:28 +01:00
dd6eefcd9c Make "Active" projects the default instead of "Joined". 2013-11-15 17:30:28 +01:00
ae2b0e63d6 Further tweaks to gi ermissions script
- Seems @all didn't work, now use explicit list of repos
- Handle cases when phab user uses the same key as used for
  "system" repositories.
2013-11-15 20:19:20 +06:00
bcbae097de Another attempt to make phab sync working 2013-11-15 18:55:16 +06:00
b59dca4eeb Made it so public keys are stored in files with .pub extension
It is an attempt to solve issues with missing permissions on
pahabricator users.
2013-11-15 18:49:56 +06:00
8098798fa9 Fix T37435: clicking maniphest next button with no /query in the URL would lose
the default query and show the search form on the second page.

This was a bug in Phabricator itself, just not as likely to happen because we
have the default query as Open Tasks while they have Assigned, which is unlikely
to give more than 100 results and so there would be no next button.
2013-11-15 02:46:28 +01:00
d8d0e2c99f Fix T37447: Add a link to project list when creating tasks. 2013-11-15 00:32:18 +01:00
214233f82f Show author name in task list views.
Fixes T37451.
2013-11-15 00:04:00 +01:00
eebe82c990 Assume 4 space for tabs by default in differential. 2013-11-14 23:27:09 +01:00
5b73071d64 Removed debug-only left-over 2013-11-15 02:29:03 +06:00
cd04de7f72 Order diffusion repositories by name (so Blender is at the top). 2013-11-14 19:44:47 +01:00
cf4b1317e1 Fix T37441: Could not assign tasks to self (via the "Assigned To" box)
Issue was caused by our database having login "watch" which is
apparently an object's method in firefox.

Added workaround for this into the code.
2013-11-14 21:24:26 +06:00
10e353b090 Allow anyone to login to developer.blender.org again. 2013-11-14 12:15:23 +01:00
81bd756ec6 Also correct sheband for svn auth script 2013-11-14 13:34:55 +06:00
9527d8aafd tweak to shebang so cron is happy with it 2013-11-14 13:31:50 +06:00
e35bc58e0e Fixes for gitosis admin script
Basically, pull wasn't happening correct and it was
updating files from CWD< not from workdir.
2013-11-14 12:44:59 +06:00
bd0f9fd913 Add sculpting and websites projects to welcome page. 2013-11-13 19:53:22 +01:00
44957f7806 Get repsitory name from svn URI
Would only work for repos inside svn.b.o.
Some further checks might be needed.
2013-11-14 00:21:04 +06:00
7ff26eb95d Show message that developer.blender.org is not open yet for logins. 2013-11-13 19:10:10 +01:00
1f2471fa67 Use git repository name from uri. not from repo name 2013-11-14 00:09:40 +06:00
7a5588a408 Appeared a typo in previous commmit
Didn't notice on system where had htaccess custom field.
2013-11-13 14:37:47 +06:00
ac03ae737f Fix for recent htaccess hack broke user registration
Issue was caused by custom fields loading will set
user's profile. In case user is new, this profile
will have title and blurb set to NULL, which is
unsupported.

Solved by setting title and blurb to an empty string
if setPassword detects that this is a new user.

tested with accountadin script, need to be tested
with online registration as well.
2013-11-13 14:35:29 +06:00
f9db95f555 Add a missing translations project member. 2013-11-13 09:06:25 +01:00
2bc045a997 * Add another duplicate email
* Remove addon testing tmp code
* Make files public viewable
2013-11-13 09:03:09 +01:00
531b0fa529 Added script to generate svn auth files
This script only generates content of either svnroot-access
or svnroot-authfile (depending on command line arguments).

It's not directly usable yet and would need some magic to
make it able to pass the data to svn.b.o. But this is up to
machines setup, not to the script.
2013-11-12 23:47:17 +06:00
6b1ed4fb9b Seems we need to load custom field manully 2013-11-12 23:46:10 +06:00
645a6a9f54 Made rebuild_gitadmin only take git repositories into account 2013-11-12 22:43:01 +06:00
88cd291b0f Only allow admins to login and disable new account registration.
This is a temporary change to avoid accidental interference during import tomorrow.
2013-11-12 14:55:36 +01:00
76e4aaf429 Import unix passwords for svn access, and fix passwrod/password typo. 2013-11-12 14:29:03 +01:00
365f8ad981 Added instructions about htaccess_passowrd_hash hidden field 2013-11-12 18:10:06 +06:00
0af08f9469 Added hack to user class to set htaccess hash when setting password
Uses hash generation from gforge code. Wouldn't work correct for
imported users yet.

TODO: Imported users are to use hash from projects.b.o's database.
2013-11-12 18:02:37 +06:00
23ce658af7 Made it possible to mark custom fields as hidden
it is done using "hidden": true setting in custom
field specification.

Hidden fields will never be displayed and their
intention is to be used by autohmatization scripts.
2013-11-12 18:02:37 +06:00
0a54474f98 Better Submit Addon page. 2013-11-11 21:00:30 +01:00
93ca9ed3a8 Update docs for migration 2013-11-12 00:58:04 +06:00
d110fdccc2 Added options to disable lint and unit tests in diffusion 2013-11-12 00:53:39 +06:00
4197d983bf Fix 404 after clicking Find Owners in diffusion, since we have this application disabled. 2013-11-11 17:49:03 +01:00
d23883b799 Update design task and todo task instructions. 2013-11-09 15:46:03 +01:00
675d2ac374 Link to addon docs on submit patch page. 2013-11-09 15:25:03 +01:00
5c86735ab7 On projects pages, add Submit Patch, Add To Do, Add Design Task links. It's all
a bit hardcoded but makes the expected workflow easier to understand.
2013-11-09 15:12:40 +01:00
566274dcac Patch/diff html pages updates. 2013-11-08 22:01:38 +01:00
de2814545b Add Submit Patch, Create Design Task and Create To Do actions in Maniphest when
browsing the corresponding.
2013-11-08 21:01:07 +01:00
4ea26f22c4 Add html instructions for diff/patch, todo and design tasks. 2013-11-08 20:55:53 +01:00
458bc7f921 Update task import script to handle addons. 2013-11-07 18:30:39 +01:00
3839698e4a Remove unnecessary whitespace. 2013-11-07 16:54:39 +01:00
b403863e62 Correction to documentation, extra bachslashes 2013-11-07 20:41:56 +06:00
246b73f922 Fix type in previous commit, sorry 2013-11-07 20:39:56 +06:00
6da7f462a9 Fix for documentation 2013-11-07 20:36:43 +06:00
90874b8b00 Links which goes to different site better open pages in new tab 2013-11-07 20:33:52 +06:00
7e874a33fb Added instructions to patch submission form. 2013-11-07 20:33:51 +06:00
bbe357e5c0 Add [developer.blender.org] in mail subject instead of only [Maniphest], [Diffusion], etc.
For casual users it's important to be able to see where the message comes from.
2013-11-07 20:33:51 +06:00
428cdae3cf Further tweaks to gitosis conf rebuild script
- Pull need SSH key as well
- Repository names are ensured to be lower case
- Use verbose name for commti author
2013-11-07 20:33:51 +06:00
1c689a6b24 Correct shebang for gitx-ssh 2013-11-07 20:33:51 +06:00
55972ef2c1 Gitadmin configuration now could be commited and pushed from update script 2013-11-07 20:33:51 +06:00
966687d1ab Project pages now have a Report Bug link, and View Tasks now uses the project
filter rather than setting up a query.
2013-11-07 20:33:51 +06:00
6a86144adf Another update to migration steps. 2013-11-07 20:33:51 +06:00
57765e88c0 Update migration steps with mailhook.
This commit also tests the mailhook itself, if all goes well.
2013-11-07 20:33:51 +06:00
60cddad047 Whitespace cleanup 2013-11-07 20:33:51 +06:00
a77a33dc50 Rework gitosis config generator to use pushable settings from repository 2013-11-07 20:33:51 +06:00
405a07e638 Made 'pushable' of repository editable for non-hosted repos as well 2013-11-07 20:33:51 +06:00
75f9ce9d77 Missed this in previous 'revert changes' commit 2013-11-07 20:33:50 +06:00
259588e747 Added script which rebuilds gitadmin configuration
It generates new gitosis.conf and public key files.
Actual commit to repo would likely be done with a
wrapper script which will run in cron.
2013-11-07 20:33:50 +06:00
0e8b746fb0 Tweaks to page and mail titles
- Welcome page will now have title "Blender Foundation: Welcome",
  the same as in old projects.b.o.
- Mail will be sent from name of BF, not phabricator, seems to
  be quite logical to do.
2013-11-07 20:33:50 +06:00
753917c10c Revert changes in AphrontFormPolicyControl.php
After recent Brecht's commit they're not needed.

Euh, didn't notice policies could be edited in configuration :(
2013-11-07 20:33:50 +06:00
39f7364f25 Tweaks to maniphest
- Remove quick links to create tasks, now only
  "Report Bug" to a specified project is allowed.
- Added 'Addons' to list of 'always visible'
  in navigation bar projects.
2013-11-07 20:33:50 +06:00
8d7de5fcb6 Tasks: don't force particular policy in the code here, let it be defined by the
default policies configured for maniphest.
2013-11-07 20:33:50 +06:00
01398f762b Better looks and css for welcome.html. For reference, __celerity_resource_map__.php
was updated with this command.

./scripts/celerity_mapper.php --with-custom webroot
2013-11-07 20:33:50 +06:00
e12d2efe81 Add buildbot and developer wiki links in the menu. 2013-11-07 20:33:23 +06:00
5c7055d840 Reshuffle order of builtin queries 2013-11-07 20:33:23 +06:00
562c7aa126 Use blender.org's favicon 2013-11-07 20:33:22 +06:00
5994088e50 Tweaks to welcome and bug report pages, making them a bit prettier and hopefully
more clear.
2013-11-07 20:33:22 +06:00
9edbe22e19 Update welcome.html with correct project IDs. 2013-11-07 20:33:22 +06:00
7fabfff397 Update migration steps with info from test run of importing to developer.blender.org. 2013-11-07 20:33:22 +06:00
356a2fe0e8 Rework task submission form
- It is possible now to submit tasks to specified project.
- All the extra fields which are not interesting got us
  are now hidden.
- Default visibility is User (maybe we might want
  "Everyone" in fact)?
- Only Administrators are able to edit issues by
  default.
- Links to bug report are available from welcome page
  and from project's maniphest page.
- Added file with report bug guidelines. Currently
  just copy-pasted it from project.b.o, some style
  tweaks are welcome.
2013-11-07 20:33:22 +06:00
54193ebb15 Add link to all types of tasks within single project 2013-11-07 20:33:22 +06:00
2b4bb9cddd Highlight current task type in side menu
So now Bug,Patch,etc categories are always visible
and active one is being highlighted.
2013-11-07 20:33:22 +06:00
2758907517 Add some new duplicate email users for migration. 2013-11-07 20:33:22 +06:00
a7f641eee2 Allow public access to main page, with login button in the top right, and show
activity feed on the main page.
2013-11-07 20:33:22 +06:00
9799ceff80 More fixes for migration, in particular for setting policies on projects and tasks. 2013-11-07 20:33:22 +06:00
26e86c2171 Add missing files and minor migration tweaks. 2013-11-07 20:33:22 +06:00
f6c293fb4d Migration tweaks, and patch to temporarily disable sending mails and updating feeds
during import of data.
2013-11-07 20:33:22 +06:00
5abc5cf9f8 Lots of little fixes for import, status should be imported mostly OK now.
A dded a migration_steps.txt that outlines the steps to migrate data.

Still todo:
* Extensions/addons, skipped now, will do next week
* Hide some unnecessary transactions
* Figure out policies for tasks and projects
* Test run over all tasks to see that they work
2013-11-07 20:33:22 +06:00
41a820fdf0 Tweak appearance of projects page to show description more prominent, and
minor tweaks to closed status and welcome html margins.
2013-11-07 20:33:22 +06:00
2c8b09da21 Welcome page tweaks (links are mostly broken, but shows proposed structure). 2013-11-07 20:33:22 +06:00
a7ff526360 Migration: add system to warn users with merged accounts to use the other account,
and provide an option to change the username to their choice after login.
2013-11-07 20:33:21 +06:00
1a8148be97 Correct link to blender bug tracker 2013-11-07 20:33:21 +06:00
199dd85140 Show project and task type in query result header
This is intended to make it more clear which task
types are currently being displayed.

Needed to revoke "final" modifier from class
PhabricatorApplicationSearchController and make
it use separate method to get description for
this.
2013-11-07 20:33:21 +06:00
f6c44fdb03 Hide inactive projects and add button to show them all
For now uses stupid hardcoded list of "interesting"
projects. Might be changed to something smarter later.
2013-11-07 20:33:21 +06:00
1aade72cb1 Add extra filter level to filter tasks by type 2013-11-07 20:33:21 +06:00
121c9d308b Fix migration script error due to changed phab API. 2013-11-07 20:33:21 +06:00
d60a47ab5b Add migration/dump to gitignore so it's not commited by accident 2013-11-07 20:33:21 +06:00
8af9b8796f Fkx for syntax error, unexpected T_OBJECT_OPERATOR 2013-11-07 20:33:21 +06:00
7dd7673fb3 Added per-project categories to the side bar of maniphest
Currently adds all the projects there, and at this moment
current project is not being highlighted, it's only get
displayed on the top anchor bar.

TODO:
- We would probably wouldn't want all the projects there,
  need to add some filter there.
- Make it ore clear which project is currently active.
2013-11-07 20:33:21 +06:00
aeaf0902e1 Migration scripts in somewhat better state now, main missing part is that statuses
are not imported correctly, though there's a bunch of details to figure out.
2013-11-07 20:33:21 +06:00
498842475c Initial scripts for migration of data from gforge to phabricator.
Import of tasks is still a hacked together mess from before I figured out the
structure that we want.
2013-11-07 20:33:21 +06:00
755177508e Modify LiskDAO to support overriding ID and dates, used for import of data. 2013-11-07 20:33:21 +06:00
5b49fd93e2 Don't show edit button for tasks if editing is not allowed 2013-11-07 20:33:21 +06:00
968ea35b03 Use blender logo in header 2013-11-07 20:33:21 +06:00
97ed90b82a Fix for tiy typo in previous commit 2013-11-07 20:33:21 +06:00
ba34aa535a Move custom login form text to an html file
Also committing all the template files needed for
making phabricator closer to how our gforge looks.
2013-11-07 20:33:21 +06:00
e56041da3b Hide "Create Project" if it's not allowed for current user
Having a button which says "You are not allowed to press it"
is pretty much strange behavior. Better not to have such button
at first place.

For now it only affects Projects application. There're also
other places where we might want this change.
2013-11-07 20:33:20 +06:00
b19fbc39af Modification so we can import gforge passwords, by adding an extra md5 to the
password hash. By skipping this step on import, we keep passwords working.
2013-11-07 20:33:20 +06:00
1036d419fa Add "Archived" status for closing tasks. 2013-11-07 20:33:20 +06:00
0011789443 Added welcome.file setting
This setting operates quite the same as welcome.html
except for the difference that it points to a file
from which to get the HTML code.

It's more convenient to use file with HTML code if
one need to display rather long welcome screen.
2013-11-07 20:33:20 +06:00
ff7d71fb87 Make install_ubuntu.sh work for Debian as well 2013-11-07 20:33:20 +06:00
670 changed files with 31582 additions and 9373 deletions

View File

@@ -1,5 +1,5 @@
{ {
"phabricator.uri": "https://secure.phabricator.com/", "phabricator.uri": "https://developer.blender.org/",
"load": ["src/"], "load": ["src/"],
"history.immutable": false "history.immutable": false
} }

View File

@@ -64,13 +64,13 @@
"text": { "text": {
"type": "text", "type": "text",
"exclude": [ "exclude": [
"(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json))" "(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json|expect))"
] ]
}, },
"text-without-length": { "text-without-length": {
"type": "text", "type": "text",
"include": [ "include": [
"(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json))" "(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json|expect))"
], ],
"severity": { "severity": {
"3": "disabled" "3": "disabled"

3
.gitignore vendored
View File

@@ -40,3 +40,6 @@
# Places for users to add custom resources. # Places for users to add custom resources.
/resources/cows/custom/* /resources/cows/custom/*
/resources/figlet/custom/* /resources/figlet/custom/*
# blender migration files
migration/dump/

View File

@@ -1,722 +0,0 @@
<?php
/**
*
* Copyright (c) 2011, Dan Myers.
* Parts copyright (c) 2008, Donovan Schonknecht.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* This is a modified BSD license (the third clause has been removed).
* The BSD license may be found here:
* http://www.opensource.org/licenses/bsd-license.php
*
* Amazon Simple Email Service is a trademark of Amazon.com, Inc. or its affiliates.
*
* SimpleEmailService is based on Donovan Schonknecht's Amazon S3 PHP class, found here:
* http://undesigned.org.za/2007/10/22/amazon-s3-php-class
*
*/
/**
* Amazon SimpleEmailService PHP class
*
* @link http://sourceforge.net/projects/php-aws-ses/
* version 0.8.1
*
*/
class SimpleEmailService
{
protected $__accessKey; // AWS Access key
protected $__secretKey; // AWS Secret key
protected $__host;
public function getAccessKey() { return $this->__accessKey; }
public function getSecretKey() { return $this->__secretKey; }
public function getHost() { return $this->__host; }
protected $__verifyHost = 1;
protected $__verifyPeer = 1;
// verifyHost and verifyPeer determine whether curl verifies ssl certificates.
// It may be necessary to disable these checks on certain systems.
// These only have an effect if SSL is enabled.
public function verifyHost() { return $this->__verifyHost; }
public function enableVerifyHost($enable = true) { $this->__verifyHost = $enable; }
public function verifyPeer() { return $this->__verifyPeer; }
public function enableVerifyPeer($enable = true) { $this->__verifyPeer = $enable; }
// If you use exceptions, errors will be communicated by throwing a
// SimpleEmailServiceException. By default, they will be trigger_error()'d.
protected $__useExceptions = 0;
public function useExceptions() { return $this->__useExceptions; }
public function enableUseExceptions($enable = true) { $this->__useExceptions = $enable; }
/**
* Constructor
*
* @param string $accessKey Access key
* @param string $secretKey Secret key
* @return void
*/
public function __construct($accessKey = null, $secretKey = null, $host = 'email.us-east-1.amazonaws.com') {
if (!function_exists('simplexml_load_string')) {
throw new Exception(
pht(
'The PHP SimpleXML extension is not available, but this '.
'extension is required to send mail via Amazon SES, because '.
'Amazon SES returns API responses in XML format. Install or '.
'enable the SimpleXML extension.'));
}
// Catch mistakes with reading the wrong column out of the SES
// documentation. See T10728.
if (preg_match('(-smtp)', $host)) {
throw new Exception(
pht(
'Amazon SES is not configured correctly: the configured SES '.
'endpoint ("%s") is an SMTP endpoint. Instead, use an API (HTTPS) '.
'endpoint.',
$host));
}
if ($accessKey !== null && $secretKey !== null) {
$this->setAuth($accessKey, $secretKey);
}
$this->__host = $host;
}
/**
* Set AWS access key and secret key
*
* @param string $accessKey Access key
* @param string $secretKey Secret key
* @return void
*/
public function setAuth($accessKey, $secretKey) {
$this->__accessKey = $accessKey;
$this->__secretKey = $secretKey;
}
/**
* Lists the email addresses that have been verified and can be used as the 'From' address
*
* @return An array containing two items: a list of verified email addresses, and the request id.
*/
public function listVerifiedEmailAddresses() {
$rest = new SimpleEmailServiceRequest($this, 'GET');
$rest->setParameter('Action', 'ListVerifiedEmailAddresses');
$rest = $rest->getResponse();
$response = array();
if(!isset($rest->body)) {
return $response;
}
$addresses = array();
foreach($rest->body->ListVerifiedEmailAddressesResult->VerifiedEmailAddresses->member as $address) {
$addresses[] = (string)$address;
}
$response['Addresses'] = $addresses;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Requests verification of the provided email address, so it can be used
* as the 'From' address when sending emails through SimpleEmailService.
*
* After submitting this request, you should receive a verification email
* from Amazon at the specified address containing instructions to follow.
*
* @param string email The email address to get verified
* @return The request id for this request.
*/
public function verifyEmailAddress($email) {
$rest = new SimpleEmailServiceRequest($this, 'POST');
$rest->setParameter('Action', 'VerifyEmailAddress');
$rest->setParameter('EmailAddress', $email);
$rest = $rest->getResponse();
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Removes the specified email address from the list of verified addresses.
*
* @param string email The email address to remove
* @return The request id for this request.
*/
public function deleteVerifiedEmailAddress($email) {
$rest = new SimpleEmailServiceRequest($this, 'DELETE');
$rest->setParameter('Action', 'DeleteVerifiedEmailAddress');
$rest->setParameter('EmailAddress', $email);
$rest = $rest->getResponse();
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Retrieves information on the current activity limits for this account.
* See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendQuota.html
*
* @return An array containing information on this account's activity limits.
*/
public function getSendQuota() {
$rest = new SimpleEmailServiceRequest($this, 'GET');
$rest->setParameter('Action', 'GetSendQuota');
$rest = $rest->getResponse();
$response = array();
if(!isset($rest->body)) {
return $response;
}
$response['Max24HourSend'] = (string)$rest->body->GetSendQuotaResult->Max24HourSend;
$response['MaxSendRate'] = (string)$rest->body->GetSendQuotaResult->MaxSendRate;
$response['SentLast24Hours'] = (string)$rest->body->GetSendQuotaResult->SentLast24Hours;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Retrieves statistics for the last two weeks of activity on this account.
* See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendStatistics.html
*
* @return An array of activity statistics. Each array item covers a 15-minute period.
*/
public function getSendStatistics() {
$rest = new SimpleEmailServiceRequest($this, 'GET');
$rest->setParameter('Action', 'GetSendStatistics');
$rest = $rest->getResponse();
$response = array();
if(!isset($rest->body)) {
return $response;
}
$datapoints = array();
foreach($rest->body->GetSendStatisticsResult->SendDataPoints->member as $datapoint) {
$p = array();
$p['Bounces'] = (string)$datapoint->Bounces;
$p['Complaints'] = (string)$datapoint->Complaints;
$p['DeliveryAttempts'] = (string)$datapoint->DeliveryAttempts;
$p['Rejects'] = (string)$datapoint->Rejects;
$p['Timestamp'] = (string)$datapoint->Timestamp;
$datapoints[] = $p;
}
$response['SendDataPoints'] = $datapoints;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
public function sendRawEmail($raw) {
$rest = new SimpleEmailServiceRequest($this, 'POST');
$rest->setParameter('Action', 'SendRawEmail');
$rest->setParameter('RawMessage.Data', base64_encode($raw));
$rest = $rest->getResponse();
$response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Given a SimpleEmailServiceMessage object, submits the message to the service for sending.
*
* @return An array containing the unique identifier for this message and a separate request id.
* Returns false if the provided message is missing any required fields.
*/
public function sendEmail($sesMessage) {
if(!$sesMessage->validate()) {
return false;
}
$rest = new SimpleEmailServiceRequest($this, 'POST');
$rest->setParameter('Action', 'SendEmail');
$i = 1;
foreach($sesMessage->to as $to) {
$rest->setParameter('Destination.ToAddresses.member.'.$i, $to);
$i++;
}
if(is_array($sesMessage->cc)) {
$i = 1;
foreach($sesMessage->cc as $cc) {
$rest->setParameter('Destination.CcAddresses.member.'.$i, $cc);
$i++;
}
}
if(is_array($sesMessage->bcc)) {
$i = 1;
foreach($sesMessage->bcc as $bcc) {
$rest->setParameter('Destination.BccAddresses.member.'.$i, $bcc);
$i++;
}
}
if(is_array($sesMessage->replyto)) {
$i = 1;
foreach($sesMessage->replyto as $replyto) {
$rest->setParameter('ReplyToAddresses.member.'.$i, $replyto);
$i++;
}
}
$rest->setParameter('Source', $sesMessage->from);
if($sesMessage->returnpath != null) {
$rest->setParameter('ReturnPath', $sesMessage->returnpath);
}
if($sesMessage->subject != null && strlen($sesMessage->subject) > 0) {
$rest->setParameter('Message.Subject.Data', $sesMessage->subject);
if($sesMessage->subjectCharset != null && strlen($sesMessage->subjectCharset) > 0) {
$rest->setParameter('Message.Subject.Charset', $sesMessage->subjectCharset);
}
}
if($sesMessage->messagetext != null && strlen($sesMessage->messagetext) > 0) {
$rest->setParameter('Message.Body.Text.Data', $sesMessage->messagetext);
if($sesMessage->messageTextCharset != null && strlen($sesMessage->messageTextCharset) > 0) {
$rest->setParameter('Message.Body.Text.Charset', $sesMessage->messageTextCharset);
}
}
if($sesMessage->messagehtml != null && strlen($sesMessage->messagehtml) > 0) {
$rest->setParameter('Message.Body.Html.Data', $sesMessage->messagehtml);
if($sesMessage->messageHtmlCharset != null && strlen($sesMessage->messageHtmlCharset) > 0) {
$rest->setParameter('Message.Body.Html.Charset', $sesMessage->messageHtmlCharset);
}
}
$rest = $rest->getResponse();
$response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Trigger an error message
*
* @internal Used by member functions to output errors
* @param array $error Array containing error information
* @return string
*/
public function __triggerError($functionname, $error)
{
if($error == false) {
$message = sprintf("SimpleEmailService::%s(): Encountered an error, but no description given", $functionname);
}
else if(isset($error['curl']) && $error['curl'])
{
$message = sprintf("SimpleEmailService::%s(): %s %s", $functionname, $error['code'], $error['message']);
}
else if(isset($error['Error']))
{
$e = $error['Error'];
$message = sprintf("SimpleEmailService::%s(): %s - %s: %s\nRequest Id: %s\n", $functionname, $e['Type'], $e['Code'], $e['Message'], $error['RequestId']);
}
if ($this->useExceptions()) {
throw new SimpleEmailServiceException($message);
} else {
trigger_error($message, E_USER_WARNING);
}
}
/**
* Callback handler for 503 retries.
*
* @internal Used by SimpleDBRequest to call the user-specified callback, if set
* @param $attempt The number of failed attempts so far
* @return The retry delay in microseconds, or 0 to stop retrying.
*/
public function __executeServiceTemporarilyUnavailableRetryDelay($attempt)
{
if(is_callable($this->__serviceUnavailableRetryDelayCallback)) {
$callback = $this->__serviceUnavailableRetryDelayCallback;
return $callback($attempt);
}
return 0;
}
}
final class SimpleEmailServiceRequest
{
private $ses, $verb, $parameters = array();
public $response;
/**
* Constructor
*
* @param string $ses The SimpleEmailService object making this request
* @param string $action action
* @param string $verb HTTP verb
* @return mixed
*/
function __construct($ses, $verb) {
$this->ses = $ses;
$this->verb = $verb;
$this->response = new STDClass;
$this->response->error = false;
}
/**
* Set request parameter
*
* @param string $key Key
* @param string $value Value
* @param boolean $replace Whether to replace the key if it already exists (default true)
* @return void
*/
public function setParameter($key, $value, $replace = true) {
if(!$replace && isset($this->parameters[$key]))
{
$temp = (array)($this->parameters[$key]);
$temp[] = $value;
$this->parameters[$key] = $temp;
}
else
{
$this->parameters[$key] = $value;
}
}
/**
* Get the response
*
* @return object | false
*/
public function getResponse() {
$params = array();
foreach ($this->parameters as $var => $value)
{
if(is_array($value))
{
foreach($value as $v)
{
$params[] = $var.'='.$this->__customUrlEncode($v);
}
}
else
{
$params[] = $var.'='.$this->__customUrlEncode($value);
}
}
sort($params, SORT_STRING);
// must be in format 'Sun, 06 Nov 1994 08:49:37 GMT'
$date = gmdate('D, d M Y H:i:s e');
$query = implode('&', $params);
$headers = array();
$headers[] = 'Date: '.$date;
$headers[] = 'Host: '.$this->ses->getHost();
$auth = 'AWS3-HTTPS AWSAccessKeyId='.$this->ses->getAccessKey();
$auth .= ',Algorithm=HmacSHA256,Signature='.$this->__getSignature($date);
$headers[] = 'X-Amzn-Authorization: '.$auth;
$url = 'https://'.$this->ses->getHost().'/';
// Basic setup
$curl = curl_init();
curl_setopt($curl, CURLOPT_USERAGENT, 'SimpleEmailService/php');
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, ($this->ses->verifyHost() ? 2 : 0));
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, ($this->ses->verifyPeer() ? 1 : 0));
// Request types
switch ($this->verb) {
case 'GET':
$url .= '?'.$query;
break;
case 'POST':
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
curl_setopt($curl, CURLOPT_POSTFIELDS, $query);
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
break;
case 'DELETE':
$url .= '?'.$query;
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
break;
default: break;
}
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
// Execute, grab errors
if (!curl_exec($curl)) {
throw new SimpleEmailServiceException(
pht(
'Encountered an error while making an HTTP request to Amazon SES '.
'(cURL Error #%d): %s',
curl_errno($curl),
curl_error($curl)));
}
$this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($this->response->code != 200) {
throw new SimpleEmailServiceException(
pht(
'Unexpected HTTP status while making request to Amazon SES: '.
'expected 200, got %s.',
$this->response->code));
}
@curl_close($curl);
// Parse body into XML
if ($this->response->error === false && isset($this->response->body)) {
$this->response->body = simplexml_load_string($this->response->body);
// Grab SES errors
if (!in_array($this->response->code, array(200, 201, 202, 204))
&& isset($this->response->body->Error)) {
$error = $this->response->body->Error;
$output = array();
$output['curl'] = false;
$output['Error'] = array();
$output['Error']['Type'] = (string)$error->Type;
$output['Error']['Code'] = (string)$error->Code;
$output['Error']['Message'] = (string)$error->Message;
$output['RequestId'] = (string)$this->response->body->RequestId;
$this->response->error = $output;
unset($this->response->body);
}
}
return $this->response;
}
/**
* CURL write callback
*
* @param resource &$curl CURL resource
* @param string &$data Data
* @return integer
*/
private function __responseWriteCallback(&$curl, &$data) {
if(!isset($this->response->body)) $this->response->body = '';
$this->response->body .= $data;
return strlen($data);
}
/**
* Contributed by afx114
* URL encode the parameters as per http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?Query_QueryAuth.html
* PHP's rawurlencode() follows RFC 1738, not RFC 3986 as required by Amazon. The only difference is the tilde (~), so convert it back after rawurlencode
* See: http://www.morganney.com/blog/API/AWS-Product-Advertising-API-Requires-a-Signed-Request.php
*
* @param string $var String to encode
* @return string
*/
private function __customUrlEncode($var) {
return str_replace('%7E', '~', rawurlencode($var));
}
/**
* Generate the auth string using Hmac-SHA256
*
* @internal Used by SimpleDBRequest::getResponse()
* @param string $string String to sign
* @return string
*/
private function __getSignature($string) {
return base64_encode(hash_hmac('sha256', $string, $this->ses->getSecretKey(), true));
}
}
final class SimpleEmailServiceMessage {
// these are public for convenience only
// these are not to be used outside of the SimpleEmailService class!
public $to, $cc, $bcc, $replyto;
public $from, $returnpath;
public $subject, $messagetext, $messagehtml;
public $subjectCharset, $messageTextCharset, $messageHtmlCharset;
function __construct() {
$to = array();
$cc = array();
$bcc = array();
$replyto = array();
$from = null;
$returnpath = null;
$subject = null;
$messagetext = null;
$messagehtml = null;
$subjectCharset = null;
$messageTextCharset = null;
$messageHtmlCharset = null;
}
/**
* addTo, addCC, addBCC, and addReplyTo have the following behavior:
* If a single address is passed, it is appended to the current list of addresses.
* If an array of addresses is passed, that array is merged into the current list.
*/
function addTo($to) {
if(!is_array($to)) {
$this->to[] = $to;
}
else {
$this->to = array_merge($this->to, $to);
}
}
function addCC($cc) {
if(!is_array($cc)) {
$this->cc[] = $cc;
}
else {
$this->cc = array_merge($this->cc, $cc);
}
}
function addBCC($bcc) {
if(!is_array($bcc)) {
$this->bcc[] = $bcc;
}
else {
$this->bcc = array_merge($this->bcc, $bcc);
}
}
function addReplyTo($replyto) {
if(!is_array($replyto)) {
$this->replyto[] = $replyto;
}
else {
$this->replyto = array_merge($this->replyto, $replyto);
}
}
function setFrom($from) {
$this->from = $from;
}
function setReturnPath($returnpath) {
$this->returnpath = $returnpath;
}
function setSubject($subject) {
$this->subject = $subject;
}
function setSubjectCharset($charset) {
$this->subjectCharset = $charset;
}
function setMessageFromString($text, $html = null) {
$this->messagetext = $text;
$this->messagehtml = $html;
}
function setMessageFromFile($textfile, $htmlfile = null) {
if(file_exists($textfile) && is_file($textfile) && is_readable($textfile)) {
$this->messagetext = file_get_contents($textfile);
}
if(file_exists($htmlfile) && is_file($htmlfile) && is_readable($htmlfile)) {
$this->messagehtml = file_get_contents($htmlfile);
}
}
function setMessageFromURL($texturl, $htmlurl = null) {
$this->messagetext = file_get_contents($texturl);
if($htmlurl !== null) {
$this->messagehtml = file_get_contents($htmlurl);
}
}
function setMessageCharset($textCharset, $htmlCharset = null) {
$this->messageTextCharset = $textCharset;
$this->messageHtmlCharset = $htmlCharset;
}
/**
* Validates whether the message object has sufficient information to submit a request to SES.
* This does not guarantee the message will arrive, nor that the request will succeed;
* instead, it makes sure that no required fields are missing.
*
* This is used internally before attempting a SendEmail or SendRawEmail request,
* but it can be used outside of this file if verification is desired.
* May be useful if e.g. the data is being populated from a form; developers can generally
* use this function to verify completeness instead of writing custom logic.
*
* @return boolean
*/
public function validate() {
if(count($this->to) == 0)
return false;
if($this->from == null || strlen($this->from) == 0)
return false;
if($this->messagetext == null)
return false;
return true;
}
}
/**
* Thrown by SimpleEmailService when errors occur if you call
* enableUseExceptions(true).
*/
final class SimpleEmailServiceException extends Exception {
}

View File

@@ -0,0 +1,769 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd">
<!--
Copyright © 1991-2013 Unicode, Inc.
CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/)
For terms of use, see http://www.unicode.org/copyright.html
-->
<supplementalData>
<version number="$Revision$"/>
<windowsZones>
<mapTimezones otherVersion="7e00402" typeVersion="2016i">
<!-- (UTC-12:00) International Date Line West -->
<mapZone other="Dateline Standard Time" territory="001" type="Etc/GMT+12"/>
<mapZone other="Dateline Standard Time" territory="ZZ" type="Etc/GMT+12"/>
<!-- (UTC-11:00) Coordinated Universal Time-11 -->
<mapZone other="UTC-11" territory="001" type="Etc/GMT+11"/>
<mapZone other="UTC-11" territory="AS" type="Pacific/Pago_Pago"/>
<mapZone other="UTC-11" territory="NU" type="Pacific/Niue"/>
<mapZone other="UTC-11" territory="UM" type="Pacific/Midway"/>
<mapZone other="UTC-11" territory="ZZ" type="Etc/GMT+11"/>
<!-- (UTC-10:00) Aleutian Islands -->
<mapZone other="Aleutian Standard Time" territory="001" type="America/Adak"/>
<mapZone other="Aleutian Standard Time" territory="US" type="America/Adak"/>
<!-- (UTC-10:00) Hawaii -->
<mapZone other="Hawaiian Standard Time" territory="001" type="Pacific/Honolulu"/>
<mapZone other="Hawaiian Standard Time" territory="CK" type="Pacific/Rarotonga"/>
<mapZone other="Hawaiian Standard Time" territory="PF" type="Pacific/Tahiti"/>
<mapZone other="Hawaiian Standard Time" territory="UM" type="Pacific/Johnston"/>
<mapZone other="Hawaiian Standard Time" territory="US" type="Pacific/Honolulu"/>
<mapZone other="Hawaiian Standard Time" territory="ZZ" type="Etc/GMT+10"/>
<!-- (UTC-09:30) Marquesas Islands -->
<mapZone other="Marquesas Standard Time" territory="001" type="Pacific/Marquesas"/>
<mapZone other="Marquesas Standard Time" territory="PF" type="Pacific/Marquesas"/>
<!-- (UTC-09:00) Alaska -->
<mapZone other="Alaskan Standard Time" territory="001" type="America/Anchorage"/>
<mapZone other="Alaskan Standard Time" territory="US" type="America/Anchorage America/Juneau America/Metlakatla America/Nome America/Sitka America/Yakutat"/>
<!-- (UTC-09:00) Coordinated Universal Time-09 -->
<mapZone other="UTC-09" territory="001" type="Etc/GMT+9"/>
<mapZone other="UTC-09" territory="PF" type="Pacific/Gambier"/>
<mapZone other="UTC-09" territory="ZZ" type="Etc/GMT+9"/>
<!-- (UTC-08:00) Baja California -->
<mapZone other="Pacific Standard Time (Mexico)" territory="001" type="America/Tijuana"/>
<mapZone other="Pacific Standard Time (Mexico)" territory="MX" type="America/Tijuana America/Santa_Isabel"/>
<!-- (UTC-08:00) Coordinated Universal Time-08 -->
<mapZone other="UTC-08" territory="001" type="Etc/GMT+8"/>
<mapZone other="UTC-08" territory="PN" type="Pacific/Pitcairn"/>
<mapZone other="UTC-08" territory="ZZ" type="Etc/GMT+8"/>
<!-- (UTC-08:00) Pacific Time (US & Canada) -->
<mapZone other="Pacific Standard Time" territory="001" type="America/Los_Angeles"/>
<mapZone other="Pacific Standard Time" territory="CA" type="America/Vancouver America/Dawson America/Whitehorse"/>
<mapZone other="Pacific Standard Time" territory="US" type="America/Los_Angeles"/>
<mapZone other="Pacific Standard Time" territory="ZZ" type="PST8PDT"/>
<!-- (UTC-07:00) Arizona -->
<mapZone other="US Mountain Standard Time" territory="001" type="America/Phoenix"/>
<mapZone other="US Mountain Standard Time" territory="CA" type="America/Dawson_Creek America/Creston America/Fort_Nelson"/>
<mapZone other="US Mountain Standard Time" territory="MX" type="America/Hermosillo"/>
<mapZone other="US Mountain Standard Time" territory="US" type="America/Phoenix"/>
<mapZone other="US Mountain Standard Time" territory="ZZ" type="Etc/GMT+7"/>
<!-- (UTC-07:00) Chihuahua, La Paz, Mazatlan -->
<mapZone other="Mountain Standard Time (Mexico)" territory="001" type="America/Chihuahua"/>
<mapZone other="Mountain Standard Time (Mexico)" territory="MX" type="America/Chihuahua America/Mazatlan"/>
<!-- (UTC-07:00) Mountain Time (US & Canada) -->
<mapZone other="Mountain Standard Time" territory="001" type="America/Denver"/>
<mapZone other="Mountain Standard Time" territory="CA" type="America/Edmonton America/Cambridge_Bay America/Inuvik America/Yellowknife"/>
<mapZone other="Mountain Standard Time" territory="MX" type="America/Ojinaga"/>
<mapZone other="Mountain Standard Time" territory="US" type="America/Denver America/Boise"/>
<mapZone other="Mountain Standard Time" territory="ZZ" type="MST7MDT"/>
<!-- (UTC-06:00) Central America -->
<mapZone other="Central America Standard Time" territory="001" type="America/Guatemala"/>
<mapZone other="Central America Standard Time" territory="BZ" type="America/Belize"/>
<mapZone other="Central America Standard Time" territory="CR" type="America/Costa_Rica"/>
<mapZone other="Central America Standard Time" territory="EC" type="Pacific/Galapagos"/>
<mapZone other="Central America Standard Time" territory="GT" type="America/Guatemala"/>
<mapZone other="Central America Standard Time" territory="HN" type="America/Tegucigalpa"/>
<mapZone other="Central America Standard Time" territory="NI" type="America/Managua"/>
<mapZone other="Central America Standard Time" territory="SV" type="America/El_Salvador"/>
<mapZone other="Central America Standard Time" territory="ZZ" type="Etc/GMT+6"/>
<!-- (UTC-06:00) Central Time (US & Canada) -->
<mapZone other="Central Standard Time" territory="001" type="America/Chicago"/>
<mapZone other="Central Standard Time" territory="CA" type="America/Winnipeg America/Rainy_River America/Rankin_Inlet America/Resolute"/>
<mapZone other="Central Standard Time" territory="MX" type="America/Matamoros"/>
<mapZone other="Central Standard Time" territory="US" type="America/Chicago America/Indiana/Knox America/Indiana/Tell_City America/Menominee America/North_Dakota/Beulah America/North_Dakota/Center America/North_Dakota/New_Salem"/>
<mapZone other="Central Standard Time" territory="ZZ" type="CST6CDT"/>
<!-- (UTC-06:00) Easter Island -->
<mapZone other="Easter Island Standard Time" territory="001" type="Pacific/Easter"/>
<mapZone other="Easter Island Standard Time" territory="CL" type="Pacific/Easter"/>
<!-- (UTC-06:00) Guadalajara, Mexico City, Monterrey -->
<mapZone other="Central Standard Time (Mexico)" territory="001" type="America/Mexico_City"/>
<mapZone other="Central Standard Time (Mexico)" territory="MX" type="America/Mexico_City America/Bahia_Banderas America/Merida America/Monterrey"/>
<!-- (UTC-06:00) Saskatchewan -->
<mapZone other="Canada Central Standard Time" territory="001" type="America/Regina"/>
<mapZone other="Canada Central Standard Time" territory="CA" type="America/Regina America/Swift_Current"/>
<!-- (UTC-05:00) Bogota, Lima, Quito, Rio Branco -->
<mapZone other="SA Pacific Standard Time" territory="001" type="America/Bogota"/>
<mapZone other="SA Pacific Standard Time" territory="BR" type="America/Rio_Branco America/Eirunepe"/>
<mapZone other="SA Pacific Standard Time" territory="CA" type="America/Coral_Harbour"/>
<mapZone other="SA Pacific Standard Time" territory="CO" type="America/Bogota"/>
<mapZone other="SA Pacific Standard Time" territory="EC" type="America/Guayaquil"/>
<mapZone other="SA Pacific Standard Time" territory="JM" type="America/Jamaica"/>
<mapZone other="SA Pacific Standard Time" territory="KY" type="America/Cayman"/>
<mapZone other="SA Pacific Standard Time" territory="PA" type="America/Panama"/>
<mapZone other="SA Pacific Standard Time" territory="PE" type="America/Lima"/>
<mapZone other="SA Pacific Standard Time" territory="ZZ" type="Etc/GMT+5"/>
<!-- (UTC-05:00) Chetumal -->
<mapZone other="Eastern Standard Time (Mexico)" territory="001" type="America/Cancun"/>
<mapZone other="Eastern Standard Time (Mexico)" territory="MX" type="America/Cancun"/>
<!-- (UTC-05:00) Eastern Time (US & Canada) -->
<mapZone other="Eastern Standard Time" territory="001" type="America/New_York"/>
<mapZone other="Eastern Standard Time" territory="BS" type="America/Nassau"/>
<mapZone other="Eastern Standard Time" territory="CA" type="America/Toronto America/Iqaluit America/Montreal America/Nipigon America/Pangnirtung America/Thunder_Bay"/>
<mapZone other="Eastern Standard Time" territory="US" type="America/New_York America/Detroit America/Indiana/Petersburg America/Indiana/Vincennes America/Indiana/Winamac America/Kentucky/Monticello America/Louisville"/>
<mapZone other="Eastern Standard Time" territory="ZZ" type="EST5EDT"/>
<!-- (UTC-05:00) Haiti -->
<mapZone other="Haiti Standard Time" territory="001" type="America/Port-au-Prince"/>
<mapZone other="Haiti Standard Time" territory="HT" type="America/Port-au-Prince"/>
<!-- (UTC-05:00) Havana -->
<mapZone other="Cuba Standard Time" territory="001" type="America/Havana"/>
<mapZone other="Cuba Standard Time" territory="CU" type="America/Havana"/>
<!-- (UTC-05:00) Indiana (East) -->
<mapZone other="US Eastern Standard Time" territory="001" type="America/Indianapolis"/>
<mapZone other="US Eastern Standard Time" territory="US" type="America/Indianapolis America/Indiana/Marengo America/Indiana/Vevay"/>
<!-- (UTC-04:00) Asuncion -->
<mapZone other="Paraguay Standard Time" territory="001" type="America/Asuncion"/>
<mapZone other="Paraguay Standard Time" territory="PY" type="America/Asuncion"/>
<!-- (UTC-04:00) Atlantic Time (Canada) -->
<mapZone other="Atlantic Standard Time" territory="001" type="America/Halifax"/>
<mapZone other="Atlantic Standard Time" territory="BM" type="Atlantic/Bermuda"/>
<mapZone other="Atlantic Standard Time" territory="CA" type="America/Halifax America/Glace_Bay America/Goose_Bay America/Moncton"/>
<mapZone other="Atlantic Standard Time" territory="GL" type="America/Thule"/>
<!-- (UTC-04:00) Caracas -->
<mapZone other="Venezuela Standard Time" territory="001" type="America/Caracas"/>
<mapZone other="Venezuela Standard Time" territory="VE" type="America/Caracas"/>
<!-- (UTC-04:00) Cuiaba -->
<mapZone other="Central Brazilian Standard Time" territory="001" type="America/Cuiaba"/>
<mapZone other="Central Brazilian Standard Time" territory="BR" type="America/Cuiaba America/Campo_Grande"/>
<!-- (UTC-04:00) Georgetown, La Paz, Manaus, San Juan -->
<mapZone other="SA Western Standard Time" territory="001" type="America/La_Paz"/>
<mapZone other="SA Western Standard Time" territory="AG" type="America/Antigua"/>
<mapZone other="SA Western Standard Time" territory="AI" type="America/Anguilla"/>
<mapZone other="SA Western Standard Time" territory="AW" type="America/Aruba"/>
<mapZone other="SA Western Standard Time" territory="BB" type="America/Barbados"/>
<mapZone other="SA Western Standard Time" territory="BL" type="America/St_Barthelemy"/>
<mapZone other="SA Western Standard Time" territory="BO" type="America/La_Paz"/>
<mapZone other="SA Western Standard Time" territory="BQ" type="America/Kralendijk"/>
<mapZone other="SA Western Standard Time" territory="BR" type="America/Manaus America/Boa_Vista America/Porto_Velho"/>
<mapZone other="SA Western Standard Time" territory="CA" type="America/Blanc-Sablon"/>
<mapZone other="SA Western Standard Time" territory="CW" type="America/Curacao"/>
<mapZone other="SA Western Standard Time" territory="DM" type="America/Dominica"/>
<mapZone other="SA Western Standard Time" territory="DO" type="America/Santo_Domingo"/>
<mapZone other="SA Western Standard Time" territory="GD" type="America/Grenada"/>
<mapZone other="SA Western Standard Time" territory="GP" type="America/Guadeloupe"/>
<mapZone other="SA Western Standard Time" territory="GY" type="America/Guyana"/>
<mapZone other="SA Western Standard Time" territory="KN" type="America/St_Kitts"/>
<mapZone other="SA Western Standard Time" territory="LC" type="America/St_Lucia"/>
<mapZone other="SA Western Standard Time" territory="MF" type="America/Marigot"/>
<mapZone other="SA Western Standard Time" territory="MQ" type="America/Martinique"/>
<mapZone other="SA Western Standard Time" territory="MS" type="America/Montserrat"/>
<mapZone other="SA Western Standard Time" territory="PR" type="America/Puerto_Rico"/>
<mapZone other="SA Western Standard Time" territory="SX" type="America/Lower_Princes"/>
<mapZone other="SA Western Standard Time" territory="TT" type="America/Port_of_Spain"/>
<mapZone other="SA Western Standard Time" territory="VC" type="America/St_Vincent"/>
<mapZone other="SA Western Standard Time" territory="VG" type="America/Tortola"/>
<mapZone other="SA Western Standard Time" territory="VI" type="America/St_Thomas"/>
<mapZone other="SA Western Standard Time" territory="ZZ" type="Etc/GMT+4"/>
<!-- (UTC-04:00) Santiago -->
<mapZone other="Pacific SA Standard Time" territory="001" type="America/Santiago"/>
<mapZone other="Pacific SA Standard Time" territory="AQ" type="Antarctica/Palmer"/>
<mapZone other="Pacific SA Standard Time" territory="CL" type="America/Santiago"/>
<!-- (UTC-04:00) Turks and Caicos -->
<mapZone other="Turks And Caicos Standard Time" territory="001" type="America/Grand_Turk"/>
<mapZone other="Turks And Caicos Standard Time" territory="TC" type="America/Grand_Turk"/>
<!-- (UTC-03:30) Newfoundland -->
<mapZone other="Newfoundland Standard Time" territory="001" type="America/St_Johns"/>
<mapZone other="Newfoundland Standard Time" territory="CA" type="America/St_Johns"/>
<!-- (UTC-03:00) Araguaina -->
<mapZone other="Tocantins Standard Time" territory="001" type="America/Araguaina"/>
<mapZone other="Tocantins Standard Time" territory="BR" type="America/Araguaina"/>
<!-- (UTC-03:00) Brasilia -->
<mapZone other="E. South America Standard Time" territory="001" type="America/Sao_Paulo"/>
<mapZone other="E. South America Standard Time" territory="BR" type="America/Sao_Paulo"/>
<!-- (UTC-03:00) Cayenne, Fortaleza -->
<mapZone other="SA Eastern Standard Time" territory="001" type="America/Cayenne"/>
<mapZone other="SA Eastern Standard Time" territory="AQ" type="Antarctica/Rothera"/>
<mapZone other="SA Eastern Standard Time" territory="BR" type="America/Fortaleza America/Belem America/Maceio America/Recife America/Santarem"/>
<mapZone other="SA Eastern Standard Time" territory="FK" type="Atlantic/Stanley"/>
<mapZone other="SA Eastern Standard Time" territory="GF" type="America/Cayenne"/>
<mapZone other="SA Eastern Standard Time" territory="SR" type="America/Paramaribo"/>
<mapZone other="SA Eastern Standard Time" territory="ZZ" type="Etc/GMT+3"/>
<!-- (UTC-03:00) City of Buenos Aires -->
<mapZone other="Argentina Standard Time" territory="001" type="America/Buenos_Aires"/>
<mapZone other="Argentina Standard Time" territory="AR" type="America/Buenos_Aires America/Argentina/La_Rioja America/Argentina/Rio_Gallegos America/Argentina/Salta America/Argentina/San_Juan America/Argentina/San_Luis America/Argentina/Tucuman America/Argentina/Ushuaia America/Catamarca America/Cordoba America/Jujuy America/Mendoza"/>
<!-- (UTC-03:00) Greenland -->
<mapZone other="Greenland Standard Time" territory="001" type="America/Godthab"/>
<mapZone other="Greenland Standard Time" territory="GL" type="America/Godthab"/>
<!-- (UTC-03:00) Montevideo -->
<mapZone other="Montevideo Standard Time" territory="001" type="America/Montevideo"/>
<mapZone other="Montevideo Standard Time" territory="UY" type="America/Montevideo"/>
<!-- (UTC-03:00) Saint Pierre and Miquelon -->
<mapZone other="Saint Pierre Standard Time" territory="001" type="America/Miquelon"/>
<mapZone other="Saint Pierre Standard Time" territory="PM" type="America/Miquelon"/>
<!-- (UTC-03:00) Salvador -->
<mapZone other="Bahia Standard Time" territory="001" type="America/Bahia"/>
<mapZone other="Bahia Standard Time" territory="BR" type="America/Bahia"/>
<!-- (UTC-02:00) Coordinated Universal Time-02 -->
<mapZone other="UTC-02" territory="001" type="Etc/GMT+2"/>
<mapZone other="UTC-02" territory="BR" type="America/Noronha"/>
<mapZone other="UTC-02" territory="GS" type="Atlantic/South_Georgia"/>
<mapZone other="UTC-02" territory="ZZ" type="Etc/GMT+2"/>
<!-- (UTC-01:00) Azores -->
<mapZone other="Azores Standard Time" territory="001" type="Atlantic/Azores"/>
<mapZone other="Azores Standard Time" territory="GL" type="America/Scoresbysund"/>
<mapZone other="Azores Standard Time" territory="PT" type="Atlantic/Azores"/>
<!-- (UTC-01:00) Cabo Verde Is. -->
<mapZone other="Cape Verde Standard Time" territory="001" type="Atlantic/Cape_Verde"/>
<mapZone other="Cape Verde Standard Time" territory="CV" type="Atlantic/Cape_Verde"/>
<mapZone other="Cape Verde Standard Time" territory="ZZ" type="Etc/GMT+1"/>
<!-- (UTC) Coordinated Universal Time -->
<mapZone other="UTC" territory="001" type="Etc/GMT"/>
<mapZone other="UTC" territory="GL" type="America/Danmarkshavn"/>
<mapZone other="UTC" territory="ZZ" type="Etc/GMT"/>
<!-- (UTC+00:00) Casablanca -->
<mapZone other="Morocco Standard Time" territory="001" type="Africa/Casablanca"/>
<mapZone other="Morocco Standard Time" territory="EH" type="Africa/El_Aaiun"/>
<mapZone other="Morocco Standard Time" territory="MA" type="Africa/Casablanca"/>
<!-- (UTC+00:00) Dublin, Edinburgh, Lisbon, London -->
<mapZone other="GMT Standard Time" territory="001" type="Europe/London"/>
<mapZone other="GMT Standard Time" territory="ES" type="Atlantic/Canary"/>
<mapZone other="GMT Standard Time" territory="FO" type="Atlantic/Faeroe"/>
<mapZone other="GMT Standard Time" territory="GB" type="Europe/London"/>
<mapZone other="GMT Standard Time" territory="GG" type="Europe/Guernsey"/>
<mapZone other="GMT Standard Time" territory="IE" type="Europe/Dublin"/>
<mapZone other="GMT Standard Time" territory="IM" type="Europe/Isle_of_Man"/>
<mapZone other="GMT Standard Time" territory="JE" type="Europe/Jersey"/>
<mapZone other="GMT Standard Time" territory="PT" type="Europe/Lisbon Atlantic/Madeira"/>
<!-- (UTC+00:00) Monrovia, Reykjavik -->
<mapZone other="Greenwich Standard Time" territory="001" type="Atlantic/Reykjavik"/>
<mapZone other="Greenwich Standard Time" territory="BF" type="Africa/Ouagadougou"/>
<mapZone other="Greenwich Standard Time" territory="CI" type="Africa/Abidjan"/>
<mapZone other="Greenwich Standard Time" territory="GH" type="Africa/Accra"/>
<mapZone other="Greenwich Standard Time" territory="GM" type="Africa/Banjul"/>
<mapZone other="Greenwich Standard Time" territory="GN" type="Africa/Conakry"/>
<mapZone other="Greenwich Standard Time" territory="GW" type="Africa/Bissau"/>
<mapZone other="Greenwich Standard Time" territory="IS" type="Atlantic/Reykjavik"/>
<mapZone other="Greenwich Standard Time" territory="LR" type="Africa/Monrovia"/>
<mapZone other="Greenwich Standard Time" territory="ML" type="Africa/Bamako"/>
<mapZone other="Greenwich Standard Time" territory="MR" type="Africa/Nouakchott"/>
<mapZone other="Greenwich Standard Time" territory="SH" type="Atlantic/St_Helena"/>
<mapZone other="Greenwich Standard Time" territory="SL" type="Africa/Freetown"/>
<mapZone other="Greenwich Standard Time" territory="SN" type="Africa/Dakar"/>
<mapZone other="Greenwich Standard Time" territory="ST" type="Africa/Sao_Tome"/>
<mapZone other="Greenwich Standard Time" territory="TG" type="Africa/Lome"/>
<!-- (UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna -->
<mapZone other="W. Europe Standard Time" territory="001" type="Europe/Berlin"/>
<mapZone other="W. Europe Standard Time" territory="AD" type="Europe/Andorra"/>
<mapZone other="W. Europe Standard Time" territory="AT" type="Europe/Vienna"/>
<mapZone other="W. Europe Standard Time" territory="CH" type="Europe/Zurich"/>
<mapZone other="W. Europe Standard Time" territory="DE" type="Europe/Berlin Europe/Busingen"/>
<mapZone other="W. Europe Standard Time" territory="GI" type="Europe/Gibraltar"/>
<mapZone other="W. Europe Standard Time" territory="IT" type="Europe/Rome"/>
<mapZone other="W. Europe Standard Time" territory="LI" type="Europe/Vaduz"/>
<mapZone other="W. Europe Standard Time" territory="LU" type="Europe/Luxembourg"/>
<mapZone other="W. Europe Standard Time" territory="MC" type="Europe/Monaco"/>
<mapZone other="W. Europe Standard Time" territory="MT" type="Europe/Malta"/>
<mapZone other="W. Europe Standard Time" territory="NL" type="Europe/Amsterdam"/>
<mapZone other="W. Europe Standard Time" territory="NO" type="Europe/Oslo"/>
<mapZone other="W. Europe Standard Time" territory="SE" type="Europe/Stockholm"/>
<mapZone other="W. Europe Standard Time" territory="SJ" type="Arctic/Longyearbyen"/>
<mapZone other="W. Europe Standard Time" territory="SM" type="Europe/San_Marino"/>
<mapZone other="W. Europe Standard Time" territory="VA" type="Europe/Vatican"/>
<!-- (UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague -->
<mapZone other="Central Europe Standard Time" territory="001" type="Europe/Budapest"/>
<mapZone other="Central Europe Standard Time" territory="AL" type="Europe/Tirane"/>
<mapZone other="Central Europe Standard Time" territory="CZ" type="Europe/Prague"/>
<mapZone other="Central Europe Standard Time" territory="HU" type="Europe/Budapest"/>
<mapZone other="Central Europe Standard Time" territory="ME" type="Europe/Podgorica"/>
<mapZone other="Central Europe Standard Time" territory="RS" type="Europe/Belgrade"/>
<mapZone other="Central Europe Standard Time" territory="SI" type="Europe/Ljubljana"/>
<mapZone other="Central Europe Standard Time" territory="SK" type="Europe/Bratislava"/>
<!-- (UTC+01:00) Brussels, Copenhagen, Madrid, Paris -->
<mapZone other="Romance Standard Time" territory="001" type="Europe/Paris"/>
<mapZone other="Romance Standard Time" territory="BE" type="Europe/Brussels"/>
<mapZone other="Romance Standard Time" territory="DK" type="Europe/Copenhagen"/>
<mapZone other="Romance Standard Time" territory="ES" type="Europe/Madrid Africa/Ceuta"/>
<mapZone other="Romance Standard Time" territory="FR" type="Europe/Paris"/>
<!-- (UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb -->
<mapZone other="Central European Standard Time" territory="001" type="Europe/Warsaw"/>
<mapZone other="Central European Standard Time" territory="BA" type="Europe/Sarajevo"/>
<mapZone other="Central European Standard Time" territory="HR" type="Europe/Zagreb"/>
<mapZone other="Central European Standard Time" territory="MK" type="Europe/Skopje"/>
<mapZone other="Central European Standard Time" territory="PL" type="Europe/Warsaw"/>
<!-- (UTC+01:00) West Central Africa -->
<mapZone other="W. Central Africa Standard Time" territory="001" type="Africa/Lagos"/>
<mapZone other="W. Central Africa Standard Time" territory="AO" type="Africa/Luanda"/>
<mapZone other="W. Central Africa Standard Time" territory="BJ" type="Africa/Porto-Novo"/>
<mapZone other="W. Central Africa Standard Time" territory="CD" type="Africa/Kinshasa"/>
<mapZone other="W. Central Africa Standard Time" territory="CF" type="Africa/Bangui"/>
<mapZone other="W. Central Africa Standard Time" territory="CG" type="Africa/Brazzaville"/>
<mapZone other="W. Central Africa Standard Time" territory="CM" type="Africa/Douala"/>
<mapZone other="W. Central Africa Standard Time" territory="DZ" type="Africa/Algiers"/>
<mapZone other="W. Central Africa Standard Time" territory="GA" type="Africa/Libreville"/>
<mapZone other="W. Central Africa Standard Time" territory="GQ" type="Africa/Malabo"/>
<mapZone other="W. Central Africa Standard Time" territory="NE" type="Africa/Niamey"/>
<mapZone other="W. Central Africa Standard Time" territory="NG" type="Africa/Lagos"/>
<mapZone other="W. Central Africa Standard Time" territory="TD" type="Africa/Ndjamena"/>
<mapZone other="W. Central Africa Standard Time" territory="TN" type="Africa/Tunis"/>
<mapZone other="W. Central Africa Standard Time" territory="ZZ" type="Etc/GMT-1"/>
<!-- (UTC+01:00) Windhoek -->
<mapZone other="Namibia Standard Time" territory="001" type="Africa/Windhoek"/>
<mapZone other="Namibia Standard Time" territory="NA" type="Africa/Windhoek"/>
<!-- (UTC+02:00) Amman -->
<mapZone other="Jordan Standard Time" territory="001" type="Asia/Amman"/>
<mapZone other="Jordan Standard Time" territory="JO" type="Asia/Amman"/>
<!-- (UTC+02:00) Athens, Bucharest -->
<mapZone other="GTB Standard Time" territory="001" type="Europe/Bucharest"/>
<mapZone other="GTB Standard Time" territory="CY" type="Asia/Nicosia"/>
<mapZone other="GTB Standard Time" territory="GR" type="Europe/Athens"/>
<mapZone other="GTB Standard Time" territory="RO" type="Europe/Bucharest"/>
<!-- (UTC+02:00) Beirut -->
<mapZone other="Middle East Standard Time" territory="001" type="Asia/Beirut"/>
<mapZone other="Middle East Standard Time" territory="LB" type="Asia/Beirut"/>
<!-- (UTC+02:00) Cairo -->
<mapZone other="Egypt Standard Time" territory="001" type="Africa/Cairo"/>
<mapZone other="Egypt Standard Time" territory="EG" type="Africa/Cairo"/>
<!-- (UTC+02:00) Chisinau -->
<mapZone other="E. Europe Standard Time" territory="001" type="Europe/Chisinau"/>
<mapZone other="E. Europe Standard Time" territory="MD" type="Europe/Chisinau"/>
<!-- (UTC+02:00) Damascus -->
<mapZone other="Syria Standard Time" territory="001" type="Asia/Damascus"/>
<mapZone other="Syria Standard Time" territory="SY" type="Asia/Damascus"/>
<!-- (UTC+02:00) Gaza, Hebron -->
<mapZone other="West Bank Standard Time" territory="001" type="Asia/Hebron"/>
<mapZone other="West Bank Standard Time" territory="PS" type="Asia/Hebron Asia/Gaza"/>
<!-- (UTC+02:00) Harare, Pretoria -->
<mapZone other="South Africa Standard Time" territory="001" type="Africa/Johannesburg"/>
<mapZone other="South Africa Standard Time" territory="BI" type="Africa/Bujumbura"/>
<mapZone other="South Africa Standard Time" territory="BW" type="Africa/Gaborone"/>
<mapZone other="South Africa Standard Time" territory="CD" type="Africa/Lubumbashi"/>
<mapZone other="South Africa Standard Time" territory="LS" type="Africa/Maseru"/>
<mapZone other="South Africa Standard Time" territory="MW" type="Africa/Blantyre"/>
<mapZone other="South Africa Standard Time" territory="MZ" type="Africa/Maputo"/>
<mapZone other="South Africa Standard Time" territory="RW" type="Africa/Kigali"/>
<mapZone other="South Africa Standard Time" territory="SZ" type="Africa/Mbabane"/>
<mapZone other="South Africa Standard Time" territory="ZA" type="Africa/Johannesburg"/>
<mapZone other="South Africa Standard Time" territory="ZM" type="Africa/Lusaka"/>
<mapZone other="South Africa Standard Time" territory="ZW" type="Africa/Harare"/>
<mapZone other="South Africa Standard Time" territory="ZZ" type="Etc/GMT-2"/>
<!-- (UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius -->
<mapZone other="FLE Standard Time" territory="001" type="Europe/Kiev"/>
<mapZone other="FLE Standard Time" territory="AX" type="Europe/Mariehamn"/>
<mapZone other="FLE Standard Time" territory="BG" type="Europe/Sofia"/>
<mapZone other="FLE Standard Time" territory="EE" type="Europe/Tallinn"/>
<mapZone other="FLE Standard Time" territory="FI" type="Europe/Helsinki"/>
<mapZone other="FLE Standard Time" territory="LT" type="Europe/Vilnius"/>
<mapZone other="FLE Standard Time" territory="LV" type="Europe/Riga"/>
<mapZone other="FLE Standard Time" territory="UA" type="Europe/Kiev Europe/Uzhgorod Europe/Zaporozhye"/>
<!-- (UTC+02:00) Istanbul -->
<mapZone other="Turkey Standard Time" territory="001" type="Europe/Istanbul"/>
<mapZone other="Turkey Standard Time" territory="TR" type="Europe/Istanbul"/>
<!-- (UTC+02:00) Jerusalem -->
<mapZone other="Israel Standard Time" territory="001" type="Asia/Jerusalem"/>
<mapZone other="Israel Standard Time" territory="IL" type="Asia/Jerusalem"/>
<!-- (UTC+02:00) Kaliningrad -->
<mapZone other="Kaliningrad Standard Time" territory="001" type="Europe/Kaliningrad"/>
<mapZone other="Kaliningrad Standard Time" territory="RU" type="Europe/Kaliningrad"/>
<!-- (UTC+02:00) Tripoli -->
<mapZone other="Libya Standard Time" territory="001" type="Africa/Tripoli"/>
<mapZone other="Libya Standard Time" territory="LY" type="Africa/Tripoli"/>
<!-- (UTC+03:00) Baghdad -->
<mapZone other="Arabic Standard Time" territory="001" type="Asia/Baghdad"/>
<mapZone other="Arabic Standard Time" territory="IQ" type="Asia/Baghdad"/>
<!-- (UTC+03:00) Kuwait, Riyadh -->
<mapZone other="Arab Standard Time" territory="001" type="Asia/Riyadh"/>
<mapZone other="Arab Standard Time" territory="BH" type="Asia/Bahrain"/>
<mapZone other="Arab Standard Time" territory="KW" type="Asia/Kuwait"/>
<mapZone other="Arab Standard Time" territory="QA" type="Asia/Qatar"/>
<mapZone other="Arab Standard Time" territory="SA" type="Asia/Riyadh"/>
<mapZone other="Arab Standard Time" territory="YE" type="Asia/Aden"/>
<!-- (UTC+03:00) Minsk -->
<mapZone other="Belarus Standard Time" territory="001" type="Europe/Minsk"/>
<mapZone other="Belarus Standard Time" territory="BY" type="Europe/Minsk"/>
<!-- (UTC+03:00) Moscow, St. Petersburg, Volgograd -->
<mapZone other="Russian Standard Time" territory="001" type="Europe/Moscow"/>
<mapZone other="Russian Standard Time" territory="RU" type="Europe/Moscow Europe/Kirov Europe/Volgograd"/>
<mapZone other="Russian Standard Time" territory="UA" type="Europe/Simferopol"/>
<!-- (UTC+03:00) Nairobi -->
<mapZone other="E. Africa Standard Time" territory="001" type="Africa/Nairobi"/>
<mapZone other="E. Africa Standard Time" territory="AQ" type="Antarctica/Syowa"/>
<mapZone other="E. Africa Standard Time" territory="DJ" type="Africa/Djibouti"/>
<mapZone other="E. Africa Standard Time" territory="ER" type="Africa/Asmera"/>
<mapZone other="E. Africa Standard Time" territory="ET" type="Africa/Addis_Ababa"/>
<mapZone other="E. Africa Standard Time" territory="KE" type="Africa/Nairobi"/>
<mapZone other="E. Africa Standard Time" territory="KM" type="Indian/Comoro"/>
<mapZone other="E. Africa Standard Time" territory="MG" type="Indian/Antananarivo"/>
<mapZone other="E. Africa Standard Time" territory="SD" type="Africa/Khartoum"/>
<mapZone other="E. Africa Standard Time" territory="SO" type="Africa/Mogadishu"/>
<mapZone other="E. Africa Standard Time" territory="SS" type="Africa/Juba"/>
<mapZone other="E. Africa Standard Time" territory="TZ" type="Africa/Dar_es_Salaam"/>
<mapZone other="E. Africa Standard Time" territory="UG" type="Africa/Kampala"/>
<mapZone other="E. Africa Standard Time" territory="YT" type="Indian/Mayotte"/>
<mapZone other="E. Africa Standard Time" territory="ZZ" type="Etc/GMT-3"/>
<!-- (UTC+03:30) Tehran -->
<mapZone other="Iran Standard Time" territory="001" type="Asia/Tehran"/>
<mapZone other="Iran Standard Time" territory="IR" type="Asia/Tehran"/>
<!-- (UTC+04:00) Abu Dhabi, Muscat -->
<mapZone other="Arabian Standard Time" territory="001" type="Asia/Dubai"/>
<mapZone other="Arabian Standard Time" territory="AE" type="Asia/Dubai"/>
<mapZone other="Arabian Standard Time" territory="OM" type="Asia/Muscat"/>
<mapZone other="Arabian Standard Time" territory="ZZ" type="Etc/GMT-4"/>
<!-- (UTC+04:00) Astrakhan, Ulyanovsk -->
<mapZone other="Astrakhan Standard Time" territory="001" type="Europe/Astrakhan"/>
<mapZone other="Astrakhan Standard Time" territory="RU" type="Europe/Astrakhan Europe/Ulyanovsk"/>
<!-- (UTC+04:00) Baku -->
<mapZone other="Azerbaijan Standard Time" territory="001" type="Asia/Baku"/>
<mapZone other="Azerbaijan Standard Time" territory="AZ" type="Asia/Baku"/>
<!-- (UTC+04:00) Izhevsk, Samara -->
<mapZone other="Russia Time Zone 3" territory="001" type="Europe/Samara"/>
<mapZone other="Russia Time Zone 3" territory="RU" type="Europe/Samara"/>
<!-- (UTC+04:00) Port Louis -->
<mapZone other="Mauritius Standard Time" territory="001" type="Indian/Mauritius"/>
<mapZone other="Mauritius Standard Time" territory="MU" type="Indian/Mauritius"/>
<mapZone other="Mauritius Standard Time" territory="RE" type="Indian/Reunion"/>
<mapZone other="Mauritius Standard Time" territory="SC" type="Indian/Mahe"/>
<!-- (UTC+04:00) Tbilisi -->
<mapZone other="Georgian Standard Time" territory="001" type="Asia/Tbilisi"/>
<mapZone other="Georgian Standard Time" territory="GE" type="Asia/Tbilisi"/>
<!-- (UTC+04:00) Yerevan -->
<mapZone other="Caucasus Standard Time" territory="001" type="Asia/Yerevan"/>
<mapZone other="Caucasus Standard Time" territory="AM" type="Asia/Yerevan"/>
<!-- (UTC+04:30) Kabul -->
<mapZone other="Afghanistan Standard Time" territory="001" type="Asia/Kabul"/>
<mapZone other="Afghanistan Standard Time" territory="AF" type="Asia/Kabul"/>
<!-- (UTC+05:00) Ashgabat, Tashkent -->
<mapZone other="West Asia Standard Time" territory="001" type="Asia/Tashkent"/>
<mapZone other="West Asia Standard Time" territory="AQ" type="Antarctica/Mawson"/>
<mapZone other="West Asia Standard Time" territory="KZ" type="Asia/Oral Asia/Aqtau Asia/Aqtobe"/>
<mapZone other="West Asia Standard Time" territory="MV" type="Indian/Maldives"/>
<mapZone other="West Asia Standard Time" territory="TF" type="Indian/Kerguelen"/>
<mapZone other="West Asia Standard Time" territory="TJ" type="Asia/Dushanbe"/>
<mapZone other="West Asia Standard Time" territory="TM" type="Asia/Ashgabat"/>
<mapZone other="West Asia Standard Time" territory="UZ" type="Asia/Tashkent Asia/Samarkand"/>
<mapZone other="West Asia Standard Time" territory="ZZ" type="Etc/GMT-5"/>
<!-- (UTC+05:00) Ekaterinburg -->
<mapZone other="Ekaterinburg Standard Time" territory="001" type="Asia/Yekaterinburg"/>
<mapZone other="Ekaterinburg Standard Time" territory="RU" type="Asia/Yekaterinburg"/>
<!-- (UTC+05:00) Islamabad, Karachi -->
<mapZone other="Pakistan Standard Time" territory="001" type="Asia/Karachi"/>
<mapZone other="Pakistan Standard Time" territory="PK" type="Asia/Karachi"/>
<!-- (UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi -->
<mapZone other="India Standard Time" territory="001" type="Asia/Calcutta"/>
<mapZone other="India Standard Time" territory="IN" type="Asia/Calcutta"/>
<!-- (UTC+05:30) Sri Jayawardenepura -->
<mapZone other="Sri Lanka Standard Time" territory="001" type="Asia/Colombo"/>
<mapZone other="Sri Lanka Standard Time" territory="LK" type="Asia/Colombo"/>
<!-- (UTC+05:45) Kathmandu -->
<mapZone other="Nepal Standard Time" territory="001" type="Asia/Katmandu"/>
<mapZone other="Nepal Standard Time" territory="NP" type="Asia/Katmandu"/>
<!-- (UTC+06:00) Astana -->
<mapZone other="Central Asia Standard Time" territory="001" type="Asia/Almaty"/>
<mapZone other="Central Asia Standard Time" territory="AQ" type="Antarctica/Vostok"/>
<mapZone other="Central Asia Standard Time" territory="CN" type="Asia/Urumqi"/>
<mapZone other="Central Asia Standard Time" territory="IO" type="Indian/Chagos"/>
<mapZone other="Central Asia Standard Time" territory="KG" type="Asia/Bishkek"/>
<mapZone other="Central Asia Standard Time" territory="KZ" type="Asia/Almaty Asia/Qyzylorda"/>
<mapZone other="Central Asia Standard Time" territory="ZZ" type="Etc/GMT-6"/>
<!-- (UTC+06:00) Dhaka -->
<mapZone other="Bangladesh Standard Time" territory="001" type="Asia/Dhaka"/>
<mapZone other="Bangladesh Standard Time" territory="BD" type="Asia/Dhaka"/>
<mapZone other="Bangladesh Standard Time" territory="BT" type="Asia/Thimphu"/>
<!-- (UTC+06:00) Omsk -->
<mapZone other="Omsk Standard Time" territory="001" type="Asia/Omsk"/>
<mapZone other="Omsk Standard Time" territory="RU" type="Asia/Omsk"/>
<!-- (UTC+06:30) Yangon (Rangoon) -->
<mapZone other="Myanmar Standard Time" territory="001" type="Asia/Rangoon"/>
<mapZone other="Myanmar Standard Time" territory="CC" type="Indian/Cocos"/>
<mapZone other="Myanmar Standard Time" territory="MM" type="Asia/Rangoon"/>
<!-- (UTC+07:00) Bangkok, Hanoi, Jakarta -->
<mapZone other="SE Asia Standard Time" territory="001" type="Asia/Bangkok"/>
<mapZone other="SE Asia Standard Time" territory="AQ" type="Antarctica/Davis"/>
<mapZone other="SE Asia Standard Time" territory="CX" type="Indian/Christmas"/>
<mapZone other="SE Asia Standard Time" territory="ID" type="Asia/Jakarta Asia/Pontianak"/>
<mapZone other="SE Asia Standard Time" territory="KH" type="Asia/Phnom_Penh"/>
<mapZone other="SE Asia Standard Time" territory="LA" type="Asia/Vientiane"/>
<mapZone other="SE Asia Standard Time" territory="TH" type="Asia/Bangkok"/>
<mapZone other="SE Asia Standard Time" territory="VN" type="Asia/Saigon"/>
<mapZone other="SE Asia Standard Time" territory="ZZ" type="Etc/GMT-7"/>
<!-- (UTC+07:00) Barnaul, Gorno-Altaysk -->
<mapZone other="Altai Standard Time" territory="001" type="Asia/Barnaul"/>
<mapZone other="Altai Standard Time" territory="RU" type="Asia/Barnaul"/>
<!-- (UTC+07:00) Hovd -->
<mapZone other="W. Mongolia Standard Time" territory="001" type="Asia/Hovd"/>
<mapZone other="W. Mongolia Standard Time" territory="MN" type="Asia/Hovd"/>
<!-- (UTC+07:00) Krasnoyarsk -->
<mapZone other="North Asia Standard Time" territory="001" type="Asia/Krasnoyarsk"/>
<mapZone other="North Asia Standard Time" territory="RU" type="Asia/Krasnoyarsk Asia/Novokuznetsk"/>
<!-- (UTC+07:00) Novosibirsk -->
<mapZone other="N. Central Asia Standard Time" territory="001" type="Asia/Novosibirsk"/>
<mapZone other="N. Central Asia Standard Time" territory="RU" type="Asia/Novosibirsk"/>
<!-- (UTC+07:00) Tomsk -->
<mapZone other="Tomsk Standard Time" territory="001" type="Asia/Tomsk"/>
<mapZone other="Tomsk Standard Time" territory="RU" type="Asia/Tomsk"/>
<!-- (UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi -->
<mapZone other="China Standard Time" territory="001" type="Asia/Shanghai"/>
<mapZone other="China Standard Time" territory="CN" type="Asia/Shanghai"/>
<mapZone other="China Standard Time" territory="HK" type="Asia/Hong_Kong"/>
<mapZone other="China Standard Time" territory="MO" type="Asia/Macau"/>
<!-- (UTC+08:00) Irkutsk -->
<mapZone other="North Asia East Standard Time" territory="001" type="Asia/Irkutsk"/>
<mapZone other="North Asia East Standard Time" territory="RU" type="Asia/Irkutsk"/>
<!-- (UTC+08:00) Kuala Lumpur, Singapore -->
<mapZone other="Singapore Standard Time" territory="001" type="Asia/Singapore"/>
<mapZone other="Singapore Standard Time" territory="BN" type="Asia/Brunei"/>
<mapZone other="Singapore Standard Time" territory="ID" type="Asia/Makassar"/>
<mapZone other="Singapore Standard Time" territory="MY" type="Asia/Kuala_Lumpur Asia/Kuching"/>
<mapZone other="Singapore Standard Time" territory="PH" type="Asia/Manila"/>
<mapZone other="Singapore Standard Time" territory="SG" type="Asia/Singapore"/>
<mapZone other="Singapore Standard Time" territory="ZZ" type="Etc/GMT-8"/>
<!-- (UTC+08:00) Perth -->
<mapZone other="W. Australia Standard Time" territory="001" type="Australia/Perth"/>
<mapZone other="W. Australia Standard Time" territory="AU" type="Australia/Perth"/>
<!-- (UTC+08:00) Taipei -->
<mapZone other="Taipei Standard Time" territory="001" type="Asia/Taipei"/>
<mapZone other="Taipei Standard Time" territory="TW" type="Asia/Taipei"/>
<!-- (UTC+08:00) Ulaanbaatar -->
<mapZone other="Ulaanbaatar Standard Time" territory="001" type="Asia/Ulaanbaatar"/>
<mapZone other="Ulaanbaatar Standard Time" territory="MN" type="Asia/Ulaanbaatar Asia/Choibalsan"/>
<!-- (UTC+08:30) Pyongyang -->
<mapZone other="North Korea Standard Time" territory="001" type="Asia/Pyongyang"/>
<mapZone other="North Korea Standard Time" territory="KP" type="Asia/Pyongyang"/>
<!-- (UTC+08:45) Eucla -->
<mapZone other="Aus Central W. Standard Time" territory="001" type="Australia/Eucla"/>
<mapZone other="Aus Central W. Standard Time" territory="AU" type="Australia/Eucla"/>
<!-- (UTC+09:00) Chita -->
<mapZone other="Transbaikal Standard Time" territory="001" type="Asia/Chita"/>
<mapZone other="Transbaikal Standard Time" territory="RU" type="Asia/Chita"/>
<!-- (UTC+09:00) Osaka, Sapporo, Tokyo -->
<mapZone other="Tokyo Standard Time" territory="001" type="Asia/Tokyo"/>
<mapZone other="Tokyo Standard Time" territory="ID" type="Asia/Jayapura"/>
<mapZone other="Tokyo Standard Time" territory="JP" type="Asia/Tokyo"/>
<mapZone other="Tokyo Standard Time" territory="PW" type="Pacific/Palau"/>
<mapZone other="Tokyo Standard Time" territory="TL" type="Asia/Dili"/>
<mapZone other="Tokyo Standard Time" territory="ZZ" type="Etc/GMT-9"/>
<!-- (UTC+09:00) Seoul -->
<mapZone other="Korea Standard Time" territory="001" type="Asia/Seoul"/>
<mapZone other="Korea Standard Time" territory="KR" type="Asia/Seoul"/>
<!-- (UTC+09:00) Yakutsk -->
<mapZone other="Yakutsk Standard Time" territory="001" type="Asia/Yakutsk"/>
<mapZone other="Yakutsk Standard Time" territory="RU" type="Asia/Yakutsk Asia/Khandyga"/>
<!-- (UTC+09:30) Adelaide -->
<mapZone other="Cen. Australia Standard Time" territory="001" type="Australia/Adelaide"/>
<mapZone other="Cen. Australia Standard Time" territory="AU" type="Australia/Adelaide Australia/Broken_Hill"/>
<!-- (UTC+09:30) Darwin -->
<mapZone other="AUS Central Standard Time" territory="001" type="Australia/Darwin"/>
<mapZone other="AUS Central Standard Time" territory="AU" type="Australia/Darwin"/>
<!-- (UTC+10:00) Brisbane -->
<mapZone other="E. Australia Standard Time" territory="001" type="Australia/Brisbane"/>
<mapZone other="E. Australia Standard Time" territory="AU" type="Australia/Brisbane Australia/Lindeman"/>
<!-- (UTC+10:00) Canberra, Melbourne, Sydney -->
<mapZone other="AUS Eastern Standard Time" territory="001" type="Australia/Sydney"/>
<mapZone other="AUS Eastern Standard Time" territory="AU" type="Australia/Sydney Australia/Melbourne"/>
<!-- (UTC+10:00) Guam, Port Moresby -->
<mapZone other="West Pacific Standard Time" territory="001" type="Pacific/Port_Moresby"/>
<mapZone other="West Pacific Standard Time" territory="AQ" type="Antarctica/DumontDUrville"/>
<mapZone other="West Pacific Standard Time" territory="FM" type="Pacific/Truk"/>
<mapZone other="West Pacific Standard Time" territory="GU" type="Pacific/Guam"/>
<mapZone other="West Pacific Standard Time" territory="MP" type="Pacific/Saipan"/>
<mapZone other="West Pacific Standard Time" territory="PG" type="Pacific/Port_Moresby"/>
<mapZone other="West Pacific Standard Time" territory="ZZ" type="Etc/GMT-10"/>
<!-- (UTC+10:00) Hobart -->
<mapZone other="Tasmania Standard Time" territory="001" type="Australia/Hobart"/>
<mapZone other="Tasmania Standard Time" territory="AU" type="Australia/Hobart Australia/Currie"/>
<!-- (UTC+10:00) Vladivostok -->
<mapZone other="Vladivostok Standard Time" territory="001" type="Asia/Vladivostok"/>
<mapZone other="Vladivostok Standard Time" territory="RU" type="Asia/Vladivostok Asia/Ust-Nera"/>
<!-- (UTC+10:30) Lord Howe Island -->
<mapZone other="Lord Howe Standard Time" territory="001" type="Australia/Lord_Howe"/>
<mapZone other="Lord Howe Standard Time" territory="AU" type="Australia/Lord_Howe"/>
<!-- (UTC+11:00) Bougainville Island -->
<mapZone other="Bougainville Standard Time" territory="001" type="Pacific/Bougainville"/>
<mapZone other="Bougainville Standard Time" territory="PG" type="Pacific/Bougainville"/>
<!-- (UTC+11:00) Chokurdakh -->
<mapZone other="Russia Time Zone 10" territory="001" type="Asia/Srednekolymsk"/>
<mapZone other="Russia Time Zone 10" territory="RU" type="Asia/Srednekolymsk"/>
<!-- (UTC+11:00) Magadan -->
<mapZone other="Magadan Standard Time" territory="001" type="Asia/Magadan"/>
<mapZone other="Magadan Standard Time" territory="RU" type="Asia/Magadan"/>
<!-- (UTC+11:00) Norfolk Island -->
<mapZone other="Norfolk Standard Time" territory="001" type="Pacific/Norfolk"/>
<mapZone other="Norfolk Standard Time" territory="NF" type="Pacific/Norfolk"/>
<!-- (UTC+11:00) Sakhalin -->
<mapZone other="Sakhalin Standard Time" territory="001" type="Asia/Sakhalin"/>
<mapZone other="Sakhalin Standard Time" territory="RU" type="Asia/Sakhalin"/>
<!-- (UTC+11:00) Solomon Is., New Caledonia -->
<mapZone other="Central Pacific Standard Time" territory="001" type="Pacific/Guadalcanal"/>
<mapZone other="Central Pacific Standard Time" territory="AQ" type="Antarctica/Casey"/>
<mapZone other="Central Pacific Standard Time" territory="AU" type="Antarctica/Macquarie"/>
<mapZone other="Central Pacific Standard Time" territory="FM" type="Pacific/Ponape Pacific/Kosrae"/>
<mapZone other="Central Pacific Standard Time" territory="NC" type="Pacific/Noumea"/>
<mapZone other="Central Pacific Standard Time" territory="SB" type="Pacific/Guadalcanal"/>
<mapZone other="Central Pacific Standard Time" territory="VU" type="Pacific/Efate"/>
<mapZone other="Central Pacific Standard Time" territory="ZZ" type="Etc/GMT-11"/>
<!-- (UTC+12:00) Anadyr, Petropavlovsk-Kamchatsky -->
<mapZone other="Russia Time Zone 11" territory="001" type="Asia/Kamchatka"/>
<mapZone other="Russia Time Zone 11" territory="RU" type="Asia/Kamchatka Asia/Anadyr"/>
<!-- (UTC+12:00) Auckland, Wellington -->
<mapZone other="New Zealand Standard Time" territory="001" type="Pacific/Auckland"/>
<mapZone other="New Zealand Standard Time" territory="AQ" type="Antarctica/McMurdo"/>
<mapZone other="New Zealand Standard Time" territory="NZ" type="Pacific/Auckland"/>
<!-- (UTC+12:00) Coordinated Universal Time+12 -->
<mapZone other="UTC+12" territory="001" type="Etc/GMT-12"/>
<mapZone other="UTC+12" territory="KI" type="Pacific/Tarawa"/>
<mapZone other="UTC+12" territory="MH" type="Pacific/Majuro Pacific/Kwajalein"/>
<mapZone other="UTC+12" territory="NR" type="Pacific/Nauru"/>
<mapZone other="UTC+12" territory="TV" type="Pacific/Funafuti"/>
<mapZone other="UTC+12" territory="UM" type="Pacific/Wake"/>
<mapZone other="UTC+12" territory="WF" type="Pacific/Wallis"/>
<mapZone other="UTC+12" territory="ZZ" type="Etc/GMT-12"/>
<!-- (UTC+12:00) Fiji -->
<mapZone other="Fiji Standard Time" territory="001" type="Pacific/Fiji"/>
<mapZone other="Fiji Standard Time" territory="FJ" type="Pacific/Fiji"/>
<!-- (UTC+12:45) Chatham Islands -->
<mapZone other="Chatham Islands Standard Time" territory="001" type="Pacific/Chatham"/>
<mapZone other="Chatham Islands Standard Time" territory="NZ" type="Pacific/Chatham"/>
<!-- (UTC+13:00) Nuku'alofa -->
<mapZone other="Tonga Standard Time" territory="001" type="Pacific/Tongatapu"/>
<mapZone other="Tonga Standard Time" territory="KI" type="Pacific/Enderbury"/>
<mapZone other="Tonga Standard Time" territory="TK" type="Pacific/Fakaofo"/>
<mapZone other="Tonga Standard Time" territory="TO" type="Pacific/Tongatapu"/>
<mapZone other="Tonga Standard Time" territory="ZZ" type="Etc/GMT-13"/>
<!-- (UTC+13:00) Samoa -->
<mapZone other="Samoa Standard Time" territory="001" type="Pacific/Apia"/>
<mapZone other="Samoa Standard Time" territory="WS" type="Pacific/Apia"/>
<!-- (UTC+14:00) Kiritimati Island -->
<mapZone other="Line Islands Standard Time" territory="001" type="Pacific/Kiritimati"/>
<mapZone other="Line Islands Standard Time" territory="KI" type="Pacific/Kiritimati"/>
<mapZone other="Line Islands Standard Time" territory="ZZ" type="Etc/GMT-14"/>
</mapTimezones>
</windowsZones>
</supplementalData>

View File

@@ -1110,8 +1110,6 @@ class PHPMailer {
if($this->MessageID != '') { if($this->MessageID != '') {
$result .= $this->HeaderLine('Message-ID',$this->MessageID); $result .= $this->HeaderLine('Message-ID',$this->MessageID);
} else {
$result .= sprintf("Message-ID: <%s@%s>%s", $uniq_id, $this->ServerHostname(), $this->LE);
} }
$result .= $this->HeaderLine('X-Priority', $this->Priority); $result .= $this->HeaderLine('X-Priority', $this->Priority);
$result .= $this->HeaderLine('X-Mailer', 'PHPMailer '.$this->Version.' (phpmailer.sourceforge.net)'); $result .= $this->HeaderLine('X-Mailer', 'PHPMailer '.$this->Version.' (phpmailer.sourceforge.net)');

610
migration/dedup.php Normal file
View File

@@ -0,0 +1,610 @@
<?php
$migrate_dedup_users = array();
$migrate_dedup_users["midiclub"] = "midclub";
$migrate_dedup_users["trip"] = "car";
$migrate_dedup_users["thunderbolt"] = "thunderbolt16";
$migrate_dedup_users["avirnig"] = "beav";
$migrate_dedup_users["jeaninmontreal"] = "jeanm";
$migrate_dedup_users["nneo"] = "nneonneo";
$migrate_dedup_users["ebonyj"] = "jim";
$migrate_dedup_users["tnr"] = "ichem";
$migrate_dedup_users["archus"] = "rstehwien";
$migrate_dedup_users["kalysto"] = "kalar";
$migrate_dedup_users["eloe"] = "mouvanteloe";
$migrate_dedup_users["erdling"] = "eitj";
$migrate_dedup_users["cwinebrinner"] = "chuck_1027";
$migrate_dedup_users["foo"] = "mfranz";
$migrate_dedup_users["f00fbug"] = "viviowns";
$migrate_dedup_users["olavbalm"] = "olav";
$migrate_dedup_users["kapong"] = "el_kapong";
$migrate_dedup_users["thorax"] = "rofthorax";
$migrate_dedup_users["kramer3d"] = "asdfghytrewq";
$migrate_dedup_users["reevee"] = "vee";
$migrate_dedup_users["matzeb"] = "matzebraun";
$migrate_dedup_users["jmsoler"] = "jms";
$migrate_dedup_users["aqa"] = "angelito";
$migrate_dedup_users["blendorphin"] = "blenergetic";
$migrate_dedup_users["paolo"] = "jody";
$migrate_dedup_users["blendedidea"] = "chubango";
$migrate_dedup_users["ruiramos"] = "p3ctu5";
$migrate_dedup_users["head_chees"] = "vassilizaitsaf";
$migrate_dedup_users["kiernan"] = "rofthorax";
$migrate_dedup_users["olaf_arnold"] = "olafarnold";
$migrate_dedup_users["joseyanesaltos"] = "joseyanes";
$migrate_dedup_users["nicholasfrancis"] = "nicholas";
$migrate_dedup_users["briancary"] = "brinux";
$migrate_dedup_users["piotrek"] = "ccpiotr";
$migrate_dedup_users["slashdev"] = "colonel_panic";
$migrate_dedup_users["rj2004"] = "rjblender";
$migrate_dedup_users["jeffreymcgrew"] = "toast";
$migrate_dedup_users["hendersj"] = "jhenderson";
$migrate_dedup_users["pierpy"] = "pier";
$migrate_dedup_users["buschhardt"] = "ishtar";
$migrate_dedup_users["timlesher"] = "tim";
$migrate_dedup_users["tizmo"] = "timlucas";
$migrate_dedup_users["pprandin"] = "pashaweb";
$migrate_dedup_users["servivo"] = "taz";
$migrate_dedup_users["stabby"] = "xingo";
$migrate_dedup_users["jimmydietulpe"] = "jimmadietulpe";
$migrate_dedup_users["palmnet"] = "palmer";
$migrate_dedup_users["graphique-chti"] = "gcprod";
$migrate_dedup_users["dreamkatana1"] = "dreamkatana";
$migrate_dedup_users["wolverine"] = "shane";
$migrate_dedup_users["schwarz"] = "wene";
$migrate_dedup_users["justtesting"] = "levon";
$migrate_dedup_users["erwin_xnan"] = "erwin";
$migrate_dedup_users["zack"] = "krystof";
$migrate_dedup_users["fabinator31"] = "fab31";
$migrate_dedup_users["themilkman"] = "cliftonium";
$migrate_dedup_users["etiennel"] = "choupi";
$migrate_dedup_users["m_i_c_h_e_l"] = "michaels";
$migrate_dedup_users["techrolla"] = "muffinpeddler";
$migrate_dedup_users["fireflymantis"] = "twilight";
$migrate_dedup_users["pinucset0"] = "pinucset";
$migrate_dedup_users["franzrogar"] = "blender-i18n-po";
$migrate_dedup_users["pinucset1"] = "pinucset";
$migrate_dedup_users["pinucset3"] = "pinucset";
$migrate_dedup_users["threeddream"] = "x-axis";
$migrate_dedup_users["mudbot"] = "laurence";
$migrate_dedup_users["ligeyx"] = "ligey";
$migrate_dedup_users["dreas"] = "dre";
$migrate_dedup_users["joorvapl"] = "joorva";
$migrate_dedup_users["voyage"] = "xenithi";
$migrate_dedup_users["azter"] = "azterix";
$migrate_dedup_users["lapinbleu"] = "simeon";
$migrate_dedup_users["emmanuel_t"] = "emmanuel";
$migrate_dedup_users["cricket"] = "gku";
$migrate_dedup_users["justanumber"] = "mad4linux";
$migrate_dedup_users["johnnyen"] = "johnny";
$migrate_dedup_users["leiota"] = "marcopolo";
$migrate_dedup_users["blackstorm"] = "ikbendirk";
$migrate_dedup_users["richie"] = "ligey";
$migrate_dedup_users["mrcarnivore2"] = "mrcarnivore";
$migrate_dedup_users["muddym1nd"] = "muddymind";
$migrate_dedup_users["eumesmo"] = "vasco_da_gama";
$migrate_dedup_users["ph-kby"] = "olav";
$migrate_dedup_users["piotrek_"] = "ccpiotr";
$migrate_dedup_users["joaquin"] = "prospero";
$migrate_dedup_users["konrad_haenel"] = "konrad8ha";
$migrate_dedup_users["benstabler"] = "lightning";
$migrate_dedup_users["gustav"] = "draknoid";
$migrate_dedup_users["slikdigit"] = "bassamk";
$migrate_dedup_users["karlerlandsen"] = "lethalsidep";
$migrate_dedup_users["djwillmsoh"] = "djw1";
$migrate_dedup_users["aschmitz"] = "root_42";
$migrate_dedup_users["malfunc"] = "mcq";
$migrate_dedup_users["m_jack"] = "jack";
$migrate_dedup_users["davey"] = "madcow";
$migrate_dedup_users["iraytrace"] = "butler";
$migrate_dedup_users["duser"] = "mslechols";
$migrate_dedup_users["akito"] = "ton";
$migrate_dedup_users["jochenschmitt"] = "s4504kr";
$migrate_dedup_users["digikiller"] = "ace1";
$migrate_dedup_users["shzhc"] = "zzzz";
$migrate_dedup_users["cyrilbrulebois"] = "kibi";
$migrate_dedup_users["themushroom"] = "draknoid";
$migrate_dedup_users["zinkmaster"] = "mariolink";
$migrate_dedup_users["ashsid"] = "ash";
$migrate_dedup_users["novato"] = "jimenez";
$migrate_dedup_users["jadie_p"] = "jadie";
$migrate_dedup_users["neshshah007"] = "neshrai";
$migrate_dedup_users["ryanshow"] = "tankcoder";
$migrate_dedup_users["squarelinesq"] = "squareline";
$migrate_dedup_users["virginijus"] = "ernestas1994";
$migrate_dedup_users["renderdemon"] = "pnio";
$migrate_dedup_users["kwab"] = "kobi";
$migrate_dedup_users["peterjonckheere"] = "jonckheerep";
$migrate_dedup_users["vidar"] = "vidarino";
$migrate_dedup_users["sapjohannes"] = "johannesje";
$migrate_dedup_users["youri"] = "ayers";
$migrate_dedup_users["theamiman"] = "amiman";
$migrate_dedup_users["chrisg_ksi"] = "red_planet";
$migrate_dedup_users["blenderorgfan"] = "franciscosilva";
$migrate_dedup_users["ericd"] = "ericdrayer";
$migrate_dedup_users["asdfghjkl2"] = "asdfghjkl";
$migrate_dedup_users["quorn"] = "alienit";
$migrate_dedup_users["daan221"] = "phoenix221";
$migrate_dedup_users["user"] = "mslechols";
$migrate_dedup_users["pyb"] = "lomperillo";
$migrate_dedup_users["g56"] = "gwald";
$migrate_dedup_users["ausland"] = "vots";
$migrate_dedup_users["quillinan"] = "orinoco";
$migrate_dedup_users["t3tsuj1n"] = "bootsheoz";
$migrate_dedup_users["molflesh"] = "melflash";
$migrate_dedup_users["rickta59"] = "aerobicsboy";
$migrate_dedup_users["nolopoly"] = "lopoly";
$migrate_dedup_users["jeel"] = "jl57";
$migrate_dedup_users["brianmccumber2"] = "brianmccumber";
$migrate_dedup_users["mouette"] = "moutte";
$migrate_dedup_users["wout"] = "wonderingwout";
$migrate_dedup_users["fatfinger"] = "mattyc";
$migrate_dedup_users["nathangull"] = "nateg";
$migrate_dedup_users["tampadave"] = "dkmweeks";
$migrate_dedup_users["sausages"] = "lethargic";
$migrate_dedup_users["chris3d"] = "chrisbd";
$migrate_dedup_users["kaito"] = "ton";
$migrate_dedup_users["akirasan"] = "akira_b";
$migrate_dedup_users["fiddle"] = "rayf";
$migrate_dedup_users["bald"] = "manu";
$migrate_dedup_users["mistermelquin"] = "melquin";
$migrate_dedup_users["kofish"] = "kingdomoffish";
$migrate_dedup_users["onanj"] = "orangee";
$migrate_dedup_users["jasoncarrier"] = "jason";
$migrate_dedup_users["joelgodin"] = "jenniferblender";
$migrate_dedup_users["oranj"] = "orangee";
$migrate_dedup_users["schdeb"] = "schmidtcristian";
$migrate_dedup_users["bastian"] = "angerb";
$migrate_dedup_users["patko"] = "patco";
$migrate_dedup_users["arakyd666"] = "arakyd";
$migrate_dedup_users["karimf"] = "rim-k";
$migrate_dedup_users["nletwory"] = "jestertestest";
$migrate_dedup_users["mansoorhyder"] = "mansoor";
$migrate_dedup_users["rocketmagnet"] = "steventr";
$migrate_dedup_users["thekernal"] = "kernal";
$migrate_dedup_users["satom"] = "satriatomat";
$migrate_dedup_users["a624"] = "a623";
$migrate_dedup_users["jldavilacasares"] = "muki";
$migrate_dedup_users["segersj"] = "segers";
$migrate_dedup_users["hoisan49"] = "hoisan";
$migrate_dedup_users["j_bessette"] = "linuxpimp20";
$migrate_dedup_users["andreas999"] = "andreas";
$migrate_dedup_users["dusan"] = "dsc512";
$migrate_dedup_users["dx-mon"] = "mantr100";
$migrate_dedup_users["ilislab"] = "ids";
$migrate_dedup_users["rben"] = "raybenjamin";
$migrate_dedup_users["sphere"] = "dysonsphere";
$migrate_dedup_users["highlife22"] = "highlife";
$migrate_dedup_users["yonarw"] = "yona";
$migrate_dedup_users["kryptic89"] = "kryptic";
$migrate_dedup_users["axelp"] = "axel";
$migrate_dedup_users["perebalsach"] = "fog22";
$migrate_dedup_users["alogerson"] = "gerson";
$migrate_dedup_users["cotejrp"] = "cotejrp1";
$migrate_dedup_users["tak"] = "carlosjamesr";
$migrate_dedup_users["ziddy"] = "anishchandran";
$migrate_dedup_users["asdgz"] = "blenderiseur";
$migrate_dedup_users["cdated"] = "cdated257";
$migrate_dedup_users["jayrkalugin"] = "jayr";
$migrate_dedup_users["zeemzoet"] = "johannesje";
$migrate_dedup_users["duo"] = "ambyra";
$migrate_dedup_users["philb"] = "chewbacca";
$migrate_dedup_users["tzi"] = "izt";
$migrate_dedup_users["squirrelthetire"] = "squirrel-tire";
$migrate_dedup_users["anwyn"] = "sugarshark";
$migrate_dedup_users["a2z"] = "a2zaa";
$migrate_dedup_users["fxrex"] = "femi";
$migrate_dedup_users["alethewiz"] = "mfaso68";
$migrate_dedup_users["noseferrit"] = "billybong";
$migrate_dedup_users["fcali"] = "fabz";
$migrate_dedup_users["bbirchler"] = "bblender";
$migrate_dedup_users["oslosewers"] = "oslo";
$migrate_dedup_users["wtrsltnk"] = "wtr";
$migrate_dedup_users["cspohst"] = "spohst";
$migrate_dedup_users["warhawk08"] = "warhawk1990";
$migrate_dedup_users["magick_crow"] = "magickcrow";
$migrate_dedup_users["guillaumem"] = "guillaume";
$migrate_dedup_users["jwitthuhn"] = "rahu";
$migrate_dedup_users["fmehigan"] = "frank_me";
$migrate_dedup_users["ilissys"] = "ids";
$migrate_dedup_users["supermoaaa"] = "moaaa";
$migrate_dedup_users["nwmatt"] = "mhenley";
$migrate_dedup_users["bezel"] = "xshell";
$migrate_dedup_users["rebuss"] = "studioa";
$migrate_dedup_users["geonom"] = "geoadel";
$migrate_dedup_users["seldan"] = "farakon";
$migrate_dedup_users["kuru76"] = "kcorbin";
$migrate_dedup_users["kapil"] = "kapilbedarkar";
$migrate_dedup_users["henryiii"] = "henryschreiner";
$migrate_dedup_users["pateamcarl"] = "carlhuth";
$migrate_dedup_users["kamen"] = "bigbob1993";
$migrate_dedup_users["olddemon"] = "old_demon";
$migrate_dedup_users["draklaw2"] = "draklaw";
$migrate_dedup_users["ksdlee"] = "kdlee";
$migrate_dedup_users["xource"] = "admix";
$migrate_dedup_users["invertednormal"] = "smokebox46and2";
$migrate_dedup_users["feelgoodcomics"] = "onlygoodwin";
$migrate_dedup_users["glorund"] = "undolaure";
$migrate_dedup_users["soulofsound"] = "johnnym";
$migrate_dedup_users["ghigi123"] = "ghigi";
$migrate_dedup_users["stefanvoigthpi"] = "derstefan";
$migrate_dedup_users["dblenderv"] = "default";
$migrate_dedup_users["ophiocus"] = "oneliner";
$migrate_dedup_users["colleywrks"] = "colley";
$migrate_dedup_users["fogy"] = "fog22";
$migrate_dedup_users["vidarn"] = "bida70";
$migrate_dedup_users["kevin"] = "goblender2541";
$migrate_dedup_users["tisachris"] = "warflight";
$migrate_dedup_users["personalex2"] = "personalex";
$migrate_dedup_users["polypa"] = "lile";
$migrate_dedup_users["i4dnf"] = "emilian";
$migrate_dedup_users["nitalleb"] = "singleman";
$migrate_dedup_users["tcgodoy"] = "godoy";
$migrate_dedup_users["joorva_pl"] = "joorva";
$migrate_dedup_users["sex"] = "xest";
$migrate_dedup_users["chipmunk"] = "ignatz";
$migrate_dedup_users["schalmagne"] = "chalmagne";
$migrate_dedup_users["michalziulek"] = "eneida";
$migrate_dedup_users["jeeps"] = "jeepster";
$migrate_dedup_users["yain"] = "chaos";
$migrate_dedup_users["amadio"] = "marble";
$migrate_dedup_users["javiere"] = "javierchavez";
$migrate_dedup_users["drmzperx"] = "mzperx";
$migrate_dedup_users["sebastianreaser"] = "sebastian0";
$migrate_dedup_users["idolon"] = "spadija";
$migrate_dedup_users["alanhzhcn"] = "alanhzh";
$migrate_dedup_users["jeanc"] = "jean";
$migrate_dedup_users["transblue2"] = "transblue";
$migrate_dedup_users["cesarwilfredo"] = "cesar";
$migrate_dedup_users["nspyr"] = "spiderfire";
$migrate_dedup_users["supermegamoaaa"] = "moaaa";
$migrate_dedup_users["firetiger"] = "opensolution";
$migrate_dedup_users["skarg"] = "cados";
$migrate_dedup_users["realheadcrusher"] = "danigr";
$migrate_dedup_users["kpg"] = "nihylius";
$migrate_dedup_users["tft"] = "tft67";
$migrate_dedup_users["storabbarn"] = "morre";
$migrate_dedup_users["bacurau"] = "roger_roo";
$migrate_dedup_users["alisari"] = "parasoley";
$migrate_dedup_users["skiski"] = "superrom";
$migrate_dedup_users["halfninja"] = "nickh";
$migrate_dedup_users["drell_develop"] = "drellex";
$migrate_dedup_users["highlif3"] = "highlife";
$migrate_dedup_users["broggsim1"] = "broggsim";
$migrate_dedup_users["blendermanuci"] = "yunior88";
$migrate_dedup_users["jpt9"] = "jtuttle";
$migrate_dedup_users["farrer"] = "farpro";
$migrate_dedup_users["badcheez"] = "randall";
$migrate_dedup_users["leewj_"] = "lohns";
$migrate_dedup_users["martin107"] = "martinfrances";
$migrate_dedup_users["dedpan"] = "tberghuis";
$migrate_dedup_users["whiterabbit"] = "dreamscapearts";
$migrate_dedup_users["shuvro"] = "shuvro05";
$migrate_dedup_users["anichandru"] = "anishchandran";
$migrate_dedup_users["ncaralph"] = "ralphdoctorow";
$migrate_dedup_users["nlongchamps"] = "nlong";
$migrate_dedup_users["mawi37"] = "mawi";
$migrate_dedup_users["rizla"] = "jay";
$migrate_dedup_users["jeh"] = "mirkril";
$migrate_dedup_users["b-fighter"] = "fanatic";
$migrate_dedup_users["wasamonkey"] = "wasa";
$migrate_dedup_users["nicola"] = "martin45";
$migrate_dedup_users["method-es"] = "method";
$migrate_dedup_users["pablow"] = "warhole";
$migrate_dedup_users["alanishzh"] = "alanhzh";
$migrate_dedup_users["thunder947"] = "thunder";
$migrate_dedup_users["yijimi"] = "roler";
$migrate_dedup_users["jaydenb"] = "logidude";
$migrate_dedup_users["leandropolus"] = "leandrosz";
$migrate_dedup_users["ibi002"] = "ibi001";
$migrate_dedup_users["hakanortasoz"] = "tayfax";
$migrate_dedup_users["enur"] = "rune";
$migrate_dedup_users["dsuesse"] = "qiip";
$migrate_dedup_users["bipedlaboratory"] = "redmetal";
$migrate_dedup_users["codeyhanson"] = "codey";
$migrate_dedup_users["alphonso_b"] = "alfonso_b";
$migrate_dedup_users["surreal6"] = "carlospadial";
$migrate_dedup_users["kralizek"] = "kral";
$migrate_dedup_users["kfrechet"] = "keithfr";
$migrate_dedup_users["jwedlake"] = "joshwedlake";
$migrate_dedup_users["westie630"] = "bully";
$migrate_dedup_users["fictional"] = "icefire";
$migrate_dedup_users["zelozelos"] = "zelozelos1";
$migrate_dedup_users["dragonlord"] = "acuena";
$migrate_dedup_users["mrcheese"] = "jpeg";
$migrate_dedup_users["willemverwey"] = "dandandan";
$migrate_dedup_users["jhed"] = "anton_foy";
$migrate_dedup_users["treacy1077"] = "briant";
$migrate_dedup_users["xest"] = "xembie";
$migrate_dedup_users["cyphl25"] = "jmsfreezer";
$migrate_dedup_users["wynk"] = "wynn";
$migrate_dedup_users["trock2957"] = "trock";
$migrate_dedup_users["mr_bomb"] = "carter24";
$migrate_dedup_users["nikolaus"] = "tortellini";
$migrate_dedup_users["pegasus_001"] = "pegasus001";
$migrate_dedup_users["fayte"] = "fayte220";
$migrate_dedup_users["jagang_8"] = "jagang";
$migrate_dedup_users["thetwom"] = "moe";
$migrate_dedup_users["kevlareditor"] = "klthomas";
$migrate_dedup_users["damien_deom"] = "dams";
$migrate_dedup_users["reynantem"] = "reynante";
$migrate_dedup_users["hiralm01"] = "hiralm";
$migrate_dedup_users["radix7"] = "wyldethang";
$migrate_dedup_users["puppetm"] = "puppetmaster";
$migrate_dedup_users["vimax"] = "vimaxus";
$migrate_dedup_users["mozzy69"] = "lyndon";
$migrate_dedup_users["vitranaccad"] = "thestorm74";
$migrate_dedup_users["kj12345"] = "kevinjames";
$migrate_dedup_users["foinix"] = "mrnoodle";
$migrate_dedup_users["shizu"] = "sntulix";
$migrate_dedup_users["kukulcangod23"] = "kukulcangod";
$migrate_dedup_users["piliq"] = "qpblendpolis";
$migrate_dedup_users["benvad"] = "vbenny";
$migrate_dedup_users["fgribben"] = "sharkey";
$migrate_dedup_users["makospince"] = "mako_spince";
$migrate_dedup_users["ablenderuser"] = "pancakeface";
$migrate_dedup_users["axelphi"] = "axel";
$migrate_dedup_users["josiasbh"] = "josias";
$migrate_dedup_users["mattsix"] = "majawe";
$migrate_dedup_users["duarte_ramos"] = "dphantom";
$migrate_dedup_users["killogge"] = "muraj";
$migrate_dedup_users["luka"] = "omgwtfbbq";
$migrate_dedup_users["chromemonkey"] = "brian651msp";
$migrate_dedup_users["e_d_i"] = "ide";
$migrate_dedup_users["mrhaynesy"] = "haynesy";
$migrate_dedup_users["imagineering"] = "imagineer";
$migrate_dedup_users["virgiliovasconc"] = "virgilio";
$migrate_dedup_users["blenderlb57"] = "louigi";
$migrate_dedup_users["nick65"] = "dgnicola";
$migrate_dedup_users["mmatthews"] = "mamatthews";
$migrate_dedup_users["gerwood"] = "arilian";
$migrate_dedup_users["xsidmax"] = "xsi";
$migrate_dedup_users["mix-yag"] = "aynahsim";
$migrate_dedup_users["wpthomas"] = "tallguy";
$migrate_dedup_users["bp007"] = "bender007";
$migrate_dedup_users["emach"] = "eon4blender";
$migrate_dedup_users["alfclement"] = "alfc";
$migrate_dedup_users["blenderwell"] = "goplexian";
$migrate_dedup_users["asebastianr"] = "sebastian0";
$migrate_dedup_users["joepal1976"] = "joepal";
$migrate_dedup_users["chertov"] = "lestat";
$migrate_dedup_users["djela63"] = "jerominovich";
$migrate_dedup_users["lppinto"] = "lpciper";
$migrate_dedup_users["prana"] = "nomath";
$migrate_dedup_users["sarubadoru"] = "salvador";
$migrate_dedup_users["rarebit"] = "rawstar7";
$migrate_dedup_users["hikikamori"] = "hikkikamori";
$migrate_dedup_users["mrduke"] = "drop";
$migrate_dedup_users["deetee"] = "dertee";
$migrate_dedup_users["woooooah"] = "noxwell";
$migrate_dedup_users["macoss"] = "ossmac";
$migrate_dedup_users["bunnyboy212"] = "blender_buddie";
$migrate_dedup_users["maxgrip"] = "czarek";
$migrate_dedup_users["tentonman"] = "titandtat";
$migrate_dedup_users["jabozzo"] = "bozzo";
$migrate_dedup_users["reinways"] = "reinw";
$migrate_dedup_users["petergk"] = "nihylius";
$migrate_dedup_users["zphr3000"] = "zphr";
$migrate_dedup_users["ruivo"] = "andreh";
$migrate_dedup_users["kosta"] = "kgd";
$migrate_dedup_users["delter"] = "dertee";
$migrate_dedup_users["jmiller"] = "lethargic";
$migrate_dedup_users["dealga"] = "zeffii";
$migrate_dedup_users["bogey"] = "daveh";
$migrate_dedup_users["silencebe"] = "silence";
$migrate_dedup_users["temozarela"] = "gorn";
$migrate_dedup_users["tischite"] = "greenbutton";
$migrate_dedup_users["buisson"] = "dinodino";
$migrate_dedup_users["cfox"] = "colinfox";
$migrate_dedup_users["hunkadoodle"] = "hunkadoodledoo";
$migrate_dedup_users["jlwitthuhn"] = "rahu";
$migrate_dedup_users["hvfrancesco"] = "hva";
$migrate_dedup_users["hazim-jamal"] = "hazim1";
$migrate_dedup_users["aurosutru"] = "tlm";
$migrate_dedup_users["pierrea"] = "pier2";
$migrate_dedup_users["zoon"] = "zoonpolygonikon";
$migrate_dedup_users["gruntbatch"] = "carmichael";
$migrate_dedup_users["petru"] = "virusanti";
$migrate_dedup_users["mikeh74"] = "mikeh";
$migrate_dedup_users["sugoi"] = "juntunen";
$migrate_dedup_users["bartje"] = "bartart3d";
$migrate_dedup_users["yaoyansi"] = "yaoyansi2";
$migrate_dedup_users["rafek"] = "rafek_finearts";
$migrate_dedup_users["caspern"] = "caspernilsson";
$migrate_dedup_users["lee"] = "s_random";
$migrate_dedup_users["sparky"] = "mmikkelsen";
$migrate_dedup_users["wigglyframes"] = "rene";
$migrate_dedup_users["kroni"] = "kronos";
$migrate_dedup_users["xgl_asyliax"] = "xglasyliax";
$migrate_dedup_users["blendeador"] = "luisperosio";
$migrate_dedup_users["kaos1986"] = "kaos86";
$migrate_dedup_users["adamdoyle"] = "advs89";
$migrate_dedup_users["kahenraz"] = "mistrel";
$migrate_dedup_users["ccliffe"] = "cjcliffe";
$migrate_dedup_users["nullfied"] = "xercesblue";
$migrate_dedup_users["dustyghost"] = "dustbin1_uk";
$migrate_dedup_users["claaskuhnen"] = "cekuhnendev";
$migrate_dedup_users["vercingetorix"] = "diamond_rust";
$migrate_dedup_users["plugboy"] = "centerlaw";
$migrate_dedup_users["rben13"] = "raybenjamin";
$migrate_dedup_users["deangiberson"] = "voidptr";
$migrate_dedup_users["makeitwork"] = "bboybram";
$migrate_dedup_users["iljosli"] = "jos";
$migrate_dedup_users["slowan"] = "slovan";
$migrate_dedup_users["mooonwalkercz"] = "mooonwalker";
$migrate_dedup_users["tapplek"] = "tapple";
$migrate_dedup_users["blendmond"] = "cornix";
$migrate_dedup_users["giorgiomartini"] = "tweakingknobs";
$migrate_dedup_users["davidray"] = "deadman";
$migrate_dedup_users["justthisguy"] = "nyctef";
$migrate_dedup_users["logobar"] = "freyer";
$migrate_dedup_users["zooo"] = "leon_cheung";
$migrate_dedup_users["josiasalexandre"] = "josias";
$migrate_dedup_users["tsukiko_chan"] = "tsukikochan";
$migrate_dedup_users["akira_san"] = "akira_b";
$migrate_dedup_users["walberti"] = "walbertievarist";
$migrate_dedup_users["astro"] = "tnboma";
$migrate_dedup_users["mccx"] = "mcq";
$migrate_dedup_users["daa84"] = "daa";
$migrate_dedup_users["sivert3"] = "cent";
$migrate_dedup_users["twentyone"] = "glade";
$migrate_dedup_users["endi2"] = "endi";
$migrate_dedup_users["jamesr"] = "james_r";
$migrate_dedup_users["reece"] = "reecerobinson";
$migrate_dedup_users["dustractor"] = "bpygrams";
$migrate_dedup_users["pencilhead"] = "pencil-head";
$migrate_dedup_users["toml"] = "tomol";
$migrate_dedup_users["colinm"] = "tablaman";
$migrate_dedup_users["blendphys2"] = "blendphys";
$migrate_dedup_users["xgoff"] = "zzyxz";
$migrate_dedup_users["coleingraham"] = "coledingraham";
$migrate_dedup_users["danielvmacedo"] = "skul3r";
$migrate_dedup_users["burster"] = "przemaz";
$migrate_dedup_users["tung"] = "tungster";
$migrate_dedup_users["chessie"] = "blackcatt";
$migrate_dedup_users["foxdog"] = "rubbernuke";
$migrate_dedup_users["mordachai"] = "gus";
$migrate_dedup_users["chilamlai"] = "cllai";
$migrate_dedup_users["sliders34"] = "sliders";
$migrate_dedup_users["benji852"] = "benjamin852";
$migrate_dedup_users["bebinalpha"] = "bebin";
$migrate_dedup_users["mr78"] = "alexandr";
$migrate_dedup_users["avirillion"] = "tarion";
$migrate_dedup_users["matthiasro"] = "matthias_r";
$migrate_dedup_users["debearseax"] = "seogeniuss";
$migrate_dedup_users["karja"] = "trockenfrosch";
$migrate_dedup_users["rojuinex"] = "ifrit";
$migrate_dedup_users["bernardo"] = "dados";
$migrate_dedup_users["ddeclara"] = "decden";
$migrate_dedup_users["zm_sansan"] = "sansan";
$migrate_dedup_users["useless"] = "cortot";
$migrate_dedup_users["tymnclono"] = "sooccatly";
$migrate_dedup_users["rodrigo_b"] = "rodrigob";
$migrate_dedup_users["shnurui"] = "spinster";
$migrate_dedup_users["michalisz"] = "michaliszissiou";
$migrate_dedup_users["fbbdev"] = "babboide";
$migrate_dedup_users["tjackson"] = "tjonline";
$migrate_dedup_users["ramaswamy"] = "ramaswamysriram";
$migrate_dedup_users["allrod5"] = "rodblender";
$migrate_dedup_users["qcp"] = "qpblendpolis";
$migrate_dedup_users["ftsf"] = "thesleepless";
$migrate_dedup_users["umagoo2012"] = "umagoo";
$migrate_dedup_users["raven"] = "rune";
$migrate_dedup_users["hsaito"] = "integer";
$migrate_dedup_users["paulthegreat"] = "digitalpyro";
$migrate_dedup_users["capheen"] = "dval";
$migrate_dedup_users["rskinner"] = "rws";
$migrate_dedup_users["gregstein"] = "gregorein";
$migrate_dedup_users["matty686"] = "matty";
$migrate_dedup_users["selby_rowley"] = "selby";
$migrate_dedup_users["shembolstudio"] = "natadams8";
$migrate_dedup_users["grenzfrequence"] = "goatrance";
$migrate_dedup_users["stephan"] = "schdeffan";
$migrate_dedup_users["axis33"] = "dsc512";
$migrate_dedup_users["redandfish"] = "red-fish";
$migrate_dedup_users["artsapcemedia"] = "arzpace";
$migrate_dedup_users["artspacemedia"] = "arzpace";
$migrate_dedup_users["mccmcc"] = "mcq";
$migrate_dedup_users["seocitterx"] = "mediabuy";
$migrate_dedup_users["lightning_limn"] = "lightning4";
$migrate_dedup_users["omarlakhdar"] = "archimage";
$migrate_dedup_users["regeleionescu"] = "regele";
$migrate_dedup_users["mitchell_decker"] = "michealikruhara";
$migrate_dedup_users["joselebon"] = "jl57";
$migrate_dedup_users["simonbroggi"] = "broggsim";
$migrate_dedup_users["inwadnepe"] = "ceapbatatry";
$migrate_dedup_users["ehobjman"] = "resbsp";
$migrate_dedup_users["davelassanske"] = "dolby411";
$migrate_dedup_users["jsu"] = "jansub";
$migrate_dedup_users["agricola"] = "agricola1";
$migrate_dedup_users["bartoszek"] = "bartus";
$migrate_dedup_users["captainoblivion"] = "cptoblivion";
$migrate_dedup_users["alexmcourt"] = "personalex";
$migrate_dedup_users["jmsprss"] = "xonar";
$migrate_dedup_users["awarnock"] = "salsa";
$migrate_dedup_users["mcc2"] = "mcq";
$migrate_dedup_users["psyborg042"] = "psyborg";
$migrate_dedup_users["ushiproject"] = "ushi";
$migrate_dedup_users["mrjimmy"] = "mrjimmyos";
$migrate_dedup_users["thefinalcut"] = "tlousky";
$migrate_dedup_users["startheshadow"] = "star";
$migrate_dedup_users["axredneck"] = "redneck";
$migrate_dedup_users["phimestudio"] = "phime";
$migrate_dedup_users["dwatts1"] = "dlax";
$migrate_dedup_users["rertjoi"] = "rertjwi";
$migrate_dedup_users["erdjkgh"] = "rertjwi";
$migrate_dedup_users["libertainsrg"] = "fcougar";
$migrate_dedup_users["godling72"] = "dmelchio";
$migrate_dedup_users["myclay"] = "thenewone";
$migrate_dedup_users["ecaspersen"] = "ecasper";
$migrate_dedup_users["driewiel"] = "driesiedriewiel";
$migrate_dedup_users["bhupen"] = "bhupendra";
$migrate_dedup_users["caosdoar"] = "mailoyo";
$migrate_dedup_users["polyspin"] = "butler";
$migrate_dedup_users["qalb_al_aqrab"] = "efimpetelin";
$migrate_dedup_users["fdfdfdfffd"] = "fcougar";
$migrate_dedup_users["brianlockett"] = "macrow";
$migrate_dedup_users["claude"] = "coco";
$migrate_dedup_users["mattostgard"] = "drflail";
$migrate_dedup_users["cekuhnen_new"] = "cekuhnendev";
$migrate_dedup_users["kirill_lukyanov"] = "kirill";
$migrate_dedup_users["jan-eric"] = "janeric96";
$migrate_dedup_users["daniel_h"] = "dhoughto";
$migrate_dedup_users["raphaelbarros"] = "thebigheadone";
$migrate_dedup_users["salas"] = "tychota";
$migrate_dedup_users["danieljsamson"] = "techfix";
$migrate_dedup_users["vinagrito"] = "aechemendia";
$migrate_dedup_users["lin_165"] = "b1657022405";
$migrate_dedup_users["cwebber"] = "paroneayea";
$migrate_dedup_users["harolddadomo"] = "harold";
$migrate_dedup_users["rabidsquirrel"] = "genericusername";
$migrate_dedup_users["larry3"] = "lehibou";
$migrate_dedup_users["predoe"] = "petronius3d";
$migrate_dedup_users["skoo"] = "stefano";
$migrate_dedup_users["cabergolinety"] = "azathioprinewww";
$migrate_dedup_users["prestijkorsan07"] = "prestij07";
$migrate_dedup_users["scottpetrovic"] = "slpetrov";
$migrate_dedup_users["zooly76"] = "zooly";
$migrate_dedup_users["theoryanimation"] = "davidandrade";
$migrate_dedup_users["daninsky"] = "danishit";
$migrate_dedup_users["eyesee2013"] = "eyesee";
$migrate_dedup_users["megacal"] = "cmcgaugh";
$migrate_dedup_users["const"] = "kostas100";
$migrate_dedup_users["ngaudenzi"] = "puppetmaster";
$migrate_dedup_users["mroguski"] = "kaelthas";
$migrate_dedup_users["brdf"] = "origin";
$migrate_dedup_users["davis3d"] = "davis";
$migrate_dedup_users["rldigital"] = "locatelli";
$migrate_dedup_users["tomforsythe"] = "gallifrey77203";
$migrate_dedup_users["gbrnk"] = "benoe";
$migrate_dedup_users["arekkasprzyk"] = "kasperski";
$migrate_dedup_users["imbusy1"] = "imbusy";
$migrate_dedup_users["mfoxdoggg"] = "mfoxdogg";
$migrate_dedup_users["knusk"] = "kanutus";
$migrate_dedup_users["tomekk"] = "anders211";
$migrate_dedup_users["kitsueb"] = "kitsu_eb";
$migrate_dedup_users["slugzzz"] = "tsquar3d";
$migrate_dedup_users["moore500"] = "mmoore500";
$migrate_dedup_users["verumbra"] = "sebastian0";
$migrate_dedup_users["blenderbug"] = "nikola";
$migrate_dedup_users["adailtoncomp"] = "adailton";
$migrate_dedup_users["mchs3d"] = "abtrumpet";
// disabled users who have tasks
$migrate_dedup_users["sjoerd"] = "sjoerddevries";
$migrate_dedup_users["matali"] = "mat_ali";
$migrate_dedup_users["voicelesscushio"] = "None";
$migrate_dedup_users["bigben0328"] = "None";
$migrate_dedup_users["santamouse"] = "None";
$migrate_dedup_users["andreanckaert"] = "None";
$migrate_dedup_users["yesmydear"] = "None";
$migrate_dedup_users["spacetug"] = "None";
$migrate_dedup_users["omegafold"] = "None";
// testing
$migrate_dedup_users["blendix_rename_test_a"] = "blendix";
$migrate_dedup_users["blendix_rename_test_b"] = "blendix";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -54,8 +54,6 @@ return array(
'javelin-behavior-aphlict-dropdown', 'javelin-behavior-aphlict-dropdown',
'javelin-behavior-history-install', 'javelin-behavior-history-install',
'javelin-behavior-phabricator-gesture', 'javelin-behavior-phabricator-gesture',
'javelin-behavior-phabricator-active-nav',
'javelin-behavior-phabricator-nav',
'javelin-behavior-phabricator-remarkup-assist', 'javelin-behavior-phabricator-remarkup-assist',
'phabricator-textareautils', 'phabricator-textareautils',
'phabricator-file-upload', 'phabricator-file-upload',
@@ -88,6 +86,12 @@ return array(
'javelin-behavior-aphlict-status', 'javelin-behavior-aphlict-status',
'javelin-behavior-user-menu', 'javelin-behavior-user-menu',
'phabricator-favicon', 'phabricator-favicon',
'javelin-behavior-phui-tab-group',
'javelin-behavior-phui-submenu',
'phuix-button-view',
'javelin-behavior-comment-actions',
'phuix-form-control-view',
'phuix-autocomplete',
), ),
'core.pkg.css' => array( 'core.pkg.css' => array(
'phabricator-core-css', 'phabricator-core-css',
@@ -158,9 +162,14 @@ return array(
'phabricator-feed-css', 'phabricator-feed-css',
'phabricator-dashboard-css', 'phabricator-dashboard-css',
'aphront-multi-column-view-css', 'aphront-multi-column-view-css',
'phui-curtain-object-ref-view-css',
'phui-comment-form-css',
'phui-head-thing-view-css',
'conpherence-durable-column-view',
'phui-button-bar-css',
), ),
'conpherence.pkg.css' => array( 'conpherence.pkg.css' => array(
'conpherence-durable-column-view',
'conpherence-menu-css', 'conpherence-menu-css',
'conpherence-color-css', 'conpherence-color-css',
'conpherence-message-pane-css', 'conpherence-message-pane-css',
@@ -187,7 +196,8 @@ return array(
'phabricator-content-source-view-css', 'phabricator-content-source-view-css',
'inline-comment-summary-css', 'inline-comment-summary-css',
'phui-inline-comment-view-css', 'phui-inline-comment-view-css',
'phabricator-filetree-view-css', 'diff-tree-view-css',
'phui-formation-view-css',
), ),
'differential.pkg.js' => array( 'differential.pkg.js' => array(
'phabricator-drag-and-drop-file-upload', 'phabricator-drag-and-drop-file-upload',
@@ -204,6 +214,14 @@ return array(
'phabricator-diff-inline', 'phabricator-diff-inline',
'phabricator-diff-changeset', 'phabricator-diff-changeset',
'phabricator-diff-changeset-list', 'phabricator-diff-changeset-list',
'phabricator-diff-tree-view',
'phabricator-diff-path-view',
'phuix-formation-view',
'phuix-formation-column-view',
'phuix-formation-flank-view',
'javelin-external-editor-link-engine',
), ),
'diffusion.pkg.css' => array( 'diffusion.pkg.css' => array(
'diffusion-icons-css', 'diffusion-icons-css',
@@ -220,4 +238,9 @@ return array(
'javelin-behavior-maniphest-batch-selector', 'javelin-behavior-maniphest-batch-selector',
'javelin-behavior-maniphest-list-editor', 'javelin-behavior-maniphest-list-editor',
), ),
'dark-console.pkg.js' => array(
'javelin-behavior-dark-console',
'phabricator-darklog',
'phabricator-darkmessage',
),
); );

View File

@@ -7,6 +7,7 @@ $status_map = array(
3 => 'invalid', 3 => 'invalid',
4 => 'duplicate', 4 => 'duplicate',
5 => 'spite', 5 => 'spite',
123450 => 'archived',
); );
$conn_w = id(new ManiphestTask())->establishConnection('w'); $conn_w = id(new ManiphestTask())->establishConnection('w');

View File

@@ -16,6 +16,9 @@ foreach (new LiskMigrationIterator($table) as $doc) {
continue; continue;
} }
$new_view_policy = $default_view_policy;
$new_edit_policy = $default_edit_policy;
// If this was previously a magical project wiki page (under projects/, but // If this was previously a magical project wiki page (under projects/, but
// not projects/ itself) we need to apply the project policies. Otherwise, // not projects/ itself) we need to apply the project policies. Otherwise,
// apply the default policies. // apply the default policies.
@@ -35,26 +38,24 @@ foreach (new LiskMigrationIterator($table) as $doc) {
->executeOne(); ->executeOne();
if ($project) { if ($project) {
$view_policy = nonempty($project->getViewPolicy(), $default_view_policy); $view_policy = nonempty($project->getViewPolicy(), $default_view_policy);
$edit_policy = nonempty($project->getEditPolicy(), $default_edit_policy); $edit_policy = nonempty($project->getEditPolicy(), $default_edit_policy);
$project_name = $project->getName(); $new_view_policy = $view_policy;
echo pht( $new_edit_policy = $edit_policy;
"Migrating document %d to project policy %s...\n",
$id,
$project_name);
$doc->setViewPolicy($view_policy);
$doc->setEditPolicy($edit_policy);
$doc->save();
continue;
} }
} }
echo pht('Migrating document %d to default install policy...', $id)."\n"; echo pht('Migrating document %d to new policy...', $id)."\n";
$doc->setViewPolicy($default_view_policy);
$doc->setEditPolicy($default_edit_policy); queryfx(
$doc->save(); $conn_w,
'UPDATE %R SET viewPolicy = %s, editPolicy = %s
WHERE id = %d',
$table,
$new_view_policy,
$new_edit_policy,
$id);
} }
echo pht('Done.')."\n"; echo pht('Done.')."\n";

View File

@@ -40,7 +40,8 @@ foreach ($lists as $list) {
if (!$username_okay) { if (!$username_okay) {
echo pht( echo pht(
'Failed to migrate mailing list "%s": unable to generate a unique '. 'Failed to migrate mailing list "%s": unable to generate a unique '.
'username for it.')."\n"; 'username for it.',
$name)."\n";
continue; continue;
} }

View File

@@ -4,18 +4,8 @@
// underlying file data were not written correctly. This restores edges for // underlying file data were not written correctly. This restores edges for
// any missing pastes. // any missing pastes.
$table = new PhabricatorPaste(); // See T13510. The "pastebin" database was later renamed to "paste", which
$edge_type = PhabricatorObjectHasFileEdgeType::EDGECONST; // broke this migration. The migration was removed in 2020 since it seems
// plausible that zero installs are impacted (only installs that ran code
foreach (new LiskMigrationIterator($table) as $paste) { // from November 2015 and have not upgraded in five years could possibly be
$paste_phid = $paste->getPHID(); // impacted).
$file_phid = $paste->getFilePHID();
if (!$file_phid) {
continue;
}
id(new PhabricatorEdgeEditor())
->addEdge($paste_phid, $edge_type, $file_phid)
->save();
}

View File

@@ -0,0 +1,10 @@
CREATE TABLE {$NAMESPACE}_user.user_externalaccountidentifier (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
externalAccountPHID VARBINARY(64) NOT NULL,
providerConfigPHID VARBINARY(64) NOT NULL,
identifierHash BINARY(12) NOT NULL,
identifierRaw LONGTEXT NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,40 @@
<?php
$account_table = new PhabricatorExternalAccount();
$identifier_table = new PhabricatorExternalAccountIdentifier();
$conn = $account_table->establishConnection('w');
$table_name = $account_table->getTableName();
$iterator = new LiskRawMigrationIterator($conn, $table_name);
foreach ($iterator as $account_row) {
// We don't need to migrate "accountID" values for "password" accounts,
// since these were dummy values in the first place and are no longer
// read or written after D21014. (There would be no harm in writing these
// rows, but it's easy to skip them.)
if ($account_row['accountType'] === 'password') {
continue;
}
$account_id = $account_row['accountID'];
if (!strlen($account_id)) {
continue;
}
queryfx(
$conn,
'INSERT IGNORE INTO %R (
phid, externalAccountPHID, providerConfigPHID,
identifierHash, identifierRaw,
dateCreated, dateModified)
VALUES (%s, %s, %s, %s, %s, %d, %d)',
$identifier_table,
$identifier_table->generatePHID(),
$account_row['phid'],
$account_row['providerConfigPHID'],
PhabricatorHash::digestForIndex($account_id),
$account_id,
$account_row['dateCreated'],
$account_row['dateModified']);
}

View File

@@ -0,0 +1,21 @@
<?php
// See T13493. This table previously had a UNIQUE KEY on "<accountType,
// accountDomain, accountID>", which is obsolete. The application now violates
// this key, so make sure it gets dropped.
// There's no "IF EXISTS" modifier for "ALTER TABLE" so run this as a PHP patch
// instead of an SQL patch.
$table = new PhabricatorExternalAccount();
$conn = $table->establishConnection('w');
try {
queryfx(
$conn,
'ALTER TABLE %R DROP KEY %T',
$table,
'account_details');
} catch (AphrontQueryException $ex) {
// Ignore.
}

View File

@@ -0,0 +1,9 @@
CREATE TABLE {$NAMESPACE}_paste.paste_paste_fdocument (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
objectPHID VARBINARY(64) NOT NULL,
isClosed BOOL NOT NULL,
authorPHID VARBINARY(64),
ownerPHID VARBINARY(64),
epochCreated INT UNSIGNED NOT NULL,
epochModified INT UNSIGNED NOT NULL
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,8 @@
CREATE TABLE {$NAMESPACE}_paste.paste_paste_ffield (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
documentID INT UNSIGNED NOT NULL,
fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT},
rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT},
termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT},
normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,5 @@
CREATE TABLE {$NAMESPACE}_paste.paste_paste_fngrams (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
documentID INT UNSIGNED NOT NULL,
ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,7 @@
CREATE TABLE {$NAMESPACE}_paste.paste_paste_fngrams_common (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT},
needsCollection BOOL NOT NULL,
UNIQUE KEY `key_ngram` (ngram),
KEY `key_collect` (needsCollection)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,9 @@
CREATE TABLE {$NAMESPACE}_differential.differential_viewstate (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
viewerPHID VARBINARY(64) NOT NULL,
objectPHID VARBINARY(64) NOT NULL,
viewState LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_viewer` (viewerPHID, objectPHID)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_differential.differential_transaction_comment
ADD attributes LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_audit.audit_transaction_comment
ADD attributes LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_differential.differential_transaction_comment
SET attributes = '{}' WHERE attributes = '';

View File

@@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_audit.audit_transaction_comment
SET attributes = '{}' WHERE attributes = '';

View File

@@ -0,0 +1 @@
DROP TABLE {$NAMESPACE}_differential.differential_changeset_parse_cache;

View File

@@ -0,0 +1,7 @@
CREATE TABLE {$NAMESPACE}_differential.differential_changeset_parse_cache (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
cacheIndex BINARY(12) NOT NULL,
cache LONGBLOB NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
UNIQUE KEY `key_cacheIndex` (cacheIndex)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1 @@
DROP TABLE IF EXISTS {$NAMESPACE}_differential.differential_commit;

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/init/init-script.php';
$xml = $root.'/externals/cldr/cldr_windows_timezones.xml';
$xml = Filesystem::readFile($xml);
$xml = new SimpleXMLElement($xml);
$result_map = array();
$ignore = array(
'UTC',
'UTC-11',
'UTC-02',
'UTC-08',
'UTC-09',
'UTC+12',
);
$ignore = array_fuse($ignore);
$zones = $xml->windowsZones->mapTimezones->mapZone;
foreach ($zones as $zone) {
$windows_name = (string)$zone['other'];
$target_name = (string)$zone['type'];
// Ignore the offset-based timezones from the CLDR map, since we handle
// these later.
if (isset($ignore[$windows_name])) {
continue;
}
// We've already seen this timezone so we don't need to add it to the map
// again.
if (isset($result_map[$windows_name])) {
continue;
}
$result_map[$windows_name] = $target_name;
}
asort($result_map);
echo id(new PhutilJSON())
->encodeFormatted($result_map);

View File

@@ -0,0 +1,126 @@
{
"Egypt Standard Time": "Africa/Cairo",
"Morocco Standard Time": "Africa/Casablanca",
"South Africa Standard Time": "Africa/Johannesburg",
"W. Central Africa Standard Time": "Africa/Lagos",
"E. Africa Standard Time": "Africa/Nairobi",
"Libya Standard Time": "Africa/Tripoli",
"Namibia Standard Time": "Africa/Windhoek",
"Aleutian Standard Time": "America/Adak",
"Alaskan Standard Time": "America/Anchorage",
"Tocantins Standard Time": "America/Araguaina",
"Paraguay Standard Time": "America/Asuncion",
"Bahia Standard Time": "America/Bahia",
"SA Pacific Standard Time": "America/Bogota",
"Argentina Standard Time": "America/Buenos_Aires",
"Eastern Standard Time (Mexico)": "America/Cancun",
"Venezuela Standard Time": "America/Caracas",
"SA Eastern Standard Time": "America/Cayenne",
"Central Standard Time": "America/Chicago",
"Mountain Standard Time (Mexico)": "America/Chihuahua",
"Central Brazilian Standard Time": "America/Cuiaba",
"Mountain Standard Time": "America/Denver",
"Greenland Standard Time": "America/Godthab",
"Turks And Caicos Standard Time": "America/Grand_Turk",
"Central America Standard Time": "America/Guatemala",
"Atlantic Standard Time": "America/Halifax",
"Cuba Standard Time": "America/Havana",
"US Eastern Standard Time": "America/Indianapolis",
"SA Western Standard Time": "America/La_Paz",
"Pacific Standard Time": "America/Los_Angeles",
"Central Standard Time (Mexico)": "America/Mexico_City",
"Saint Pierre Standard Time": "America/Miquelon",
"Montevideo Standard Time": "America/Montevideo",
"Eastern Standard Time": "America/New_York",
"US Mountain Standard Time": "America/Phoenix",
"Haiti Standard Time": "America/Port-au-Prince",
"Canada Central Standard Time": "America/Regina",
"Pacific SA Standard Time": "America/Santiago",
"E. South America Standard Time": "America/Sao_Paulo",
"Newfoundland Standard Time": "America/St_Johns",
"Pacific Standard Time (Mexico)": "America/Tijuana",
"Central Asia Standard Time": "Asia/Almaty",
"Jordan Standard Time": "Asia/Amman",
"Arabic Standard Time": "Asia/Baghdad",
"Azerbaijan Standard Time": "Asia/Baku",
"SE Asia Standard Time": "Asia/Bangkok",
"Altai Standard Time": "Asia/Barnaul",
"Middle East Standard Time": "Asia/Beirut",
"India Standard Time": "Asia/Calcutta",
"Transbaikal Standard Time": "Asia/Chita",
"Sri Lanka Standard Time": "Asia/Colombo",
"Syria Standard Time": "Asia/Damascus",
"Bangladesh Standard Time": "Asia/Dhaka",
"Arabian Standard Time": "Asia/Dubai",
"West Bank Standard Time": "Asia/Hebron",
"W. Mongolia Standard Time": "Asia/Hovd",
"North Asia East Standard Time": "Asia/Irkutsk",
"Israel Standard Time": "Asia/Jerusalem",
"Afghanistan Standard Time": "Asia/Kabul",
"Russia Time Zone 11": "Asia/Kamchatka",
"Pakistan Standard Time": "Asia/Karachi",
"Nepal Standard Time": "Asia/Katmandu",
"North Asia Standard Time": "Asia/Krasnoyarsk",
"Magadan Standard Time": "Asia/Magadan",
"N. Central Asia Standard Time": "Asia/Novosibirsk",
"Omsk Standard Time": "Asia/Omsk",
"North Korea Standard Time": "Asia/Pyongyang",
"Myanmar Standard Time": "Asia/Rangoon",
"Arab Standard Time": "Asia/Riyadh",
"Sakhalin Standard Time": "Asia/Sakhalin",
"Korea Standard Time": "Asia/Seoul",
"China Standard Time": "Asia/Shanghai",
"Singapore Standard Time": "Asia/Singapore",
"Russia Time Zone 10": "Asia/Srednekolymsk",
"Taipei Standard Time": "Asia/Taipei",
"West Asia Standard Time": "Asia/Tashkent",
"Georgian Standard Time": "Asia/Tbilisi",
"Iran Standard Time": "Asia/Tehran",
"Tokyo Standard Time": "Asia/Tokyo",
"Tomsk Standard Time": "Asia/Tomsk",
"Ulaanbaatar Standard Time": "Asia/Ulaanbaatar",
"Vladivostok Standard Time": "Asia/Vladivostok",
"Yakutsk Standard Time": "Asia/Yakutsk",
"Ekaterinburg Standard Time": "Asia/Yekaterinburg",
"Caucasus Standard Time": "Asia/Yerevan",
"Azores Standard Time": "Atlantic/Azores",
"Cape Verde Standard Time": "Atlantic/Cape_Verde",
"Greenwich Standard Time": "Atlantic/Reykjavik",
"Cen. Australia Standard Time": "Australia/Adelaide",
"E. Australia Standard Time": "Australia/Brisbane",
"AUS Central Standard Time": "Australia/Darwin",
"Aus Central W. Standard Time": "Australia/Eucla",
"Tasmania Standard Time": "Australia/Hobart",
"Lord Howe Standard Time": "Australia/Lord_Howe",
"W. Australia Standard Time": "Australia/Perth",
"AUS Eastern Standard Time": "Australia/Sydney",
"Dateline Standard Time": "Etc/GMT+12",
"Astrakhan Standard Time": "Europe/Astrakhan",
"W. Europe Standard Time": "Europe/Berlin",
"GTB Standard Time": "Europe/Bucharest",
"Central Europe Standard Time": "Europe/Budapest",
"E. Europe Standard Time": "Europe/Chisinau",
"Turkey Standard Time": "Europe/Istanbul",
"Kaliningrad Standard Time": "Europe/Kaliningrad",
"FLE Standard Time": "Europe/Kiev",
"GMT Standard Time": "Europe/London",
"Belarus Standard Time": "Europe/Minsk",
"Russian Standard Time": "Europe/Moscow",
"Romance Standard Time": "Europe/Paris",
"Russia Time Zone 3": "Europe/Samara",
"Central European Standard Time": "Europe/Warsaw",
"Mauritius Standard Time": "Indian/Mauritius",
"Samoa Standard Time": "Pacific/Apia",
"New Zealand Standard Time": "Pacific/Auckland",
"Bougainville Standard Time": "Pacific/Bougainville",
"Chatham Islands Standard Time": "Pacific/Chatham",
"Easter Island Standard Time": "Pacific/Easter",
"Fiji Standard Time": "Pacific/Fiji",
"Central Pacific Standard Time": "Pacific/Guadalcanal",
"Hawaiian Standard Time": "Pacific/Honolulu",
"Line Islands Standard Time": "Pacific/Kiritimati",
"Marquesas Standard Time": "Pacific/Marquesas",
"Norfolk Standard Time": "Pacific/Norfolk",
"West Pacific Standard Time": "Pacific/Port_Moresby",
"Tonga Standard Time": "Pacific/Tongatapu"
}

View File

@@ -0,0 +1,172 @@
#!/usr/local/bin/php
<?php
#
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# The Original Code is Copyright (C) 2019, Blender Foundation
# All rights reserved.
#
# Contributor(s): Sergey Sharybin.
#
# ***** END GPL LICENSE BLOCK *****
#
# This scripts implements external AuthBasicProvider which can be used
# to authentificate users using Phabricator's database.
#
# Example configuration:
#
# .htaccess file:
#
# AuthType Basic
# AuthName "Please Enter Password"
# AuthBasicProvider external
# AuthExternal phabricator
# Require valid-user
#
# It is also required to have the following provider registered in the
# configuration. There are two ways to do it:
#
# 1. Separate conf file in /etc/apache2/conf-enabled
# Create a file (say, authz_external_phabricator.conf) with the following
# content:
#
# DefineExternalAuth phabricator pipe /path/to/auth_provider.php
#
# This method allows to use provider in any VHOST.
#
# Downside: from local tests .htaccess file picks the provider nicely,
# but attempts to specifu the provider in virtual host configuration
# resulted in issues (seems to be different initialization order between
# global config in those files and virtual hosts).
#
# 2. Define provider in the virtual host definition:
#
# <IfModule mod_authnz_external.c>
# AddExternalAuth phabricator /path/to/auth_provider.php
# SetExternalAuthMethod phabricator pipe
# </IfModule>
#
# This method allowed to use this auth provider for SVN DAV.
$IS_DEBUG = false;
$PHABRICATOR_ROOT = dirname(dirname(dirname(__FILE__)));
require_once $PHABRICATOR_ROOT.'/scripts/__init_script__.php';
function initLogging() {
global $IS_DEBUG;
global $argv;
for ($i = 1; $i < count($argv); ++$i) {
if ($argv[$i] == '--debug') {
$IS_DEBUG = true;
}
}
}
function debugLog(string $text) {
global $IS_DEBUG;
if (!$IS_DEBUG) {
return;
}
print("${text}\n");
}
function removeSingleTrailingNewline(string $string) {
if ($string === '') {
return $string;
}
$length = strlen($string);
if ($string[$length - 1] == "\n") {
return substr($string, 0, -1);
}
return $string;
}
class AuthRequest {
public $user_name;
public $password;
static function fromStdin() {
$auth_request = new AuthRequest();
$stdin = fopen('php://stdin', 'r');
$auth_request->user_name = removeSingleTrailingNewline(fgets($stdin));
$auth_request->password = removeSingleTrailingNewline(fgets($stdin));
return $auth_request;
}
};
function getUserForAuthRequest(AuthRequest $auth_request) {
if ($auth_request->user_name === '') {
return null;
}
$username_or_email = $auth_request->user_name;
$user = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$username_or_email);
if (!$user) {
$user = PhabricatorUser::loadOneWithEmailAddress(
$username_or_email);
}
return $user;
}
function createContentSourceForAuth() {
return PhabricatorContentSource::newForSource(
PhabricatorUnitTestContentSource::SOURCECONST);
}
function isValidAuth(AuthRequest $auth_request) {
debugLog("Begin authentification check for user '$auth_request->user_name'");
$user = getUserForAuthRequest($auth_request);
if (!$user) {
debugLog("No PhabricatorUser() object found for requested user.");
return false;
}
$content_source = createContentSourceForAuth();
$envelope = new PhutilOpaqueEnvelope($auth_request->password);
$engine = id(new PhabricatorAuthPasswordEngine())
->setViewer($user)
->setContentSource($content_source)
->setPasswordType(PhabricatorAuthPassword::PASSWORD_TYPE_ACCOUNT)
->setObject($user);
if (!$engine->isValidPassword($envelope)) {
debugLog('Engine reported invalid password.');
return false;
}
debugLog('Authentirfication passed.');
return true;
}
function main() {
initLogging();
$auth_request = AuthRequest::fromStdin();
if (!isValidAuth($auth_request)) {
exit(1);
}
exit(0);
}
main();
?>

View File

@@ -0,0 +1,131 @@
#!/usr/bin/env php
<?php
if (function_exists('pcntl_async_signals')) {
pcntl_async_signals(true);
} else {
declare(ticks = 1);
}
require_once dirname(__FILE__).'/../../__init_script__.php';
if (!posix_isatty(STDOUT)) {
$sid = posix_setsid();
if ($sid <= 0) {
throw new Exception(pht('Failed to create new process session!'));
}
}
$args = new PhutilArgumentParser($argv);
$args->setTagline(pht('daemon executor'));
$args->setSynopsis(<<<EOHELP
**exec_daemon.php** [__options__] __daemon__ ...
Run an instance of __daemon__.
EOHELP
);
$args->parse(
array(
array(
'name' => 'trace',
'help' => pht('Enable debug tracing.'),
),
array(
'name' => 'trace-memory',
'help' => pht('Enable debug memory tracing.'),
),
array(
'name' => 'verbose',
'help' => pht('Enable verbose activity logging.'),
),
array(
'name' => 'label',
'short' => 'l',
'param' => 'label',
'help' => pht(
'Optional process label. Makes "%s" nicer, no behavioral effects.',
'ps'),
),
array(
'name' => 'daemon',
'wildcard' => true,
),
));
$trace_memory = $args->getArg('trace-memory');
$trace_mode = $args->getArg('trace') || $trace_memory;
$verbose = $args->getArg('verbose');
if (function_exists('posix_isatty') && posix_isatty(STDIN)) {
fprintf(STDERR, pht('Reading daemon configuration from stdin...')."\n");
}
$config = @file_get_contents('php://stdin');
$config = id(new PhutilJSONParser())->parse($config);
PhutilTypeSpec::checkMap(
$config,
array(
'log' => 'optional string|null',
'argv' => 'optional list<wild>',
'load' => 'optional list<string>',
'down' => 'optional int',
));
$log = idx($config, 'log');
if ($log) {
ini_set('error_log', $log);
PhutilErrorHandler::setErrorListener(array('PhutilDaemon', 'errorListener'));
}
$load = idx($config, 'load', array());
foreach ($load as $library) {
$library = Filesystem::resolvePath($library);
phutil_load_library($library);
}
PhutilErrorHandler::initialize();
$daemon = $args->getArg('daemon');
if (!$daemon) {
throw new PhutilArgumentUsageException(
pht('Specify which class of daemon to start.'));
} else if (count($daemon) > 1) {
throw new PhutilArgumentUsageException(
pht('Specify exactly one daemon to start.'));
} else {
$daemon = head($daemon);
if (!class_exists($daemon)) {
throw new PhutilArgumentUsageException(
pht(
'No class "%s" exists in any known library.',
$daemon));
} else if (!is_subclass_of($daemon, 'PhutilDaemon')) {
throw new PhutilArgumentUsageException(
pht(
'Class "%s" is not a subclass of "%s".',
$daemon,
'PhutilDaemon'));
}
}
$argv = idx($config, 'argv', array());
$daemon = newv($daemon, array($argv));
if ($trace_mode) {
$daemon->setTraceMode();
}
if ($trace_memory) {
$daemon->setTraceMemory();
}
if ($verbose) {
$daemon->setVerbose(true);
}
$down_duration = idx($config, 'down');
if ($down_duration) {
$daemon->setScaledownDuration($down_duration);
}
$daemon->execute();

6
scripts/gitadmin/gitx-ssh Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/local/bin/bash
set -e
set -u
ssh -i $SSH_KEYFILE $@

View File

@@ -0,0 +1,443 @@
#!/usr/local/bin/php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
function escape_name($name) {
return preg_replace('/[^A-Za-z0-9\-]/', '_', $name);
}
function startswith($string, $prefix) {
return substr($string, 0, strlen($prefix)) == $prefix;
}
function endswith($string, $suffix) {
$suffix_length = strlen($suffix);
return substr($string, strlen($string) - $suffix_length,
$suffix_length) == $suffix;
}
function write_ini_file($array, $file) {
$res = array();
foreach ($array as $key => $val) {
if (is_array($val)) {
$res[] = "[$key]";
foreach ($val as $skey => $sval) {
$res[] = "$skey = $sval";
}
$res[] = '';
} else {
$res[] = "$key = $val";
}
}
file_put_contents($file, implode("\n", $res));
}
function getProjectMembersPHIDs($viewer, $project_phid) {
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->needMembers(true)
->withPHIDs(array($project_phid))
->executeOne();
return $project->getMemberPHIDs();
}
// Get user's heys and put them to the configuration
function handleSingleUserPHID(
$keydir, $viewer, $userPHID, $system_keys, &$used_keys) {
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($userPHID))
->executeOne();
if (!$user) {
return array();
}
if ($user->getIsDisabled()) {
return array();
}
$keys = id(new PhabricatorAuthSSHKey())->loadAllWhere(
'objectPHID = %s',
$user->getPHID());
$members = array();
foreach ($keys as $key) {
$full_key_content =
$key->getKeyType().' '.
$key->getKeyBody().' '.
$key->getKeyComment()."\n";
if (array_key_exists($full_key_content, $system_keys)) {
$members[] = $system_keys[$full_key_content];
} else {
$escaped_key_name = escape_name($key->getName());
$member = 'PHAB_'.$user->getUserName().
'_'.$escaped_key_name.
'_'.$key->getID();
$members[] = $member;
if (!array_key_exists($member, $used_keys)) {
$used_keys[$member] = true;
file_put_contents("$keydir/$member.pub", $full_key_content);
}
}
}
return $members;
}
function handleUsersPolicyRule(
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
$members = array();
foreach ($rule['value'] as $userPHID) {
$members = array_merge($members,
handleSingleUserPHID($keydir, $viewer, $userPHID,
$system_keys, $used_keys));
}
return $members;
}
function handleProjectsPolicyRule(
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
$members = array();
foreach ($rule['value'] as $projectPHID) {
$memberPHIDs = getProjectMembersPHIDs($viewer, $projectPHID);
foreach ($memberPHIDs as $userPHID) {
$members = array_merge($members,
handleSingleUserPHID($keydir, $viewer, $userPHID,
$system_keys, $used_keys));
}
}
return $members;
}
function handleProjectsAllPolicyRule(
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
$is_first_project = true;
$allowed_members_phids = array();
foreach ($rule['value'] as $project_phid) {
$memberPHIDs = getProjectMembersPHIDs($viewer, $project_phid);
if ($is_first_project) {
$allowed_members_phids = $memberPHIDs;
$is_first_project = false;
} else {
$allowed_members_phids = array_intersect(
$allowed_members_phids, $memberPHIDs);
}
}
$members = array();
foreach ($allowed_members_phids as $userPHID) {
$members = array_merge($members,
handleSingleUserPHID($keydir, $viewer, $userPHID,
$system_keys, $used_keys));
}
return $members;
}
function handleAdministratorsPolicyRule(
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
$administrators = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIsAdmin(true)
->execute();
$members = array();
foreach ($administrators as $administrator) {
$members = array_merge($members,
handleSingleUserPHID($keydir, $viewer, $administrator->getPHID(),
$system_keys, $used_keys));
}
return $members;
}
function handleLegalpadSingleDocument(
$keydir, $viewer, $document, $system_keys, &$used_keys) {
if ($document->getSignatureType() !=
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
return array();
}
$members = array();
foreach ($document->getSignatures() as $signature) {
if ($signature->getSignatureType() !=
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
continue;
}
$members = array_merge($members,
handleSingleUserPHID($keydir, $viewer, $signature->getSignerPHID(),
$system_keys, $used_keys));
}
return $members;
}
function handleLegalpadSignaturePolicyRule(
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
$documents = id(new LegalpadDocumentQuery())
->setViewer($viewer)
->withPHIDs($rule['value'])
->needSignatures(true)
->execute();
$members = array();
foreach ($documents as $document) {
$members = array_merge(
$members,
handleLegalpadSingleDocument(
$keydir, $viewer, $document, $system_keys, $used_keys));
}
return $members;
}
function handleCustomPolicy(
$keydir, $viewer, $policy, $system_keys, &$used_keys) {
$members = array();
$rules = $policy->getRules();
foreach ($rules as $rule) {
// Everyone is denied by default anyway
if ($rule['action'] != 'allow') {
continue;
}
$policy_members = array();
$rule_type = $rule['rule'];
if ($rule_type == 'PhabricatorPolicyRuleUsers') {
$policy_members = handleUsersPolicyRule(
$keydir, $viewer, $rule, $system_keys, $used_keys);
} else if ($rule_type == 'PhabricatorProjectsPolicyRule') {
$policy_members = handleProjectsPolicyRule(
$keydir, $viewer, $rule, $system_keys, $used_keys);
} else if ($rule_type == 'PhabricatorProjectsAllPolicyRule') {
$policy_members = handleProjectsAllPolicyRule(
$keydir, $viewer, $rule, $system_keys, $used_keys);
} else if ($rule_type == 'PhabricatorAdministratorsPolicyRule') {
$policy_members = handleAdministratorsPolicyRule(
$keydir, $viewer, $rule, $system_keys, $used_keys);
} else if ($rule_type == 'PhabricatorLegalpadSignaturePolicyRule') {
$policy_members = handleLegalpadSignaturePolicyRule(
$keydir, $viewer, $rule, $system_keys, $used_keys);
}
$members = array_merge($members, $policy_members);
}
return $members;
}
// Parse repository and put it's members to the config file
function handleSingleRepository(
$keydir, $viewer, $repository, $all_repositories, $system_keys,
&$new_configuration, &$used_keys) {
$policies = PhabricatorPolicyQuery::loadPolicies(
$viewer,
$repository);
$pushable = $policies[DiffusionPushCapability::CAPABILITY];
$type = $pushable->getType();
$members = array();
if ($type == PhabricatorPolicyType::TYPE_PROJECT) {
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->needMembers(true)
->withPHIDs(array($pushable->getPHID()))
->executeOne();
$memberPHIDs = $project->getMemberPHIDs();
foreach ($memberPHIDs as $memberPHID) {
$members = array_merge($members,
handleSingleUserPHID($keydir, $viewer, $memberPHID,
$system_keys, $used_keys));
}
} else if ($type == PhabricatorPolicyType::TYPE_USER) {
$members = handleSingleUserPHID(
$keydir, $viewer, $pushable->getPHID(), $system_keys, $used_keys);
} else if ($type == PhabricatorPolicyType::TYPE_CUSTOM) {
$members = handleCustomPolicy(
$keydir, $viewer, $pushable, $system_keys, $used_keys);
} else {
/* pass */
}
if (count($members)) {
$uri = $repository->getRemoteURI();
$repository_name = basename($uri, '.git');
$escaped_repository_name = escape_name($repository->getName());
$group_name = "PHAB_${escaped_repository_name}";
$values = array();
$values['members'] = join(' ', $members);
$values['readonly'] = join(' ', $all_repositories);
$values['writable'] = $repository_name;
$new_configuration["group $group_name"] = $values;
}
}
// Remove groups from previous automated configuration built
function getCleanOldConfiguration($old_configuration) {
$new_configuration = array();
foreach ($old_configuration as $group => $values) {
if (!startswith($group, 'group PHAB')) {
$new_configuration[$group] = $values;
}
}
return $new_configuration;
}
// Get non-phab keys
function getSystemPublicKeys($keydir) {
$files = scandir($keydir);
$system_keys = array();
foreach ($files as $file) {
if (!startswith($file, "PHAB") && endswith($file, '.pub')) {
$key = file_get_contents("$keydir/$file");
$system_keys[$key] = basename($file, '.pub');
}
}
return $system_keys;
}
// Remove unused public keys
function removeUnusedPublicKeys($keydir, $used_keys) {
$files = scandir($keydir);
foreach ($files as $file) {
if (startswith($file, "PHAB")) {
$member = basename($file, '.pub');
if (!array_key_exists($member, $used_keys)) {
unlink("$keydir/$file");
}
}
}
}
function rebuildConfiguration($gitosis_root) {
$keydir = "$gitosis_root/keydir";
$configuration_file = "$gitosis_root/gitosis.conf";
if (!file_exists($configuration_file)) {
print("Not found: $configuration_file\n");
return false;
}
$viewer = id(new PhabricatorUser())
->loadOneWhere('username = %s', 'sergey');
$old_configuration = parse_ini_file(
$configuration_file, true, INI_SCANNER_RAW);
$new_configuration = getCleanOldConfiguration(
$old_configuration);
// Get "system" keys to re-use if phab account uses the
// same public key
$system_keys = getSystemPublicKeys($keydir);
// Get list of all repos which is awailable for read
$all_repositories = array();
foreach ($old_configuration as $group => $values) {
if (startswith($group, 'repo')) {
$repository_name = substr($group, 5, strlen($group) - 5);
if ($repository_name == 'gitosis-admin')
continue;
$all_repositories[] = $repository_name;
}
}
// Fill in new configuration and keys
$used_keys = array();
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->execute();
foreach ($repositories as $repository_id => $repository) {
$type = $repository->getVersionControlSystem();
if ($type == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) {
handleSingleRepository(
$keydir, $viewer, $repository, $all_repositories, $system_keys,
$new_configuration, $used_keys);
}
}
write_ini_file($new_configuration, $configuration_file);
removeUnusedPublicKeys($keydir, $used_keys);
return true;
}
function getGitCommand($repository) {
$git_dir = realpath("$repository/.git");
$git = "git --git-dir='$git_dir'";
$git .= ' --work-tree='.realpath($repository);
return $git;
}
function runGitCommand($repository, $arguments,
&$output=null, &$return_var=null) {
$git = getGitCommand($repository);
$git .= " $arguments";
exec($git, $output, $return_var);
return $return_var == 0;
}
function runGitSshCommand($repository, $key, $arguments,
&$output=null, &$return_var=null) {
$gitx_ssh = realpath(dirname(__FILE__) . "/gitx-ssh");
$abs_key = realpath($key);
$git = "SSH_KEYFILE=$abs_key GIT_SSH=$gitx_ssh ";
$git .= getGitCommand($repository);
$git .= " $arguments";
exec($git, $output, $return_var);
return $return_var == 0;
}
function repositoryPull($repository, $key) {
return runGitSshCommand($repository, $key, 'pull');
}
function repositoryCommitAll($repository, $author, $message) {
if (!runGitCommand(
$repository, 'ls-files --other --exclude-standard', $untracked_files)) {
return false;
}
if (count($untracked_files)) {
$flat_files = join(' ', $untracked_files);
if (!runGitCommand($repository, "add $flat_files")) {
return false;
}
}
runGitCommand($repository, "update-index -q --refresh", $output);
runGitCommand($repository, "diff-index --name-only HEAD --", $output);
if (count($output)) {
return runGitCommand(
$repository, "commit --author='$author' -a -m '$message'", $output);
}
return true;
}
if (count($argv) != 3) {
print("Usage: {$argv[0]} /path/to/gitosis-admin /path/to/id_rsa.pub\n");
exit(1);
}
$gitosis_root = $argv[1];
$key = $argv[2];
if (!repositoryPull($gitosis_root, $key)) {
print("Failed to pull changes from server.\n");
exit(1);
}
if (!rebuildConfiguration($gitosis_root)) {
exit(1);
}
if (!repositoryCommitAll(
$gitosis_root, 'Rebuild Gitadmin <null@git.blender.org>',
'Update to correspond changes in Phabricator')) {
print("Failed to commit changes.\n");
exit(1);
}
runGitSshCommand($gitosis_root, $key, 'push origin master');
?>

View File

@@ -0,0 +1,504 @@
#!/usr/local/bin/php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
////////////////////////////////////////////////////////////////////////////////
// Utilities.
//
// TODO(sergey): Move somewhere else. Or, evenmore ideally, use Phabricator's
// utilities instead.
function escape_name($name) {
return preg_replace('/[^A-Za-z0-9]/', '_', $name);
}
function startswith($string, $prefix) {
return substr($string, 0, strlen($prefix)) == $prefix;
}
function endswith($string, $suffix) {
$suffix_length = strlen($suffix);
return substr($string, strlen($string) - $suffix_length,
$suffix_length) == $suffix;
}
function file_put_contents_if_different($file_name, $content) {
if (file_exists($file_name)) {
$current_content = file_get_contents($file_name);
if ($current_content == $content) {
return;
}
}
file_put_contents($file_name, $content);
}
////////////////////////////////////////////////////////////////////////////////
// Phabricator access list traversal.
class Configuration {
// Phabricator user which is used as a viewer.
public $viewer;
// Directory where public keys are stored.
// Full path.
protected $keys_directory;
// Gitolite configuration file (gitolite.conf).
// Full path.
protected $config_file;
// Indexed by key content, contains configuration user name.
protected $stored_keys;
// Indexed by config user name.
protected $used_keys;
// Indexed by committers variable name, contains list of users which are
// configured by Phabricator to be able to commit to the repository.
protected $committers;
public function __construct($gitolite_root) {
$this->viewer = PhabricatorUser::getOmnipotentUser();
$this->keys_directory = "$gitolite_root/keydir";
$this->config_file = "$gitolite_root/conf/gitolite.conf";
$this->collectSystemPublicKeys();
$this->used_keys = array();
$this->committers = array();
if (!file_exists($this->config_file)) {
die("Not found: $this->config_file\n");
}
}
// Store given key of given user.
//
// Includes both storing public key in the file, and storing mapping between
// user and the key.
public function storeUserPublicKey($user, $key) {
$full_key_content = $this->getPublicKeyContent($key);
if (array_key_exists($full_key_content, $this->stored_keys)) {
return $this->stored_keys[$full_key_content];
}
$config_user_name = $this->getConfigUserName($user, $key);
if (!array_key_exists($config_user_name, $this->used_keys)) {
$this->used_keys[$config_user_name] = true;
file_put_contents_if_different("$this->keys_directory/$config_user_name.pub",
$full_key_content);
}
$this->stored_keys[$full_key_content] = $config_user_name;
return $config_user_name;
}
public function setRepositoryUsers($repository, $config_user_names) {
$uri = $repository->getRemoteURI();
$repository_name = basename($uri, '.git');
$variable_name = '@committers_' . escape_name(strtolower($repository_name));
if (array_key_exists($variable_name, $this->committers)) {
die("Duplicate variable mapping for repository $uri\n");
}
$this->committers[$variable_name] = $config_user_names;
}
public function writeNewConfiguration() {
$current_config = file_get_contents($this->config_file);
$current_config_lines = explode("\n", $current_config);
$new_config = "";
foreach ($current_config_lines as $line) {
if (startswith($line, '@committers_')) {
$parts = explode('=', $line);
$variable_name = trim($parts[0]);
if (array_key_exists($variable_name, $this->committers)) {
$system_committers = $this->getNonPhabtricatorUsers($parts[1]);
$all_committers = array_merge(
$system_committers, $this->committers[$variable_name]);
$unique_committers = array_unique($all_committers);
$committers = implode(' ', $unique_committers);
$line = "$variable_name = $committers";
}
}
$new_config .= $line . "\n";
}
file_put_contents_if_different($this->config_file, trim($new_config) . "\n");
}
protected function getNonPhabtricatorUsers($configuration_value) {
$system_users = array();
$users = explode(' ', $configuration_value);
foreach ($users as $user) {
$user = trim($user);
if (empty($user)) {
continue;
}
if (startswith($user, 'PHAB')) {
continue;
}
$system_users[] = $user;
}
return $system_users;
}
public function finalize() {
$this->removeUnusedPublicKeys();
}
// Get content of a public key to be stored in file.
protected function getPublicKeyContent($key) {
return $key->getKeyType().' '.
$key->getKeyBody().' '.
$key->getKeyComment()."\n";
}
// Get user+key name used by the Gitolite configuration.
protected function getConfigUserName($user, $key) {
$escaped_key_name = escape_name($key->getName());
return 'PHAB_'.$user->getUserName().
'_'.$escaped_key_name.
'_'.$key->getID();
}
// Get keys which are not managed by this Phabricator/Git integration script.
//
// Returns map from key content to the key file name. This is used to avoid
// public key duplication in the case system key is used by phabricator user.
protected function collectSystemPublicKeys() {
$files = scandir($this->keys_directory);
foreach ($files as $file) {
if (startswith($file, "PHAB")) {
continue;
}
if (!endswith($file, '.pub')) {
continue;
}
$key = file_get_contents("$this->keys_directory/$file");
$file_we = basename($file, '.pub');
$this->stored_keys[$key] = $file_we;
$this->used_keys[$file_we] = true;
}
}
protected function removeUnusedPublicKeys() {
$files = scandir($this->keys_directory);
foreach ($files as $file) {
if (!startswith($file, "PHAB")) {
continue;
}
$config_user_name = basename($file, '.pub');
if (!array_key_exists($config_user_name, $this->used_keys)) {
unlink("$this->keys_directory/$file");
}
}
}
};
function getProjectMembersPHIDs($viewer, $project_phid) {
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->needMembers(true)
->withPHIDs(array($project_phid))
->executeOne();
return $project->getMemberPHIDs();
}
// Get user's heys and put them to the configuration
function handleSingleUserPHID($config, $userPHID) {
$user = id(new PhabricatorPeopleQuery())
->setViewer($config->viewer)
->withPHIDs(array($userPHID))
->executeOne();
if (!$user) {
return array();
}
if ($user->getIsDisabled()) {
return array();
}
$keys = id(new PhabricatorAuthSSHKey())->loadAllWhere(
'objectPHID = %s',
$user->getPHID());
$config_user_names = array();
foreach ($keys as $key) {
$config_user_name = $config->storeUserPublicKey($user, $key);
$config_user_names[] = $config_user_name;
}
return $config_user_names;
}
function handleUsersPolicyRule($config, $rule) {
$config_user_names = array();
foreach ($rule['value'] as $userPHID) {
$config_user_names = array_merge($config_user_names,
handleSingleUserPHID($config, $userPHID));
}
return $config_user_names;
}
function handleProjectsPolicyRule($config, $rule) {
$config_user_names = array();
foreach ($rule['value'] as $projectPHID) {
$memberPHIDs = getProjectMembersPHIDs($config->viewer, $projectPHID);
foreach ($memberPHIDs as $userPHID) {
$config_user_names = array_merge($config_user_names,
handleSingleUserPHID($config, $userPHID));
}
}
return $config_user_names;
}
function handleProjectsAllPolicyRule($config, $rule) {
$is_first_project = true;
$allowed_members_phids = array();
foreach ($rule['value'] as $project_phid) {
$memberPHIDs = getProjectMembersPHIDs($config->viewer, $project_phid);
if ($is_first_project) {
$allowed_members_phids = $memberPHIDs;
$is_first_project = false;
} else {
$allowed_members_phids = array_intersect(
$allowed_members_phids, $memberPHIDs);
}
}
$config_user_names = array();
foreach ($allowed_members_phids as $userPHID) {
$config_user_names = array_merge($config_user_names,
handleSingleUserPHID($config, $userPHID));
}
return $config_user_names;
}
function handleAdministratorsPolicyRule($config, $rule) {
$administrators = id(new PhabricatorPeopleQuery())
->setViewer($config->viewer)
->withIsAdmin(true)
->execute();
$config_user_names = array();
foreach ($administrators as $administrator) {
$config_user_names = array_merge($config_user_names,
handleSingleUserPHID($config, $administrator->getPHID()));
}
return $config_user_names;
}
function handleLegalpadSingleDocument($config, $document) {
if ($document->getSignatureType() !=
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
return array();
}
$config_user_names = array();
foreach ($document->getSignatures() as $signature) {
if ($signature->getSignatureType() !=
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
continue;
}
$config_user_names = array_merge($config_user_names,
handleSingleUserPHID($config, $signature->getSignerPHID()));
}
return $config_user_names;
}
function handleLegalpadSignaturePolicyRule($config, $rule) {
$documents = id(new LegalpadDocumentQuery())
->setViewer($config->viewer)
->withPHIDs($rule['value'])
->needSignatures(true)
->execute();
$config_user_names = array();
foreach ($documents as $document) {
$config_user_names = array_merge($config_user_names,
handleLegalpadSingleDocument($config, $document));
}
return $config_user_names;
}
function handleCustomPolicy($config, $policy) {
$config_user_names = array();
$rules = $policy->getRules();
foreach ($rules as $rule) {
// Everyone is denied by default anyway
if ($rule['action'] != 'allow') {
continue;
}
$policy_config_user_names = array();
$rule_type = $rule['rule'];
if ($rule_type == 'PhabricatorUsersPolicyRule') {
$policy_config_user_names =
handleUsersPolicyRule($config, $rule);
} else if ($rule_type == 'PhabricatorProjectsPolicyRule') {
$policy_config_user_names =
handleProjectsPolicyRule($config, $rule);
} else if ($rule_type == 'PhabricatorProjectsAllPolicyRule') {
$policy_config_user_names =
handleProjectsAllPolicyRule($config, $rule);
} else if ($rule_type == 'PhabricatorAdministratorsPolicyRule') {
$policy_config_user_names =
handleAdministratorsPolicyRule($config, $rule);
} else if ($rule_type == 'PhabricatorLegalpadSignaturePolicyRule') {
$policy_config_user_names =
handleLegalpadSignaturePolicyRule($config, $rule);
}
$config_user_names = array_merge(
$config_user_names, $policy_config_user_names);
}
return $config_user_names;
}
// Parse repository and put it's members to the config file
function handleSingleRepository($config, $repository) {
$policies = PhabricatorPolicyQuery::loadPolicies(
$config->viewer,
$repository);
$pushable = $policies[DiffusionPushCapability::CAPABILITY];
$type = $pushable->getType();
$config_user_names = array();
if ($type == PhabricatorPolicyType::TYPE_PROJECT) {
$project = id(new PhabricatorProjectQuery())
->setViewer($config->viewer)
->needMembers(true)
->withPHIDs(array($pushable->getPHID()))
->executeOne();
$memberPHIDs = $project->getMemberPHIDs();
foreach ($memberPHIDs as $memberPHID) {
$config_user_names = array_merge($config_user_names,
handleSingleUserPHID($config, $memberPHID));
}
} else if ($type == PhabricatorPolicyType::TYPE_USER) {
$config_user_names = handleSingleUserPHID($config, $pushable->getPHID());
} else if ($type == PhabricatorPolicyType::TYPE_CUSTOM) {
$config_user_names = handleCustomPolicy($config, $pushable);
} else {
/* pass */
}
$config->setRepositoryUsers($repository, $config_user_names);
}
function rebuildConfiguration($gitolite_root) {
$config = new Configuration($gitolite_root);
// Fill in new configuration and keys
$used_keys = array();
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($config->viewer)
->execute();
foreach ($repositories as $repository_id => $repository) {
$type = $repository->getVersionControlSystem();
if ($type == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) {
handleSingleRepository($config, $repository);
}
}
$config->writeNewConfiguration();
$config->finalize();
return true;
}
////////////////////////////////////////////////////////////////////////////////
// Repository manipulation functionality.
function getGitCommand($repository) {
$git_dir = realpath("$repository/.git");
$git = "git --git-dir='$git_dir'";
$git .= ' --work-tree='.realpath($repository);
return $git;
}
function runGitCommand($repository, $arguments,
&$output=null, &$return_var=null) {
$git = getGitCommand($repository);
$git .= " $arguments";
exec($git, $output, $return_var);
return $return_var == 0;
}
function runGitSshCommand($repository, $key, $arguments,
&$output=null, &$return_var=null) {
$abs_key = realpath($key);
$git = "GIT_SSH_COMMAND=\"ssh -i $key -o IdentitiesOnly=yes\" ";
$git .= getGitCommand($repository);
$git .= " $arguments";
exec($git, $output, $return_var);
return $return_var == 0;
}
function repositoryPull($repository, $key) {
return runGitSshCommand($repository, $key, 'pull --rebase');
}
function repositoryCommitAll($repository, $author, $message) {
if (!runGitCommand(
$repository, 'ls-files --other --exclude-standard', $untracked_files)) {
return false;
}
if (count($untracked_files)) {
$flat_files = join(' ', $untracked_files);
if (!runGitCommand($repository, "add $flat_files")) {
return false;
}
}
runGitCommand($repository, "update-index -q --refresh", $output);
runGitCommand($repository, "diff-index --name-only HEAD --", $output);
if (count($output)) {
return runGitCommand(
$repository, "commit --author='$author' -a -m '$message'", $output);
}
return true;
}
if (count($argv) != 3) {
print("Usage: {$argv[0]} /path/to/gitolite-admin /path/to/id_rsa.pub\n");
exit(1);
}
$gitolite_root = $argv[1];
$key = $argv[2];
if (!repositoryPull($gitolite_root, $key)) {
print("Failed to pull changes from server.\n");
exit(1);
}
if (!rebuildConfiguration($gitolite_root)) {
exit(1);
}
if (!repositoryCommitAll(
$gitolite_root, 'Rebuild Gitadmin <null@git.blender.org>',
'Update to correspond changes in Phabricator')) {
print("Failed to commit changes.\n");
exit(1);
}
runGitSshCommand($gitolite_root, $key, 'push origin master');
?>

View File

@@ -8,10 +8,14 @@ function init_phabricator_script(array $options) {
ini_set( ini_set(
'include_path', 'include_path',
$include_path.PATH_SEPARATOR.dirname(__FILE__).'/../../../'); $include_path.PATH_SEPARATOR.dirname(__FILE__).'/../../../');
@include_once 'libphutil/scripts/__init_script__.php';
if (!@constant('__LIBPHUTIL__')) { $ok = @include_once 'arcanist/support/init/init-script.php';
echo "ERROR: Unable to load libphutil. Update your PHP 'include_path' to ". if (!$ok) {
"include the parent directory of libphutil/.\n"; echo
'FATAL ERROR: Unable to load the "Arcanist" library. '.
'Put "arcanist/" next to "phabricator/" on disk.';
echo "\n";
exit(1); exit(1);
} }

View File

@@ -22,7 +22,7 @@ failed() {
} }
ISSUE=`cat /etc/issue` ISSUE=`cat /etc/issue`
if [[ $ISSUE != Ubuntu* ]] if [[ ($ISSUE != Ubuntu*) && ($ISSUE != Debian*) ]];
then then
echo "This script is intended for use on Ubuntu, but this system appears"; echo "This script is intended for use on Ubuntu, but this system appears";
echo "to be something else. Your results may vary."; echo "to be something else. Your results may vary.";

View File

@@ -211,21 +211,29 @@ try {
->setUniqueMethod('getName') ->setUniqueMethod('getName')
->execute(); ->execute();
$command_list = array_keys($workflows);
$command_list = implode(', ', $command_list);
$error_lines = array();
$error_lines[] = pht('Welcome to Phabricator.');
$error_lines[] = pht(
'You are logged in as %s.',
$user_name);
if (!$original_argv) { if (!$original_argv) {
throw new Exception( $error_lines[] = pht(
pht( 'You have not specified a command to run. This means you are requesting '.
"Welcome to Phabricator.\n\n". 'an interactive shell, but Phabricator does not provide interactive '.
"You are logged in as %s.\n\n". 'shells over SSH.');
"You haven't specified a command to run. This means you're requesting ". $error_lines[] = pht(
"an interactive shell, but Phabricator does not provide an ". '(Usually, you should run a command like "git clone" or "hg push" '.
"interactive shell over SSH.\n\n". 'instead of connecting directly with SSH.)');
"Usually, you should run a command like `%s` or `%s` ". $error_lines[] = pht(
"rather than connecting directly with SSH.\n\n". 'Supported commands are: %s.',
"Supported commands are: %s.", $command_list);
$user_name,
'git clone', $error_lines = implode("\n\n", $error_lines);
'hg push', throw new PhutilArgumentUsageException($error_lines);
implode(', ', array_keys($workflows))));
} }
$log_argv = implode(' ', $original_argv); $log_argv = implode(' ', $original_argv);
@@ -247,7 +255,20 @@ try {
$parsed_args = new PhutilArgumentParser($parseable_argv); $parsed_args = new PhutilArgumentParser($parseable_argv);
if (empty($workflows[$command])) { if (empty($workflows[$command])) {
throw new Exception(pht('Invalid command.')); $error_lines[] = pht(
'You have specified the command "%s", but that command is not '.
'supported by Phabricator. As received by Phabricator, your entire '.
'argument list was:',
$command);
$error_lines[] = csprintf(' $ ssh ... -- %Ls', $parseable_argv);
$error_lines[] = pht(
'Supported commands are: %s.',
$command_list);
$error_lines = implode("\n\n", $error_lines);
throw new PhutilArgumentUsageException($error_lines);
} }
$workflow = $parsed_args->parseWorkflows($workflows); $workflow = $parsed_args->parseWorkflows($workflows);

View File

@@ -0,0 +1,61 @@
<?php
$ARCHIVED_REPOS = array(
'abstractmesh',
'beast',
# 'bf-blender',
'bf-docboard-es',
'bf-extensions',
'bf-funboard',
'bf-scripts',
# 'bf-translations',
'bfct',
'bforge',
'blend-doc',
'blend2cs',
'blendedmidi',
'blendercad',
'blendxml',
'bzoo',
'docboard',
'drqueue',
'ghost',
'girona',
'guiman',
'lsystem',
'magic',
'makeh',
'mechanicblender',
'neverblender',
'news',
'night',
'nitrox',
'osgexport',
'peerrating',
'piovra',
'pyverse',
'qdune',
'scolblender',
'skined',
'smdio',
'soapyblender',
'soc-2005',
'soc-2006',
'soc-2007',
'soc-2008',
'sourceforge',
'spe',
'stats',
'ter2blend',
'torqueexporter',
'tuhopuu',
'tzuray',
'vectex',
'vectorrender',
'verse',
'vrmlimportexp',
'warblender',
'wpyre',
'yafray',
'yofrankie'
);
?>

View File

@@ -0,0 +1,292 @@
#!/usr/local/bin/php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
function getSVNRepositoryName($repository) {
$uri = $repository->getRemoteURI();
return preg_replace(
'/https?\:\/\/.*?\/svnroot\/([^\/]+)\/?.*/', '$1', $uri);
}
// Get user's heys and put them to the configuration
function handleSingleUserPHID(
$viewer, $userPHID, $repository, &$namemap, &$access) {
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($userPHID))
->executeOne();
if (!$user) {
return;
}
if ($user->getIsDisabled()) {
return;
}
$user_name = $user->getUserName();
if (!array_key_exists($user_name, $namemap)) {
$namemap[$user_name] = array('email' => $user->loadPrimaryEmailAddress(),
'name' => $user->getRealName());
}
$repository_name = getSVNRepositoryName($repository);
$repository_rootpath = $repository_name . ':/';
if (!array_key_exists($repository_rootpath, $access)) {
$access[$repository_rootpath]['RW'] = array();
$access[$repository_rootpath]['RO'] = array();
}
$access[$repository_rootpath]['RO'][] = $user_name;
// Store write access settings to current subath
$subpath = $repository->getDetail('svn-subpath');
$subpath = rtrim($subpath, '/');
$repository_pathname = "$repository_name:/$subpath";
if (!array_key_exists($repository_pathname, $access)) {
$access[$repository_pathname]['RW'] = array();
$access[$repository_pathname]['RO'] = array();
}
$access[$repository_pathname]['RW'][] = $user_name;
// Write access to the tags
$tags_pathname = "$repository_name:/tags";
if (!array_key_exists($tags_pathname, $access)) {
$access[$tags_pathname]['RW'] = array();
$access[$tags_pathname]['RO'] = array();
}
$access[$tags_pathname]['RW'][] = $user_name;
// Write access to the branches.
$branches_pathname = "$repository_name:/branches";
if (!array_key_exists($branches_pathname, $access)) {
$access[$branches_pathname]['RW'] = array();
$access[$branches_pathname]['RO'] = array();
}
$access[$branches_pathname]['RW'][] = $user_name;
}
function getProjectMembersPHIDs($viewer, $project_phid) {
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->needMembers(true)
->withPHIDs(array($project_phid))
->executeOne();
return $project->getMemberPHIDs();
}
function handleProjectPHID(
$viewer, $project_phid, $repository, &$namemap, &$access) {
$memberPHIDs = getProjectMembersPHIDs($viewer, $project_phid);
foreach ($memberPHIDs as $memberPHID) {
handleSingleUserPHID(
$viewer, $memberPHID, $repository, $namemap, $access);
}
}
function handleUsersPolicyRule(
$viewer, $rule, $repository, &$namemap, &$access) {
foreach ($rule['value'] as $user_phid) {
handleSingleUserPHID(
$viewer, $user_phid, $repository, $namemap, $access);
}
}
function handleProjectsPolicyRule(
$viewer, $rule, $repository, &$namemap, &$access) {
foreach ($rule['value'] as $project_phid) {
handleProjectPHID(
$viewer, $project_phid, $repository, $namemap, $access);
}
}
function handleProjectsAllPolicyRule(
$viewer, $rule, $repository, &$namemap, &$access) {
$is_first_project = true;
$allowed_members_phids = array();
foreach ($rule['value'] as $project_phid) {
$memberPHIDs = getProjectMembersPHIDs($viewer, $project_phid);
if ($is_first_project) {
$allowed_members_phids = $memberPHIDs;
$is_first_project = false;
} else {
$allowed_members_phids = array_intersect(
$allowed_members_phids, $memberPHIDs);
}
}
foreach ($allowed_members_phids as $user_phid) {
handleSingleUserPHID(
$viewer, $user_phid, $repository, $namemap, $access);
}
}
function handleAdministratorsPolicyRule(
$viewer, $rule, $repository, &$namemap, &$access) {
$administrators = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIsAdmin(true)
->execute();
foreach ($administrators as $administrator) {
handleSingleUserPHID(
$viewer, $administrator->getPHID(), $repository, $namemap, $access);
}
}
function handleLegalpadSingleDocument(
$viewer, $document, $repository, &$namemap, &$access) {
if ($document->getSignatureType() !=
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
return;
}
foreach ($document->getSignatures() as $signature) {
if ($signature->getSignatureType() !=
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
continue;
}
handleSingleUserPHID(
$viewer, $signature->getSignerPHID(), $repository, $namemap, $access);
}
}
function handleLegalpadSignaturePolicyRule(
$viewer, $rule, $repository, &$namemap, &$access) {
$documents = id(new LegalpadDocumentQuery())
->setViewer($viewer)
->withPHIDs($rule['value'])
->needSignatures(true)
->execute();
foreach ($documents as $document) {
handleLegalpadSingleDocument(
$viewer, $document, $repository, $namemap, $access);
}
}
function handleCustomPolicyRule(
$viewer, $rule, $repository, &$namemap, &$access) {
if ($rule['action'] != PhabricatorPolicy::ACTION_ALLOW) {
// By default the script decides to DENY unless explicitly allowed.
return;
}
$rule_type = $rule['rule'];
if ($rule_type == 'PhabricatorUsersPolicyRule') {
handleUsersPolicyRule(
$viewer, $rule, $repository, $namemap, $access);
} else if ($rule_type == 'PhabricatorProjectsPolicyRule') {
handleProjectsPolicyRule(
$viewer, $rule, $repository, $namemap, $access);
} else if ($rule_type == 'PhabricatorProjectsAllPolicyRule') {
handleProjectsAllPolicyRule(
$viewer, $rule, $repository, $namemap, $access);
} else if ($rule_type == 'PhabricatorAdministratorsPolicyRule') {
handleAdministratorsPolicyRule(
$viewer, $rule, $repository, $namemap, $access);
} else if ($rule_type == 'PhabricatorLegalpadSignaturePolicyRule') {
handleLegalpadSignaturePolicyRule(
$viewer, $rule, $repository, $namemap, $access);
}
}
function handleCustomPolicy(
$viewer, $policy, $repository, &$namemap, &$access) {
foreach ($policy->getRules() as $rule) {
handleCustomPolicyRule($viewer, $rule, $repository, $namemap, $access);
}
}
// Parse repository and put it's members to the config file
function handleSingleRepository(
$viewer, $repository, &$namemap, &$access) {
$policies = PhabricatorPolicyQuery::loadPolicies(
$viewer,
$repository);
$pushable = $policies[DiffusionPushCapability::CAPABILITY];
$type = phid_get_type($pushable->getPHID());
// Make sure repository is always available for read-only access
$repository_rootpath = getSVNRepositoryName($repository) . ':/';
if (!array_key_exists($repository_rootpath, $access)) {
$access[$repository_rootpath]['RW'] = array();
$access[$repository_rootpath]['RO'] = array();
}
if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) {
handleProjectPHID(
$viewer, $pushable->getPHID(), $repository, $namemap, $access);
} else if ($type == PhabricatorPolicyType::TYPE_USER) {
handleSingleUserPHID(
$viewer, $pushable->getPHID(), $repository, $namemap, $access);
} else if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
handleCustomPolicy(
$viewer, $pushable, $repository, $namemap, $access);
/* pass */
} else {
/* pass */
}
}
function rebuildConfiguration($what) {
$viewer = id(new PhabricatorUser())
->loadOneWhere('username = %s', 'sergey');
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->execute();
$namemap = array();
$access = array();
require_once 'archived_repos.php';
foreach ($ARCHIVED_REPOS as $repository) {
$repository_pathname = "$repository:/";
$access[$repository_pathname]['RW'] = array();
$access[$repository_pathname]['RO'] = array();
}
foreach ($repositories as $repository_id => $repository) {
$type = $repository->getVersionControlSystem();
if ($type == PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) {
handleSingleRepository(
$viewer, $repository, $namemap, $access);
}
}
if ($what == 'ACCESS') {
foreach ($access as $repository => $users) {
print("[$repository]\n");
$rw_users = array();
foreach ($users['RW'] as $user) {
print("$user = rw\n");
$rw_users[$user] = true;
}
foreach ($users['RO'] as $user) {
if (!array_key_exists($user, $rw_users)) {
print("$user = r\n");
}
}
print("anonsvn = r\n");
print("* = r\n\n");
}
} else if ($what == 'NAMEMAP') {
foreach ($namemap as $user => $data) {
print("$user\t${data['email']}\t${data['name']}\n");
}
}
return true;
}
if (count($argv) != 2 ||
($argv[1] != 'ACCESS' && $argv[1] != 'NAMEMAP')) {
print("Usage: {$argv[0]} ACCESS|NAMEMAP\n");
exit(1);
}
rebuildConfiguration($argv[1]);
?>

File diff suppressed because it is too large Load Diff

View File

@@ -30,6 +30,7 @@ final class AphrontRequest extends Phobject {
private $controller; private $controller;
private $uriData = array(); private $uriData = array();
private $cookiePrefix; private $cookiePrefix;
private $submitKey;
public function __construct($host, $path) { public function __construct($host, $path) {
$this->host = $host; $this->host = $host;
@@ -914,5 +915,19 @@ final class AphrontRequest extends Phobject {
return $future; return $future;
} }
public function updateEphemeralCookies() {
$submit_cookie = PhabricatorCookies::COOKIE_SUBMIT;
$submit_key = $this->getCookie($submit_cookie);
if (strlen($submit_key)) {
$this->clearCookie($submit_cookie);
$this->submitKey = $submit_key;
}
}
public function getSubmitKey() {
return $this->submitKey;
}
} }

View File

@@ -0,0 +1,85 @@
<?php
final class AphrontRoutingMapTestCase
extends PhabricatorTestCase {
public function testRoutingMaps() {
$count = 0;
$sites = AphrontSite::getAllSites();
foreach ($sites as $site) {
$maps = $site->getRoutingMaps();
foreach ($maps as $map) {
foreach ($map->getRoutes() as $rule => $value) {
$this->assertRoutable($site, $map, array(), $rule, $value);
$count++;
}
}
}
if (!$count) {
$this->assertSkipped(
pht('No sites define any routing rules.'));
}
}
private function assertRoutable(
AphrontSite $site,
AphrontRoutingMap $map,
array $path,
$rule,
$value) {
$path[] = $rule;
$site_description = $site->getDescription();
$rule_path = implode(' > ', $path);
$pattern = implode('', $path);
$pattern = '('.$pattern.')';
$ok = @preg_match($pattern, '');
$this->assertTrue(
($ok !== false),
pht(
'Routing rule ("%s", for site "%s") does not compile into a '.
'valid regular expression.',
$rule_path,
$site_description));
if (is_array($value)) {
$this->assertTrue(
(count($value) > 0),
pht(
'Routing rule ("%s", for site "%s") does not have any targets.',
$rule_path,
$site_description));
foreach ($value as $sub_rule => $sub_value) {
$this->assertRoutable($site, $map, $path, $sub_rule, $sub_value);
}
return;
}
if (is_string($value)) {
$this->assertTrue(
class_exists($value),
pht(
'Routing rule ("%s", for site "%s") points at controller ("%s") '.
'which does not exist.',
$rule_path,
$site_description,
$value));
return;
}
$this->assertFailure(
pht(
'Routing rule ("%s", for site "%s") points at unknown value '.
'(of type "%s"), expected a controller class name string.',
$rule_path,
$site_description,
phutil_describe_type($value)));
}
}

View File

@@ -27,6 +27,8 @@ final class AphrontApplicationConfiguration
$request->setApplicationConfiguration($this); $request->setApplicationConfiguration($this);
$request->setCookiePrefix($cookie_prefix); $request->setCookiePrefix($cookie_prefix);
$request->updateEphemeralCookies();
return $request; return $request;
} }
@@ -771,12 +773,21 @@ final class AphrontApplicationConfiguration
); );
} }
$raw_input = @file_get_contents('php://input');
if ($raw_input !== false) {
$base64_input = base64_encode($raw_input);
} else {
$base64_input = null;
}
$result = array( $result = array(
'path' => $path, 'path' => $path,
'params' => $params, 'params' => $params,
'user' => idx($_SERVER, 'PHP_AUTH_USER'), 'user' => idx($_SERVER, 'PHP_AUTH_USER'),
'pass' => idx($_SERVER, 'PHP_AUTH_PW'), 'pass' => idx($_SERVER, 'PHP_AUTH_PW'),
'raw.base64' => $base64_input,
// This just makes sure that the response compresses well, so reasonable // This just makes sure that the response compresses well, so reasonable
// algorithms should want to gzip or deflate it. // algorithms should want to gzip or deflate it.
'filler' => str_repeat('Q', 1024 * 16), 'filler' => str_repeat('Q', 1024 * 16),
@@ -801,27 +812,22 @@ final class AphrontApplicationConfiguration
// if we can. Among other things, this corrects variable names with // if we can. Among other things, this corrects variable names with
// the "." character in them, which PHP normally converts into "_". // the "." character in them, which PHP normally converts into "_".
// There are two major considerations here: whether the // If "enable_post_data_reading" is on, the documentation suggests we
// `enable_post_data_reading` option is set, and whether the content // can not read the body. In practice, we seem to be able to. This may
// type is "multipart/form-data" or not. // need to be resolved at some point, likely by instructing installs
// to disable this option.
// If `enable_post_data_reading` is off, we're free to read the entire
// raw request body and parse it -- and we must, because $_POST and
// $_FILES are not built for us. If `enable_post_data_reading` is on,
// which is the default, we may not be able to read the body (the
// documentation says we can't, but empirically we can at least some
// of the time).
// If the content type is "multipart/form-data", we need to build both // If the content type is "multipart/form-data", we need to build both
// $_POST and $_FILES, which is involved. The body itself is also more // $_POST and $_FILES, which is involved. The body itself is also more
// difficult to parse than other requests. // difficult to parse than other requests.
$raw_input = PhabricatorStartup::getRawInput(); $raw_input = PhabricatorStartup::getRawInput();
$parser = new PhutilQueryStringParser(); $parser = new PhutilQueryStringParser();
if (strlen($raw_input)) { if (strlen($raw_input)) {
$content_type = idx($_SERVER, 'CONTENT_TYPE'); $content_type = idx($_SERVER, 'CONTENT_TYPE');
$is_multipart = preg_match('@^multipart/form-data@i', $content_type); $is_multipart = preg_match('@^multipart/form-data@i', $content_type);
if ($is_multipart && !ini_get('enable_post_data_reading')) { if ($is_multipart) {
$multipart_parser = id(new AphrontMultipartParser()) $multipart_parser = id(new AphrontMultipartParser())
->setContentType($content_type); ->setContentType($content_type);

View File

@@ -0,0 +1,150 @@
<?php
final class AphrontHTTPHeaderParser extends Phobject {
private $name;
private $content;
private $pairs;
public function parseRawHeader($raw_header) {
$this->name = null;
$this->content = null;
$parts = explode(':', $raw_header, 2);
$this->name = trim($parts[0]);
if (count($parts) > 1) {
$this->content = trim($parts[1]);
}
$this->pairs = null;
return $this;
}
public function getHeaderName() {
$this->requireParse();
return $this->name;
}
public function getHeaderContent() {
$this->requireParse();
return $this->content;
}
public function getHeaderContentAsPairs() {
$content = $this->getHeaderContent();
$state = 'prekey';
$length = strlen($content);
$pair_name = null;
$pair_value = null;
$pairs = array();
$ii = 0;
while ($ii < $length) {
$c = $content[$ii];
switch ($state) {
case 'prekey';
// We're eating space in front of a key.
if ($c == ' ') {
$ii++;
break;
}
$pair_name = '';
$state = 'key';
break;
case 'key';
// We're parsing a key name until we find "=" or ";".
if ($c == ';') {
$state = 'done';
break;
}
if ($c == '=') {
$ii++;
$state = 'value';
break;
}
$ii++;
$pair_name .= $c;
break;
case 'value':
// We found an "=", so now figure out if the value is quoted
// or not.
if ($c == '"') {
$ii++;
$state = 'quoted';
break;
}
$state = 'unquoted';
break;
case 'quoted':
// We're in a quoted string, parse until we find the closing quote.
if ($c == '"') {
$ii++;
$state = 'done';
break;
}
$ii++;
$pair_value .= $c;
break;
case 'unquoted':
// We're in an unquoted string, parse until we find a space or a
// semicolon.
if ($c == ' ' || $c == ';') {
$state = 'done';
break;
}
$ii++;
$pair_value .= $c;
break;
case 'done':
// We parsed something, so eat any trailing whitespace and semicolons
// and look for a new value.
if ($c == ' ' || $c == ';') {
$ii++;
break;
}
$pairs[] = array(
$pair_name,
$pair_value,
);
$pair_name = null;
$pair_value = null;
$state = 'prekey';
break;
}
}
if ($state == 'quoted') {
throw new Exception(
pht(
'Header has unterminated double quote for key "%s".',
$pair_name));
}
if ($pair_name !== null) {
$pairs[] = array(
$pair_name,
$pair_value,
);
}
return $pairs;
}
private function requireParse() {
if ($this->name === null) {
throw new PhutilInvalidStateException('parseRawHeader');
}
}
}

View File

@@ -0,0 +1,108 @@
<?php
final class AphrontHTTPHeaderParserTestCase extends PhutilTestCase {
public function testHeaderParser() {
$cases = array(
array(
'Key: x; y; z',
'Key',
'x; y; z',
array(
array('x', null),
array('y', null),
array('z', null),
),
),
array(
'Content-Disposition: form-data; name="label"',
'Content-Disposition',
'form-data; name="label"',
array(
array('form-data', null),
array('name', 'label'),
),
),
array(
'Content-Type: multipart/form-data; charset=utf-8',
'Content-Type',
'multipart/form-data; charset=utf-8',
array(
array('multipart/form-data', null),
array('charset', 'utf-8'),
),
),
array(
'Content-Type: application/octet-stream; charset="ut',
'Content-Type',
'application/octet-stream; charset="ut',
false,
),
array(
'Content-Type: multipart/form-data; boundary=ABCDEFG',
'Content-Type',
'multipart/form-data; boundary=ABCDEFG',
array(
array('multipart/form-data', null),
array('boundary', 'ABCDEFG'),
),
),
array(
'Content-Type: multipart/form-data; boundary="ABCDEFG"',
'Content-Type',
'multipart/form-data; boundary="ABCDEFG"',
array(
array('multipart/form-data', null),
array('boundary', 'ABCDEFG'),
),
),
);
foreach ($cases as $case) {
$input = $case[0];
$expect_name = $case[1];
$expect_content = $case[2];
$parser = id(new AphrontHTTPHeaderParser())
->parseRawHeader($input);
$actual_name = $parser->getHeaderName();
$actual_content = $parser->getHeaderContent();
$this->assertEqual(
$expect_name,
$actual_name,
pht('Header name for: %s', $input));
$this->assertEqual(
$expect_content,
$actual_content,
pht('Header content for: %s', $input));
if (isset($case[3])) {
$expect_pairs = $case[3];
$caught = null;
try {
$actual_pairs = $parser->getHeaderContentAsPairs();
} catch (Exception $ex) {
$caught = $ex;
}
if ($expect_pairs === false) {
$this->assertEqual(
true,
($caught instanceof Exception),
pht('Expect exception for header pairs of: %s', $input));
} else {
$this->assertEqual(
$expect_pairs,
$actual_pairs,
pht('Header pairs for: %s', $input));
}
}
}
}
}

View File

@@ -0,0 +1,249 @@
<?php
final class AphrontMultipartParser extends Phobject {
private $contentType;
private $boundary;
private $buffer;
private $body;
private $state;
private $part;
private $parts;
public function setContentType($content_type) {
$this->contentType = $content_type;
return $this;
}
public function getContentType() {
return $this->contentType;
}
public function beginParse() {
$content_type = $this->getContentType();
if ($content_type === null) {
throw new PhutilInvalidStateException('setContentType');
}
if (!preg_match('(^multipart/form-data)', $content_type)) {
throw new Exception(
pht(
'Expected "multipart/form-data" content type when executing a '.
'multipart body read.'));
}
$type_parts = preg_split('(\s*;\s*)', $content_type);
$boundary = null;
foreach ($type_parts as $type_part) {
$matches = null;
if (preg_match('(^boundary=(.*))', $type_part, $matches)) {
$boundary = $matches[1];
break;
}
}
if ($boundary === null) {
throw new Exception(
pht('Received "multipart/form-data" request with no "boundary".'));
}
$this->parts = array();
$this->part = null;
$this->buffer = '';
$this->boundary = $boundary;
// We're looking for a (usually empty) body before the first boundary.
$this->state = 'bodynewline';
}
public function continueParse($bytes) {
$this->buffer .= $bytes;
$continue = true;
while ($continue) {
switch ($this->state) {
case 'endboundary':
// We've just parsed a boundary. Next, we expect either "--" (which
// indicates we've reached the end of the parts) or "\r\n" (which
// indicates we should read the headers for the next part).
if (strlen($this->buffer) < 2) {
// We don't have enough bytes yet, so wait for more.
$continue = false;
break;
}
if (!strncmp($this->buffer, '--', 2)) {
// This is "--" after a boundary, so we're done. We'll read the
// rest of the body (the "epilogue") and discard it.
$this->buffer = substr($this->buffer, 2);
$this->state = 'epilogue';
$this->part = null;
break;
}
if (!strncmp($this->buffer, "\r\n", 2)) {
// This is "\r\n" after a boundary, so we're going to going to
// read the headers for a part.
$this->buffer = substr($this->buffer, 2);
$this->state = 'header';
// Create the object to hold the part we're about to read.
$part = new AphrontMultipartPart();
$this->parts[] = $part;
$this->part = $part;
break;
}
throw new Exception(
pht('Expected "\r\n" or "--" after multipart data boundary.'));
case 'header':
// We've just parsed a boundary, followed by "\r\n". We are going
// to read the headers for this part. They are in the form of HTTP
// headers and terminated by "\r\n". The section is terminated by
// a line with no header on it.
if (strlen($this->buffer) < 2) {
// We don't have enough data to find a "\r\n", so wait for more.
$continue = false;
break;
}
if (!strncmp("\r\n", $this->buffer, 2)) {
// This line immediately began "\r\n", so we're done with parsing
// headers. Start parsing the body.
$this->buffer = substr($this->buffer, 2);
$this->state = 'body';
break;
}
// This is an actual header, so look for the end of it.
$header_len = strpos($this->buffer, "\r\n");
if ($header_len === false) {
// We don't have a full header yet, so wait for more data.
$continue = false;
break;
}
$header_buf = substr($this->buffer, 0, $header_len);
$this->part->appendRawHeader($header_buf);
$this->buffer = substr($this->buffer, $header_len + 2);
break;
case 'body':
// We've parsed a boundary and headers, and are parsing the data for
// this part. The data is terminated by "\r\n--", then the boundary.
// We'll look for "\r\n", then switch to the "bodynewline" state if
// we find it.
$marker = "\r";
$marker_pos = strpos($this->buffer, $marker);
if ($marker_pos === false) {
// There's no "\r" anywhere in the buffer, so we can just read it
// as provided. Then, since we read all the data, we're done until
// we get more.
// Note that if we're in the preamble, we won't have a "part"
// object and will just discard the data.
if ($this->part) {
$this->part->appendData($this->buffer);
}
$this->buffer = '';
$continue = false;
break;
}
if ($marker_pos > 0) {
// If there are bytes before the "\r",
if ($this->part) {
$this->part->appendData(substr($this->buffer, 0, $marker_pos));
}
$this->buffer = substr($this->buffer, $marker_pos);
}
$expect = "\r\n";
$expect_len = strlen($expect);
if (strlen($this->buffer) < $expect_len) {
// We don't have enough bytes yet to know if this is "\r\n"
// or not.
$continue = false;
break;
}
if (strncmp($this->buffer, $expect, $expect_len)) {
// The next two bytes aren't "\r\n", so eat them and go looking
// for more newlines.
if ($this->part) {
$this->part->appendData(substr($this->buffer, 0, $expect_len));
}
$this->buffer = substr($this->buffer, $expect_len);
break;
}
// Eat the "\r\n".
$this->buffer = substr($this->buffer, $expect_len);
$this->state = 'bodynewline';
break;
case 'bodynewline':
// We've parsed a newline in a body, or we just started parsing the
// request. In either case, we're looking for "--", then the boundary.
// If we find it, this section is done. If we don't, we consume the
// bytes and move on.
$expect = '--'.$this->boundary;
$expect_len = strlen($expect);
if (strlen($this->buffer) < $expect_len) {
// We don't have enough bytes yet, so wait for more.
$continue = false;
break;
}
if (strncmp($this->buffer, $expect, $expect_len)) {
// This wasn't the boundary, so return to the "body" state and
// consume it. (But first, we need to append the "\r\n" which we
// ate earlier.)
if ($this->part) {
$this->part->appendData("\r\n");
}
$this->state = 'body';
break;
}
// This is the boundary, so toss it and move on.
$this->buffer = substr($this->buffer, $expect_len);
$this->state = 'endboundary';
break;
case 'epilogue':
// We just discard any epilogue.
$this->buffer = '';
$continue = false;
break;
default:
throw new Exception(
pht(
'Unknown parser state "%s".\n',
$this->state));
}
}
}
public function endParse() {
if ($this->state !== 'epilogue') {
throw new Exception(
pht(
'Expected "multipart/form-data" parse to end '.
'in state "epilogue".'));
}
return $this->parts;
}
}

View File

@@ -0,0 +1,96 @@
<?php
final class AphrontMultipartPart extends Phobject {
private $headers = array();
private $value = '';
private $name;
private $filename;
private $tempFile;
private $byteSize = 0;
public function appendRawHeader($bytes) {
$parser = id(new AphrontHTTPHeaderParser())
->parseRawHeader($bytes);
$header_name = $parser->getHeaderName();
$this->headers[] = array(
$header_name,
$parser->getHeaderContent(),
);
if (strtolower($header_name) === 'content-disposition') {
$pairs = $parser->getHeaderContentAsPairs();
foreach ($pairs as $pair) {
list($key, $value) = $pair;
switch ($key) {
case 'filename':
$this->filename = $value;
break;
case 'name':
$this->name = $value;
break;
}
}
}
return $this;
}
public function appendData($bytes) {
$this->byteSize += strlen($bytes);
if ($this->isVariable()) {
$this->value .= $bytes;
} else {
if (!$this->tempFile) {
$this->tempFile = new TempFile(getmypid().'.upload');
}
Filesystem::appendFile($this->tempFile, $bytes);
}
return $this;
}
public function isVariable() {
return ($this->filename === null);
}
public function getName() {
return $this->name;
}
public function getVariableValue() {
if (!$this->isVariable()) {
throw new Exception(pht('This part is not a variable!'));
}
return $this->value;
}
public function getPHPFileDictionary() {
if (!$this->tempFile) {
$this->appendData('');
}
$mime_type = 'application/octet-stream';
foreach ($this->headers as $header) {
list($name, $value) = $header;
if (strtolower($name) == 'content-type') {
$mime_type = $value;
break;
}
}
return array(
'name' => $this->filename,
'type' => $mime_type,
'tmp_name' => (string)$this->tempFile,
'error' => 0,
'size' => $this->byteSize,
);
}
}

View File

@@ -0,0 +1,45 @@
<?php
final class AphrontMultipartParserTestCase extends PhutilTestCase {
public function testParser() {
$map = array(
array(
'data' => 'simple.txt',
'variables' => array(
array('a', 'b'),
),
),
);
$data_dir = dirname(__FILE__).'/data/';
foreach ($map as $test_case) {
$data = Filesystem::readFile($data_dir.$test_case['data']);
$data = str_replace("\n", "\r\n", $data);
$parser = id(new AphrontMultipartParser())
->setContentType('multipart/form-data; boundary=ABCDEFG');
$parser->beginParse();
$parser->continueParse($data);
$parts = $parser->endParse();
$variables = array();
foreach ($parts as $part) {
if (!$part->isVariable()) {
continue;
}
$variables[] = array(
$part->getName(),
$part->getVariableValue(),
);
}
$expect_variables = idx($test_case, 'variables', array());
$this->assertEqual($expect_variables, $variables);
}
}
}

View File

@@ -0,0 +1,5 @@
--ABCDEFG
Content-Disposition: form-data; name="a"
b
--ABCDEFG--

View File

@@ -0,0 +1,113 @@
<?php
final class AphrontRequestStream extends Phobject {
private $encoding;
private $stream;
private $closed;
private $iterator;
public function setEncoding($encoding) {
$this->encoding = $encoding;
return $this;
}
public function getEncoding() {
return $this->encoding;
}
public function getIterator() {
if (!$this->iterator) {
$this->iterator = new PhutilStreamIterator($this->getStream());
}
return $this->iterator;
}
public function readData() {
if (!$this->iterator) {
$iterator = $this->getIterator();
$iterator->rewind();
} else {
$iterator = $this->getIterator();
}
if (!$iterator->valid()) {
return null;
}
$data = $iterator->current();
$iterator->next();
return $data;
}
private function getStream() {
if (!$this->stream) {
$this->stream = $this->newStream();
}
return $this->stream;
}
private function newStream() {
$stream = fopen('php://input', 'rb');
if (!$stream) {
throw new Exception(
pht(
'Failed to open stream "%s" for reading.',
'php://input'));
}
$encoding = $this->getEncoding();
if ($encoding === 'gzip') {
// This parameter is magic. Values 0-15 express a time/memory tradeoff,
// but the largest value (15) corresponds to only 32KB of memory and
// data encoded with a smaller window size than the one we pass can not
// be decompressed. Always pass the maximum window size.
// Additionally, you can add 16 (to enable gzip) or 32 (to enable both
// gzip and zlib). Add 32 to support both.
$zlib_window = 15 + 32;
$ok = stream_filter_append(
$stream,
'zlib.inflate',
STREAM_FILTER_READ,
array(
'window' => $zlib_window,
));
if (!$ok) {
throw new Exception(
pht(
'Failed to append filter "%s" to input stream while processing '.
'a request with "%s" encoding.',
'zlib.inflate',
$encoding));
}
}
return $stream;
}
public static function supportsGzip() {
if (!function_exists('gzencode') || !function_exists('gzdecode')) {
return false;
}
$has_zlib = false;
// NOTE: At least locally, this returns "zlib.*", which is not terribly
// reassuring. We care about "zlib.inflate".
$filters = stream_get_filters();
foreach ($filters as $filter) {
if (!strncasecmp($filter, 'zlib.', strlen('zlib.'))) {
$has_zlib = true;
break;
}
}
return $has_zlib;
}
}

View File

@@ -61,11 +61,17 @@ final class AphrontHTTPProxyResponse extends AphrontResponse {
// Strip "Transfer-Encoding" headers. Particularly, the server we proxied // Strip "Transfer-Encoding" headers. Particularly, the server we proxied
// may have chunked the response, but cURL will already have un-chunked it. // may have chunked the response, but cURL will already have un-chunked it.
// If we emit the header and unchunked data, the response becomes invalid. // If we emit the header and unchunked data, the response becomes invalid.
// See T13517. Strip "Content-Encoding" and "Content-Length" headers, since
// they may reflect compressed content.
foreach ($headers as $key => $header) { foreach ($headers as $key => $header) {
list($header_head, $header_body) = $header; list($header_head, $header_body) = $header;
$header_head = phutil_utf8_strtolower($header_head); $header_head = phutil_utf8_strtolower($header_head);
switch ($header_head) { switch ($header_head) {
case 'transfer-encoding': case 'transfer-encoding':
case 'content-encoding':
case 'content-length':
unset($headers[$key]); unset($headers[$key]);
break; break;
} }

View File

@@ -31,10 +31,10 @@ final class AphrontJSONResponse extends AphrontResponse {
} }
public function getHeaders() { public function getHeaders() {
$headers = array( $headers = parent::getHeaders();
array('Content-Type', 'application/json'),
); $headers[] = array('Content-Type', 'application/json');
$headers = array_merge(parent::getHeaders(), $headers);
return $headers; return $headers;
} }

View File

@@ -10,7 +10,7 @@ abstract class AphrontResponse extends Phobject {
private $contentSecurityPolicyURIs; private $contentSecurityPolicyURIs;
private $disableContentSecurityPolicy; private $disableContentSecurityPolicy;
protected $frameable; protected $frameable;
private $headers = array();
public function setRequest($request) { public function setRequest($request) {
$this->request = $request; $this->request = $request;
@@ -49,6 +49,11 @@ abstract class AphrontResponse extends Phobject {
return $this; return $this;
} }
final public function addHeader($key, $value) {
$this->headers[] = array($key, $value);
return $this;
}
/* -( Content )------------------------------------------------------------ */ /* -( Content )------------------------------------------------------------ */
@@ -105,6 +110,10 @@ abstract class AphrontResponse extends Phobject {
$headers[] = array('Referrer-Policy', 'no-referrer'); $headers[] = array('Referrer-Policy', 'no-referrer');
foreach ($this->headers as $header) {
$headers[] = $header;
}
return $headers; return $headers;
} }
@@ -417,13 +426,19 @@ abstract class AphrontResponse extends Phobject {
} }
public function willBeginWrite() { public function willBeginWrite() {
if ($this->shouldCompressResponse()) { // If we've already sent headers, these "ini_set()" calls will warn that
// Enable automatic compression here. Webservers sometimes do this for // they have no effect. Today, this always happens because we're inside
// us, but we now detect the absence of compression and warn users about // a unit test, so just skip adjusting the setting.
// it so try to cover our bases more thoroughly.
ini_set('zlib.output_compression', 1); if (!headers_sent()) {
} else { if ($this->shouldCompressResponse()) {
ini_set('zlib.output_compression', 0); // Enable automatic compression here. Webservers sometimes do this for
// us, but we now detect the absence of compression and warn users about
// it so try to cover our bases more thoroughly.
ini_set('zlib.output_compression', 1);
} else {
ini_set('zlib.output_compression', 0);
}
} }
} }

View File

@@ -0,0 +1,76 @@
<?php
/**
* NOTE: This is very new and unstable.
*/
final class PhutilSprite extends Phobject {
private $sourceFiles = array();
private $sourceX;
private $sourceY;
private $sourceW;
private $sourceH;
private $targetCSS;
private $spriteSheet;
private $name;
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setTargetCSS($target_css) {
$this->targetCSS = $target_css;
return $this;
}
public function getTargetCSS() {
return $this->targetCSS;
}
public function setSourcePosition($x, $y) {
$this->sourceX = $x;
$this->sourceY = $y;
return $this;
}
public function setSourceSize($w, $h) {
$this->sourceW = $w;
$this->sourceH = $h;
return $this;
}
public function getSourceH() {
return $this->sourceH;
}
public function getSourceW() {
return $this->sourceW;
}
public function getSourceY() {
return $this->sourceY;
}
public function getSourceX() {
return $this->sourceX;
}
public function setSourceFile($source_file, $scale = 1) {
$this->sourceFiles[$scale] = $source_file;
return $this;
}
public function getSourceFile($scale) {
if (empty($this->sourceFiles[$scale])) {
throw new Exception(pht("No source file for scale '%s'!", $scale));
}
return $this->sourceFiles[$scale];
}
}

View File

@@ -0,0 +1,385 @@
<?php
/**
* NOTE: This is very new and unstable.
*/
final class PhutilSpriteSheet extends Phobject {
const MANIFEST_VERSION = 1;
const TYPE_STANDARD = 'standard';
const TYPE_REPEAT_X = 'repeat-x';
const TYPE_REPEAT_Y = 'repeat-y';
private $sprites = array();
private $sources = array();
private $hashes = array();
private $cssHeader;
private $generated;
private $scales = array(1);
private $type = self::TYPE_STANDARD;
private $basePath;
private $css;
private $images;
public function addSprite(PhutilSprite $sprite) {
$this->generated = false;
$this->sprites[] = $sprite;
return $this;
}
public function setCSSHeader($header) {
$this->generated = false;
$this->cssHeader = $header;
return $this;
}
public function setScales(array $scales) {
$this->scales = array_values($scales);
return $this;
}
public function getScales() {
return $this->scales;
}
public function setSheetType($type) {
$this->type = $type;
return $this;
}
public function setBasePath($base_path) {
$this->basePath = $base_path;
return $this;
}
private function generate() {
if ($this->generated) {
return;
}
$multi_row = true;
$multi_col = true;
$margin_w = 1;
$margin_h = 1;
$type = $this->type;
switch ($type) {
case self::TYPE_STANDARD:
break;
case self::TYPE_REPEAT_X:
$multi_col = false;
$margin_w = 0;
$width = null;
foreach ($this->sprites as $sprite) {
if ($width === null) {
$width = $sprite->getSourceW();
} else if ($width !== $sprite->getSourceW()) {
throw new Exception(
pht(
"All sprites in a '%s' sheet must have the same width.",
'repeat-x'));
}
}
break;
case self::TYPE_REPEAT_Y:
$multi_row = false;
$margin_h = 0;
$height = null;
foreach ($this->sprites as $sprite) {
if ($height === null) {
$height = $sprite->getSourceH();
} else if ($height !== $sprite->getSourceH()) {
throw new Exception(
pht(
"All sprites in a '%s' sheet must have the same height.",
'repeat-y'));
}
}
break;
default:
throw new Exception(pht("Unknown sprite sheet type '%s'!", $type));
}
$css = array();
if ($this->cssHeader) {
$css[] = $this->cssHeader;
}
$out_w = 0;
$out_h = 0;
// Lay out the sprite sheet. We attempt to build a roughly square sheet
// so it's easier to manage, since 2000x20 is more cumbersome for humans
// to deal with than 200x200.
//
// To do this, we use a simple greedy algorithm, adding sprites one at a
// time. For each sprite, if the sheet is at least as wide as it is tall
// we create a new row. Otherwise, we try to add it to an existing row.
//
// This isn't optimal, but does a reasonable job in most cases and isn't
// too messy.
// Group the sprites by their sizes. We lay them out in the sheet as
// boxes, but then put them into the boxes in the order they were added
// so similar sprites end up nearby on the final sheet.
$boxes = array();
foreach (array_reverse($this->sprites) as $sprite) {
$s_w = $sprite->getSourceW() + $margin_w;
$s_h = $sprite->getSourceH() + $margin_h;
$boxes[$s_w][$s_h][] = $sprite;
}
$rows = array();
foreach ($this->sprites as $sprite) {
$s_w = $sprite->getSourceW() + $margin_w;
$s_h = $sprite->getSourceH() + $margin_h;
// Choose a row for this sprite.
$maybe = array();
foreach ($rows as $key => $row) {
if ($row['h'] < $s_h) {
// We can only add it to a row if the row is at least as tall as the
// sprite.
continue;
}
// We prefer rows which have the same height as the sprite, and then
// rows which aren't yet very wide.
$wasted_v = ($row['h'] - $s_h);
$wasted_h = ($row['w'] / $out_w);
$maybe[$key] = $wasted_v + $wasted_h;
}
$row_key = null;
if ($maybe && $multi_col) {
// If there were any candidate rows, pick the best one.
asort($maybe);
$row_key = head_key($maybe);
}
if ($row_key !== null && $multi_row) {
// If there's a candidate row, but adding the sprite to it would make
// the sprite wider than it is tall, create a new row instead. This
// generally keeps the sprite square-ish.
if ($rows[$row_key]['w'] + $s_w > $out_h) {
$row_key = null;
}
}
if ($row_key === null) {
// Add a new row.
$rows[] = array(
'w' => 0,
'h' => $s_h,
'boxes' => array(),
);
$row_key = last_key($rows);
$out_h += $s_h;
}
// Add the sprite box to the row.
$row = $rows[$row_key];
$row['w'] += $s_w;
$row['boxes'][] = array($s_w, $s_h);
$rows[$row_key] = $row;
$out_w = max($row['w'], $out_w);
}
$images = array();
foreach ($this->scales as $scale) {
$img = imagecreatetruecolor($out_w * $scale, $out_h * $scale);
imagesavealpha($img, true);
imagefill($img, 0, 0, imagecolorallocatealpha($img, 0, 0, 0, 127));
$images[$scale] = $img;
}
// Put the shorter rows first. At the same height, put the wider rows first.
// This makes the resulting sheet more human-readable.
foreach ($rows as $key => $row) {
$rows[$key]['sort'] = $row['h'] + (1 - ($row['w'] / $out_w));
}
$rows = isort($rows, 'sort');
$pos_x = 0;
$pos_y = 0;
$rules = array();
foreach ($rows as $row) {
$max_h = 0;
foreach ($row['boxes'] as $box) {
$sprite = array_pop($boxes[$box[0]][$box[1]]);
foreach ($images as $scale => $img) {
$src = $this->loadSource($sprite, $scale);
imagecopy(
$img,
$src,
$scale * $pos_x, $scale * $pos_y,
$scale * $sprite->getSourceX(), $scale * $sprite->getSourceY(),
$scale * $sprite->getSourceW(), $scale * $sprite->getSourceH());
}
$rule = $sprite->getTargetCSS();
$cssx = (-$pos_x).'px';
$cssy = (-$pos_y).'px';
$rules[$sprite->getName()] = "{$rule} {\n".
" background-position: {$cssx} {$cssy};\n}";
$pos_x += $sprite->getSourceW() + $margin_w;
$max_h = max($max_h, $sprite->getSourceH());
}
$pos_x = 0;
$pos_y += $max_h + $margin_h;
}
// Generate CSS rules in input order.
foreach ($this->sprites as $sprite) {
$css[] = $rules[$sprite->getName()];
}
$this->images = $images;
$this->css = implode("\n\n", $css)."\n";
$this->generated = true;
}
public function generateImage($path, $scale = 1) {
$this->generate();
$this->log(pht("Writing sprite '%s'...", $path));
imagepng($this->images[$scale], $path);
return $this;
}
public function generateCSS($path) {
$this->generate();
$this->log(pht("Writing CSS '%s'...", $path));
$out = $this->css;
$out = str_replace('{X}', imagesx($this->images[1]), $out);
$out = str_replace('{Y}', imagesy($this->images[1]), $out);
Filesystem::writeFile($path, $out);
return $this;
}
public function needsRegeneration(array $manifest) {
return ($this->buildManifest() !== $manifest);
}
private function buildManifest() {
$output = array();
foreach ($this->sprites as $sprite) {
$output[$sprite->getName()] = array(
'name' => $sprite->getName(),
'rule' => $sprite->getTargetCSS(),
'hash' => $this->loadSourceHash($sprite),
);
}
ksort($output);
$data = array(
'version' => self::MANIFEST_VERSION,
'sprites' => $output,
'scales' => $this->scales,
'header' => $this->cssHeader,
'type' => $this->type,
);
return $data;
}
public function generateManifest($path) {
$data = $this->buildManifest();
$json = new PhutilJSON();
$data = $json->encodeFormatted($data);
Filesystem::writeFile($path, $data);
return $this;
}
private function log($message) {
echo $message."\n";
}
private function loadSourceHash(PhutilSprite $sprite) {
$inputs = array();
foreach ($this->scales as $scale) {
$file = $sprite->getSourceFile($scale);
// If two users have a project in different places, like:
//
// /home/alincoln/project
// /home/htaft/project
//
// ...we want to ignore the `/home/alincoln` part when hashing the sheet,
// since the sprites don't change when the project directory moves. If
// the base path is set, build the hashes using paths relative to the
// base path.
$file_key = $file;
if ($this->basePath) {
$file_key = Filesystem::readablePath($file, $this->basePath);
}
if (empty($this->hashes[$file_key])) {
$this->hashes[$file_key] = md5(Filesystem::readFile($file));
}
$inputs[] = $file_key;
$inputs[] = $this->hashes[$file_key];
}
$inputs[] = $sprite->getSourceX();
$inputs[] = $sprite->getSourceY();
$inputs[] = $sprite->getSourceW();
$inputs[] = $sprite->getSourceH();
return md5(implode(':', $inputs));
}
private function loadSource(PhutilSprite $sprite, $scale) {
$file = $sprite->getSourceFile($scale);
if (empty($this->sources[$file])) {
$data = Filesystem::readFile($file);
$image = imagecreatefromstring($data);
$this->sources[$file] = array(
'image' => $image,
'x' => imagesx($image),
'y' => imagesy($image),
);
}
$s_w = $sprite->getSourceW() * $scale;
$i_w = $this->sources[$file]['x'];
if ($s_w > $i_w) {
throw new Exception(
pht(
"Sprite source for '%s' is too small (expected width %d, found %d).",
$file,
$s_w,
$i_w));
}
$s_h = $sprite->getSourceH() * $scale;
$i_h = $this->sources[$file]['y'];
if ($s_h > $i_h) {
throw new Exception(
pht(
"Sprite source for '%s' is too small (expected height %d, found %d).",
$file,
$s_h,
$i_h));
}
return $this->sources[$file]['image'];
}
}

View File

@@ -0,0 +1,9 @@
<?php
final class AphrontScopedUnguardedWriteCapability extends Phobject {
public function __destruct() {
AphrontWriteGuard::endUnguardedWrites();
}
}

View File

@@ -0,0 +1,267 @@
<?php
/**
* Guard writes against CSRF. The Aphront structure takes care of most of this
* for you, you just need to call:
*
* AphrontWriteGuard::willWrite();
*
* ...before executing a write against any new kind of storage engine. MySQL
* databases and the default file storage engines are already covered, but if
* you introduce new types of datastores make sure their writes are guarded. If
* you don't guard writes and make a mistake doing CSRF checks in a controller,
* a CSRF vulnerability can escape undetected.
*
* If you need to execute writes on a page which doesn't have CSRF tokens (for
* example, because you need to do logging), you can temporarily disable the
* write guard by calling:
*
* AphrontWriteGuard::beginUnguardedWrites();
* do_logging_write();
* AphrontWriteGuard::endUnguardedWrites();
*
* This is dangerous, because it disables the backup layer of CSRF protection
* this class provides. You should need this only very, very rarely.
*
* @task protect Protecting Writes
* @task disable Disabling Protection
* @task manage Managing Write Guards
* @task internal Internals
*/
final class AphrontWriteGuard extends Phobject {
private static $instance;
private static $allowUnguardedWrites = false;
private $callback;
private $allowDepth = 0;
/* -( Managing Write Guards )---------------------------------------------- */
/**
* Construct a new write guard for a request. Only one write guard may be
* active at a time. You must explicitly call @{method:dispose} when you are
* done with a write guard:
*
* $guard = new AphrontWriteGuard($callback);
* // ...
* $guard->dispose();
*
* Normally, you do not need to manage guards yourself -- the Aphront stack
* handles it for you.
*
* This class accepts a callback, which will be invoked when a write is
* attempted. The callback should validate the presence of a CSRF token in
* the request, or abort the request (e.g., by throwing an exception) if a
* valid token isn't present.
*
* @param callable CSRF callback.
* @return this
* @task manage
*/
public function __construct($callback) {
if (self::$instance) {
throw new Exception(
pht(
'An %s already exists. Dispose of the previous guard '.
'before creating a new one.',
__CLASS__));
}
if (self::$allowUnguardedWrites) {
throw new Exception(
pht(
'An %s is being created in a context which permits '.
'unguarded writes unconditionally. This is not allowed and '.
'indicates a serious error.',
__CLASS__));
}
$this->callback = $callback;
self::$instance = $this;
}
/**
* Dispose of the active write guard. You must call this method when you are
* done with a write guard. You do not normally need to call this yourself.
*
* @return void
* @task manage
*/
public function dispose() {
if (!self::$instance) {
throw new Exception(pht(
'Attempting to dispose of write guard, but no write guard is active!'));
}
if ($this->allowDepth > 0) {
throw new Exception(
pht(
'Imbalanced %s: more %s calls than %s calls.',
__CLASS__,
'beginUnguardedWrites()',
'endUnguardedWrites()'));
}
self::$instance = null;
}
/**
* Determine if there is an active write guard.
*
* @return bool
* @task manage
*/
public static function isGuardActive() {
return (bool)self::$instance;
}
/**
* Return on instance of AphrontWriteGuard if it's active, or null
*
* @return AphrontWriteGuard|null
*/
public static function getInstance() {
return self::$instance;
}
/* -( Protecting Writes )-------------------------------------------------- */
/**
* Declare intention to perform a write, validating that writes are allowed.
* You should call this method before executing a write whenever you implement
* a new storage engine where information can be permanently kept.
*
* Writes are permitted if:
*
* - The request has valid CSRF tokens.
* - Unguarded writes have been temporarily enabled by a call to
* @{method:beginUnguardedWrites}.
* - All write guarding has been disabled with
* @{method:allowDangerousUnguardedWrites}.
*
* If none of these conditions are true, this method will throw and prevent
* the write.
*
* @return void
* @task protect
*/
public static function willWrite() {
if (!self::$instance) {
if (!self::$allowUnguardedWrites) {
throw new Exception(
pht(
'Unguarded write! There must be an active %s to perform writes.',
__CLASS__));
} else {
// Unguarded writes are being allowed unconditionally.
return;
}
}
$instance = self::$instance;
if ($instance->allowDepth == 0) {
call_user_func($instance->callback);
}
}
/* -( Disabling Write Protection )----------------------------------------- */
/**
* Enter a scope which permits unguarded writes. This works like
* @{method:beginUnguardedWrites} but returns an object which will end
* the unguarded write scope when its __destruct() method is called. This
* is useful to more easily handle exceptions correctly in unguarded write
* blocks:
*
* // Restores the guard even if do_logging() throws.
* function unguarded_scope() {
* $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
* do_logging();
* }
*
* @return AphrontScopedUnguardedWriteCapability Object which ends unguarded
* writes when it leaves scope.
* @task disable
*/
public static function beginScopedUnguardedWrites() {
self::beginUnguardedWrites();
return new AphrontScopedUnguardedWriteCapability();
}
/**
* Begin a block which permits unguarded writes. You should use this very
* sparingly, and only for things like logging where CSRF is not a concern.
*
* You must pair every call to @{method:beginUnguardedWrites} with a call to
* @{method:endUnguardedWrites}:
*
* AphrontWriteGuard::beginUnguardedWrites();
* do_logging();
* AphrontWriteGuard::endUnguardedWrites();
*
* @return void
* @task disable
*/
public static function beginUnguardedWrites() {
if (!self::$instance) {
return;
}
self::$instance->allowDepth++;
}
/**
* Declare that you have finished performing unguarded writes. You must
* call this exactly once for each call to @{method:beginUnguardedWrites}.
*
* @return void
* @task disable
*/
public static function endUnguardedWrites() {
if (!self::$instance) {
return;
}
if (self::$instance->allowDepth <= 0) {
throw new Exception(
pht(
'Imbalanced %s: more %s calls than %s calls.',
__CLASS__,
'endUnguardedWrites()',
'beginUnguardedWrites()'));
}
self::$instance->allowDepth--;
}
/**
* Allow execution of unguarded writes. This is ONLY appropriate for use in
* script contexts or other contexts where you are guaranteed to never be
* vulnerable to CSRF concerns. Calling this method is EXTREMELY DANGEROUS
* if you do not understand the consequences.
*
* If you need to perform unguarded writes on an otherwise guarded workflow
* which is vulnerable to CSRF, use @{method:beginUnguardedWrites}.
*
* @return void
* @task disable
*/
public static function allowDangerousUnguardedWrites($allow) {
if (self::$instance) {
throw new Exception(
pht(
'You can not unconditionally disable %s by calling %s while a write '.
'guard is active. Use %s to temporarily allow unguarded writes.',
__CLASS__,
__FUNCTION__.'()',
'beginUnguardedWrites()'));
}
self::$allowUnguardedWrites = true;
}
}

View File

@@ -10,13 +10,15 @@ final class AlmanacConsoleController extends AlmanacController {
$viewer = $request->getViewer(); $viewer = $request->getViewer();
$menu = id(new PHUIObjectItemListView()) $menu = id(new PHUIObjectItemListView())
->setUser($viewer); ->setViewer($viewer)
->setBig(true);
$menu->addItem( $menu->addItem(
id(new PHUIObjectItemView()) id(new PHUIObjectItemView())
->setHeader(pht('Devices')) ->setHeader(pht('Devices'))
->setHref($this->getApplicationURI('device/')) ->setHref($this->getApplicationURI('device/'))
->setImageIcon('fa-server') ->setImageIcon('fa-server')
->setClickable(true)
->addAttribute( ->addAttribute(
pht( pht(
'Create an inventory of physical and virtual hosts and '. 'Create an inventory of physical and virtual hosts and '.
@@ -27,6 +29,7 @@ final class AlmanacConsoleController extends AlmanacController {
->setHeader(pht('Services')) ->setHeader(pht('Services'))
->setHref($this->getApplicationURI('service/')) ->setHref($this->getApplicationURI('service/'))
->setImageIcon('fa-plug') ->setImageIcon('fa-plug')
->setClickable(true)
->addAttribute( ->addAttribute(
pht( pht(
'Create and update services, and map them to interfaces on '. 'Create and update services, and map them to interfaces on '.
@@ -37,6 +40,7 @@ final class AlmanacConsoleController extends AlmanacController {
->setHeader(pht('Networks')) ->setHeader(pht('Networks'))
->setHref($this->getApplicationURI('network/')) ->setHref($this->getApplicationURI('network/'))
->setImageIcon('fa-globe') ->setImageIcon('fa-globe')
->setClickable(true)
->addAttribute( ->addAttribute(
pht( pht(
'Manage public and private networks.'))); 'Manage public and private networks.')));
@@ -46,6 +50,7 @@ final class AlmanacConsoleController extends AlmanacController {
->setHeader(pht('Namespaces')) ->setHeader(pht('Namespaces'))
->setHref($this->getApplicationURI('namespace/')) ->setHref($this->getApplicationURI('namespace/'))
->setImageIcon('fa-asterisk') ->setImageIcon('fa-asterisk')
->setClickable(true)
->addAttribute( ->addAttribute(
pht('Control who can create new named services and devices.'))); pht('Control who can create new named services and devices.')));
@@ -57,6 +62,7 @@ final class AlmanacConsoleController extends AlmanacController {
->setHeader(pht('Documentation')) ->setHeader(pht('Documentation'))
->setHref($docs_uri) ->setHref($docs_uri)
->setImageIcon('fa-book') ->setImageIcon('fa-book')
->setClickable(true)
->addAttribute(pht('Browse documentation for Almanac.'))); ->addAttribute(pht('Browse documentation for Almanac.')));
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
@@ -64,23 +70,20 @@ final class AlmanacConsoleController extends AlmanacController {
$crumbs->setBorder(true); $crumbs->setBorder(true);
$box = id(new PHUIObjectBoxView()) $box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Almanac Console'))
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setObjectList($menu); ->setObjectList($menu);
$header = id(new PHUIHeaderView()) $launcher_view = id(new PHUILauncherView())
->setHeader(pht('Almanac Console')) ->appendChild($box);
->setHeaderIcon('fa-server');
$view = id(new PHUITwoColumnView()) $view = id(new PHUITwoColumnView())
->setHeader($header) ->setFooter($launcher_view);
->setFooter(array(
$box,
));
return $this->newPage() return $this->newPage()
->setTitle(pht('Almanac Console')) ->setTitle(pht('Almanac Console'))
->setCrumbs($crumbs) ->setCrumbs($crumbs)
->appendChild($view); ->appendChild($view);
} }
} }

View File

@@ -57,15 +57,6 @@ final class AlmanacKeys extends Phobject {
} }
public static function getClusterSSHUser() { public static function getClusterSSHUser() {
// NOTE: When instancing, we currently use the SSH username to figure out
// which instance you are connecting to. We can't use the host name because
// we have no way to tell which host you think you're reaching: the SSH
// protocol does not have a mechanism like a "Host" header.
$username = PhabricatorEnv::getEnvConfig('cluster.instance');
if (strlen($username)) {
return $username;
}
$username = PhabricatorEnv::getEnvConfig('diffusion.ssh-user'); $username = PhabricatorEnv::getEnvConfig('diffusion.ssh-user');
if (strlen($username)) { if (strlen($username)) {
return $username; return $username;

View File

@@ -550,11 +550,18 @@ abstract class PhabricatorAphlictManagementWorkflow
} }
private function getStartCommand(array $server_argv) { private function getStartCommand(array $server_argv) {
$launch_argv = array();
if ($this->debug) {
$launch_argv[] = '--debug=1';
}
return csprintf( return csprintf(
'%R %Ls -- %s %Ls', '%R %Ls -- %s %Ls %Ls',
$this->getNodeBinary(), $this->getNodeBinary(),
$this->getNodeArgv(), $this->getNodeArgv(),
$this->getAphlictScriptPath(), $this->getAphlictScriptPath(),
$launch_argv,
$server_argv); $server_argv);
} }

View File

@@ -105,6 +105,14 @@ final class PhabricatorAuditEditor
switch ($xaction->getTransactionType()) { switch ($xaction->getTransactionType()) {
case PhabricatorAuditActionConstants::INLINE: case PhabricatorAuditActionConstants::INLINE:
$comment = $xaction->getComment();
$comment->setAttribute('editing', false);
PhabricatorVersionedDraft::purgeDrafts(
$comment->getPHID(),
$this->getActingAsPHID());
return;
case PhabricatorAuditTransaction::TYPE_COMMIT: case PhabricatorAuditTransaction::TYPE_COMMIT:
return; return;
} }
@@ -232,14 +240,22 @@ final class PhabricatorAuditEditor
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) { PhabricatorApplicationTransaction $xaction) {
$auditors_type = DiffusionCommitAuditorsTransaction::TRANSACTIONTYPE;
$xactions = parent::expandTransaction($object, $xaction); $xactions = parent::expandTransaction($object, $xaction);
switch ($xaction->getTransactionType()) { switch ($xaction->getTransactionType()) {
case PhabricatorAuditTransaction::TYPE_COMMIT: case PhabricatorAuditTransaction::TYPE_COMMIT:
$request = $this->createAuditRequestTransactionFromCommitMessage( $phids = $this->getAuditRequestTransactionPHIDsFromCommitMessage(
$object); $object);
if ($request) { if ($phids) {
$xactions[] = $request; $xactions[] = $object->getApplicationTransactionTemplate()
$this->addUnmentionablePHIDs($request->getNewValue()); ->setTransactionType($auditors_type)
->setNewValue(
array(
'+' => array_fuse($phids),
));
$this->addUnmentionablePHIDs($phids);
} }
break; break;
default: default:
@@ -268,7 +284,7 @@ final class PhabricatorAuditEditor
return $xactions; return $xactions;
} }
private function createAuditRequestTransactionFromCommitMessage( private function getAuditRequestTransactionPHIDsFromCommitMessage(
PhabricatorRepositoryCommit $commit) { PhabricatorRepositoryCommit $commit) {
$actor = $this->getActor(); $actor = $this->getActor();
@@ -297,12 +313,7 @@ final class PhabricatorAuditEditor
return array(); return array();
} }
return $commit->getApplicationTransactionTemplate() return $phids;
->setTransactionType(DiffusionCommitAuditorsTransaction::TRANSACTIONTYPE)
->setNewValue(
array(
'+' => array_fuse($phids),
));
} }
protected function sortTransactions(array $xactions) { protected function sortTransactions(array $xactions) {
@@ -405,6 +416,31 @@ final class PhabricatorAuditEditor
$phid_map[] = $reverted_phids; $phid_map[] = $reverted_phids;
} }
// See T13463. Copy "related task" edges from the associated revision, if
// one exists.
$revision = DiffusionCommitRevisionQuery::loadRevisionForCommit(
$actor,
$object);
if ($revision) {
$task_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$revision->getPHID(),
DifferentialRevisionHasTaskEdgeType::EDGECONST);
$task_phids = array_fuse($task_phids);
if ($task_phids) {
$related_edge = DiffusionCommitHasTaskEdgeType::EDGECONST;
$result[] = id(new PhabricatorAuditTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $related_edge)
->setNewValue(array('+' => $task_phids));
}
// Mark these objects as unmentionable, since the explicit relationship
// is stronger and any mentions are redundant.
$phid_map[] = $task_phids;
}
$phid_map = array_mergev($phid_map); $phid_map = array_mergev($phid_map);
$this->addUnmentionablePHIDs($phid_map); $this->addUnmentionablePHIDs($phid_map);

View File

@@ -0,0 +1,75 @@
<?php
final class DiffusionInternalCommitSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Diffusion Raw Commits');
}
public function getApplicationClassName() {
return 'PhabricatorDiffusionApplication';
}
public function newQuery() {
return new DiffusionCommitQuery();
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['repositoryPHIDs']) {
$query->withRepositoryPHIDs($map['repositoryPHIDs']);
}
return $query;
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Repositories'))
->setKey('repositoryPHIDs')
->setDatasource(new DiffusionRepositoryFunctionDatasource())
->setDescription(pht('Find commits in particular repositories.')),
);
}
protected function getURI($path) {
return null;
}
protected function renderResultList(
array $commits,
PhabricatorSavedQuery $query,
array $handles) {
return null;
}
protected function getObjectWireFieldsForConduit(
$object,
array $field_extensions,
array $extension_data) {
$commit = $object;
$viewer = $this->requireViewer();
$repository = $commit->getRepository();
$identifier = $commit->getCommitIdentifier();
id(new DiffusionRepositoryClusterEngine())
->setViewer($viewer)
->setRepository($repository)
->synchronizeWorkingCopyBeforeRead();
$ref = id(new DiffusionLowLevelCommitQuery())
->setRepository($repository)
->withIdentifier($identifier)
->execute();
return array(
'ref' => $ref->newDictionary(),
);
}
}

View File

@@ -221,9 +221,9 @@ final class PhabricatorCommitSearchEngine
$bucket = $this->getResultBucket($query); $bucket = $this->getResultBucket($query);
$template = id(new PhabricatorAuditListView()) $template = id(new DiffusionCommitGraphView())
->setViewer($viewer) ->setViewer($viewer)
->setShowDrafts(true); ->setShowAuditors(true);
$views = array(); $views = array();
if ($bucket) { if ($bucket) {
@@ -235,37 +235,31 @@ final class PhabricatorCommitSearchEngine
foreach ($groups as $group) { foreach ($groups as $group) {
// Don't show groups in Dashboard Panels // Don't show groups in Dashboard Panels
if ($group->getObjects() || !$this->isPanelContext()) { if ($group->getObjects() || !$this->isPanelContext()) {
$views[] = id(clone $template) $item_list = id(clone $template)
->setCommits($group->getObjects())
->newObjectItemListView();
$views[] = $item_list
->setHeader($group->getName()) ->setHeader($group->getName())
->setNoDataString($group->getNoDataString()) ->setNoDataString($group->getNoDataString());
->setCommits($group->getObjects());
} }
} }
} catch (Exception $ex) { } catch (Exception $ex) {
$this->addError($ex->getMessage()); $this->addError($ex->getMessage());
} }
} else {
$views[] = id(clone $template)
->setCommits($commits)
->setNoDataString(pht('No commits found.'));
} }
if (!$views) { if (!$views) {
$views[] = id(new PhabricatorAuditListView()) $item_list = id(clone $template)
->setViewer($viewer) ->setCommits($commits)
->newObjectItemListView();
$views[] = $item_list
->setNoDataString(pht('No commits found.')); ->setNoDataString(pht('No commits found.'));
} }
if (count($views) == 1) { return id(new PhabricatorApplicationSearchResultView())
$list = head($views)->buildList(); ->setContent($views);
} else {
$list = $views;
}
$result = new PhabricatorApplicationSearchResultView();
$result->setContent($list);
return $result;
} }
protected function getNewUserBody() { protected function getNewUserBody() {

View File

@@ -1,27 +1,16 @@
<?php <?php
final class PhabricatorAuditInlineComment final class PhabricatorAuditInlineComment
extends Phobject extends PhabricatorInlineComment {
implements PhabricatorInlineCommentInterface {
private $proxy; protected function newStorageObject() {
private $syntheticAuthor; return new PhabricatorAuditTransactionComment();
private $isGhost;
public function __construct() {
$this->proxy = new PhabricatorAuditTransactionComment();
} }
public function __clone() { public function getControllerURI() {
$this->proxy = clone $this->proxy; return urisprintf(
} '/diffusion/inline/edit/%s/',
$this->getCommitPHID());
public function getTransactionPHID() {
return $this->proxy->getTransactionPHID();
}
public function getTransactionComment() {
return $this->proxy;
} }
public function supportsHiding() { public function supportsHiding() {
@@ -36,246 +25,40 @@ final class PhabricatorAuditInlineComment
$content_source = PhabricatorContentSource::newForSource( $content_source = PhabricatorContentSource::newForSource(
PhabricatorOldWorldContentSource::SOURCECONST); PhabricatorOldWorldContentSource::SOURCECONST);
$this->proxy $this->getStorageObject()
->setViewPolicy('public') ->setViewPolicy('public')
->setEditPolicy($this->getAuthorPHID()) ->setEditPolicy($this->getAuthorPHID())
->setContentSource($content_source) ->setContentSource($content_source)
->setCommentVersion(1); ->setCommentVersion(1);
return $this->proxy; return $this->getStorageObject();
}
public static function loadID($id) {
$inlines = id(new PhabricatorAuditTransactionComment())->loadAllWhere(
'id = %d',
$id);
if (!$inlines) {
return null;
}
return head(self::buildProxies($inlines));
}
public static function loadPHID($phid) {
$inlines = id(new PhabricatorAuditTransactionComment())->loadAllWhere(
'phid = %s',
$phid);
if (!$inlines) {
return null;
}
return head(self::buildProxies($inlines));
}
public static function loadDraftComments(
PhabricatorUser $viewer,
$commit_phid,
$raw = false) {
$inlines = id(new DiffusionDiffInlineCommentQuery())
->setViewer($viewer)
->withAuthorPHIDs(array($viewer->getPHID()))
->withCommitPHIDs(array($commit_phid))
->withHasTransaction(false)
->withHasPath(true)
->withIsDeleted(false)
->needReplyToComments(true)
->execute();
if ($raw) {
return $inlines;
}
return self::buildProxies($inlines);
}
public static function loadPublishedComments(
PhabricatorUser $viewer,
$commit_phid) {
$inlines = id(new DiffusionDiffInlineCommentQuery())
->setViewer($viewer)
->withCommitPHIDs(array($commit_phid))
->withHasTransaction(true)
->withHasPath(true)
->execute();
return self::buildProxies($inlines);
}
public static function loadDraftAndPublishedComments(
PhabricatorUser $viewer,
$commit_phid,
$path_id = null) {
if ($path_id === null) {
$inlines = id(new PhabricatorAuditTransactionComment())->loadAllWhere(
'commitPHID = %s AND (transactionPHID IS NOT NULL OR authorPHID = %s)
AND pathID IS NOT NULL',
$commit_phid,
$viewer->getPHID());
} else {
$inlines = id(new PhabricatorAuditTransactionComment())->loadAllWhere(
'commitPHID = %s AND pathID = %d AND
((authorPHID = %s AND isDeleted = 0) OR transactionPHID IS NOT NULL)',
$commit_phid,
$path_id,
$viewer->getPHID());
}
return self::buildProxies($inlines);
}
private static function buildProxies(array $inlines) {
$results = array();
foreach ($inlines as $key => $inline) {
$results[$key] = self::newFromModernComment(
$inline);
}
return $results;
}
public function setSyntheticAuthor($synthetic_author) {
$this->syntheticAuthor = $synthetic_author;
return $this;
}
public function getSyntheticAuthor() {
return $this->syntheticAuthor;
}
public function openTransaction() {
$this->proxy->openTransaction();
}
public function saveTransaction() {
$this->proxy->saveTransaction();
}
public function save() {
$this->getTransactionCommentForSave()->save();
return $this;
}
public function delete() {
$this->proxy->delete();
return $this;
}
public function getID() {
return $this->proxy->getID();
}
public function getPHID() {
return $this->proxy->getPHID();
} }
public static function newFromModernComment( public static function newFromModernComment(
PhabricatorAuditTransactionComment $comment) { PhabricatorAuditTransactionComment $comment) {
$obj = new PhabricatorAuditInlineComment(); $obj = new PhabricatorAuditInlineComment();
$obj->proxy = $comment; $obj->setStorageObject($comment);
return $obj; return $obj;
} }
public function isCompatible(PhabricatorInlineCommentInterface $comment) {
return
($this->getAuthorPHID() === $comment->getAuthorPHID()) &&
($this->getSyntheticAuthor() === $comment->getSyntheticAuthor()) &&
($this->getContent() === $comment->getContent());
}
public function setContent($content) {
$this->proxy->setContent($content);
return $this;
}
public function getContent() {
return $this->proxy->getContent();
}
public function isDraft() {
return !$this->proxy->getTransactionPHID();
}
public function setPathID($id) { public function setPathID($id) {
$this->proxy->setPathID($id); $this->getStorageObject()->setPathID($id);
return $this; return $this;
} }
public function getPathID() { public function getPathID() {
return $this->proxy->getPathID(); return $this->getStorageObject()->getPathID();
}
public function setIsNewFile($is_new) {
$this->proxy->setIsNewFile($is_new);
return $this;
}
public function getIsNewFile() {
return $this->proxy->getIsNewFile();
}
public function setLineNumber($number) {
$this->proxy->setLineNumber($number);
return $this;
}
public function getLineNumber() {
return $this->proxy->getLineNumber();
}
public function setLineLength($length) {
$this->proxy->setLineLength($length);
return $this;
}
public function getLineLength() {
return $this->proxy->getLineLength();
}
public function setCache($cache) {
return $this;
}
public function getCache() {
return null;
}
public function setAuthorPHID($phid) {
$this->proxy->setAuthorPHID($phid);
return $this;
}
public function getAuthorPHID() {
return $this->proxy->getAuthorPHID();
} }
public function setCommitPHID($commit_phid) { public function setCommitPHID($commit_phid) {
$this->proxy->setCommitPHID($commit_phid); $this->getStorageObject()->setCommitPHID($commit_phid);
return $this; return $this;
} }
public function getCommitPHID() { public function getCommitPHID() {
return $this->proxy->getCommitPHID(); return $this->getStorageObject()->getCommitPHID();
}
// When setting a comment ID, we also generate a phantom transaction PHID for
// the future transaction.
public function setAuditCommentID($id) {
$this->proxy->setLegacyCommentID($id);
$this->proxy->setTransactionPHID(
PhabricatorPHID::generateNewPHID(
PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST,
PhabricatorRepositoryCommitPHIDType::TYPECONST));
return $this;
}
public function getAuditCommentID() {
return $this->proxy->getLegacyCommentID();
} }
public function setChangesetID($id) { public function setChangesetID($id) {
@@ -286,82 +69,4 @@ final class PhabricatorAuditInlineComment
return $this->getPathID(); return $this->getPathID();
} }
public function setReplyToCommentPHID($phid) {
$this->proxy->setReplyToCommentPHID($phid);
return $this;
}
public function getReplyToCommentPHID() {
return $this->proxy->getReplyToCommentPHID();
}
public function setHasReplies($has_replies) {
$this->proxy->setHasReplies($has_replies);
return $this;
}
public function getHasReplies() {
return $this->proxy->getHasReplies();
}
public function setIsDeleted($is_deleted) {
$this->proxy->setIsDeleted($is_deleted);
return $this;
}
public function getIsDeleted() {
return $this->proxy->getIsDeleted();
}
public function setFixedState($state) {
$this->proxy->setFixedState($state);
return $this;
}
public function getFixedState() {
return $this->proxy->getFixedState();
}
public function setIsGhost($is_ghost) {
$this->isGhost = $is_ghost;
return $this;
}
public function getIsGhost() {
return $this->isGhost;
}
public function getDateModified() {
return $this->proxy->getDateModified();
}
public function getDateCreated() {
return $this->proxy->getDateCreated();
}
/* -( PhabricatorMarkupInterface Implementation )-------------------------- */
public function getMarkupFieldKey($field) {
return 'AI:'.$this->getID();
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newDifferentialMarkupEngine();
}
public function getMarkupText($field) {
return $this->getContent();
}
public function didMarkupText($field, $output, PhutilMarkupEngine $engine) {
return $output;
}
public function shouldUseMarkupCache($field) {
// Only cache submitted comments.
return ($this->getID() && $this->getAuditCommentID());
}
} }

View File

@@ -1,7 +1,9 @@
<?php <?php
final class PhabricatorAuditTransactionComment final class PhabricatorAuditTransactionComment
extends PhabricatorApplicationTransactionComment { extends PhabricatorApplicationTransactionComment
implements
PhabricatorInlineCommentInterface {
protected $commitPHID; protected $commitPHID;
protected $pathID; protected $pathID;
@@ -12,8 +14,10 @@ final class PhabricatorAuditTransactionComment
protected $hasReplies = 0; protected $hasReplies = 0;
protected $replyToCommentPHID; protected $replyToCommentPHID;
protected $legacyCommentID; protected $legacyCommentID;
protected $attributes = array();
private $replyToComment = self::ATTACHABLE; private $replyToComment = self::ATTACHABLE;
private $inlineContext = self::ATTACHABLE;
public function getApplicationTransactionObject() { public function getApplicationTransactionObject() {
return new PhabricatorAuditTransaction(); return new PhabricatorAuditTransaction();
@@ -54,6 +58,10 @@ final class PhabricatorAuditTransactionComment
), ),
) + $config[self::CONFIG_KEY_SCHEMA]; ) + $config[self::CONFIG_KEY_SCHEMA];
$config[self::CONFIG_SERIALIZATION] = array(
'attributes' => self::SERIALIZATION_JSON,
) + idx($config, self::CONFIG_SERIALIZATION, array());
return $config; return $config;
} }
@@ -67,4 +75,27 @@ final class PhabricatorAuditTransactionComment
return $this->assertAttached($this->replyToComment); return $this->assertAttached($this->replyToComment);
} }
public function getAttribute($key, $default = null) {
return idx($this->attributes, $key, $default);
}
public function setAttribute($key, $value) {
$this->attributes[$key] = $value;
return $this;
}
public function newInlineCommentObject() {
return PhabricatorAuditInlineComment::newFromModernComment($this);
}
public function getInlineContext() {
return $this->assertAttached($this->inlineContext);
}
public function attachInlineContext(
PhabricatorInlineCommentContext $context = null) {
$this->inlineContext = $context;
return $this;
}
} }

View File

@@ -1,179 +0,0 @@
<?php
final class PhabricatorAuditListView extends AphrontView {
private $commits = array();
private $header;
private $showDrafts;
private $noDataString;
private $highlightedAudits;
public function setNoDataString($no_data_string) {
$this->noDataString = $no_data_string;
return $this;
}
public function getNoDataString() {
return $this->noDataString;
}
public function setHeader($header) {
$this->header = $header;
return $this;
}
public function getHeader() {
return $this->header;
}
public function setShowDrafts($show_drafts) {
$this->showDrafts = $show_drafts;
return $this;
}
public function getShowDrafts() {
return $this->showDrafts;
}
/**
* These commits should have both commit data and audit requests attached.
*/
public function setCommits(array $commits) {
assert_instances_of($commits, 'PhabricatorRepositoryCommit');
$this->commits = mpull($commits, null, 'getPHID');
return $this;
}
public function getCommits() {
return $this->commits;
}
private function getCommitDescription($phid) {
if ($this->commits === null) {
return pht('(Unknown Commit)');
}
$commit = idx($this->commits, $phid);
if (!$commit) {
return pht('(Unknown Commit)');
}
$summary = $commit->getCommitData()->getSummary();
if (strlen($summary)) {
return $summary;
}
// No summary, so either this is still importing or just has an empty
// commit message.
if (!$commit->isImported()) {
return pht('(Importing Commit...)');
} else {
return pht('(Untitled Commit)');
}
}
public function render() {
$list = $this->buildList();
$list->setFlush(true);
return $list->render();
}
public function buildList() {
$viewer = $this->getViewer();
$rowc = array();
$phids = array();
foreach ($this->getCommits() as $commit) {
$phids[] = $commit->getPHID();
foreach ($commit->getAudits() as $audit) {
$phids[] = $audit->getAuditorPHID();
}
$author_phid = $commit->getAuthorPHID();
if ($author_phid) {
$phids[] = $author_phid;
}
}
$handles = $viewer->loadHandles($phids);
$show_drafts = $this->getShowDrafts();
$draft_icon = id(new PHUIIconView())
->setIcon('fa-comment yellow')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Unsubmitted Comments'),
));
$list = new PHUIObjectItemListView();
foreach ($this->commits as $commit) {
$commit_phid = $commit->getPHID();
$commit_handle = $handles[$commit_phid];
$committed = null;
$commit_name = $commit_handle->getName();
$commit_link = $commit_handle->getURI();
$commit_desc = $this->getCommitDescription($commit_phid);
$committed = phabricator_datetime($commit->getEpoch(), $viewer);
$status = $commit->getAuditStatusObject();
$status_text = $status->getName();
$status_color = $status->getColor();
$status_icon = $status->getIcon();
$author_phid = $commit->getAuthorPHID();
if ($author_phid) {
$author_name = $handles[$author_phid]->renderLink();
} else {
$author_name = $commit->getCommitData()->getAuthorName();
}
$item = id(new PHUIObjectItemView())
->setObjectName($commit_name)
->setHeader($commit_desc)
->setHref($commit_link)
->setDisabled($commit->isUnreachable())
->addByline(pht('Author: %s', $author_name))
->addIcon('none', $committed);
if ($show_drafts) {
if ($commit->getHasDraft($viewer)) {
$item->addAttribute($draft_icon);
}
}
$audits = $commit->getAudits();
$auditor_phids = mpull($audits, 'getAuditorPHID');
if ($auditor_phids) {
$auditor_list = $handles->newSublist($auditor_phids)
->renderList()
->setAsInline(true);
} else {
$auditor_list = phutil_tag('em', array(), pht('None'));
}
$item->addAttribute(pht('Auditors: %s', $auditor_list));
if ($status_color) {
$item->setStatusIcon($status_icon.' '.$status_color, $status_text);
}
$list->addItem($item);
}
if ($this->noDataString) {
$list->setNoDataString($this->noDataString);
}
if ($this->header) {
$list->setHeader($this->header);
}
return $list;
}
}

View File

@@ -2,7 +2,7 @@
/** /**
* Abstract interface to an identity provider or authentication source, like * Abstract interface to an identity provider or authentication source, like
* Twitter, Facebook or Google. * Twitter, Facebook, or Google.
* *
* Generally, adapters are handed some set of credentials particular to the * Generally, adapters are handed some set of credentials particular to the
* provider they adapt, and they turn those credentials into standard * provider they adapt, and they turn those credentials into standard
@@ -17,13 +17,37 @@
*/ */
abstract class PhutilAuthAdapter extends Phobject { abstract class PhutilAuthAdapter extends Phobject {
final public function getAccountIdentifiers() {
$result = $this->newAccountIdentifiers();
assert_instances_of($result, 'PhabricatorExternalAccountIdentifier');
return $result;
}
protected function newAccountIdentifiers() {
$identifiers = array();
$raw_identifier = $this->getAccountID();
if ($raw_identifier !== null) {
$identifiers[] = $this->newAccountIdentifier($raw_identifier);
}
return $identifiers;
}
final protected function newAccountIdentifier($raw_identifier) {
return id(new PhabricatorExternalAccountIdentifier())
->setIdentifierRaw($raw_identifier);
}
/** /**
* Get a unique identifier associated with the identity. For most providers, * Get a unique identifier associated with the account.
* this is an account ID.
* *
* The account ID needs to be unique within this adapter's configuration, such * This identifier should be permanent, immutable, and uniquely identify
* that `<adapterKey, accountID>` is globally unique and always identifies the * the account. If possible, it should be nonsensitive. For providers that
* same identity. * have a GUID or PHID value for accounts, these are the best values to use.
*
* You can implement @{method:newAccountIdentifiers} instead if a provider
* is unable to emit identifiers with all of these properties.
* *
* If the adapter was unable to authenticate an identity, it should return * If the adapter was unable to authenticate an identity, it should return
* `null`. * `null`.
@@ -31,7 +55,9 @@ abstract class PhutilAuthAdapter extends Phobject {
* @return string|null Unique account identifier, or `null` if authentication * @return string|null Unique account identifier, or `null` if authentication
* failed. * failed.
*/ */
abstract public function getAccountID(); public function getAccountID() {
throw new PhutilMethodNotImplementedException();
}
/** /**

View File

@@ -51,13 +51,17 @@ final class PhutilGitHubAuthAdapter extends PhutilOAuthAuthAdapter {
protected function loadOAuthAccountData() { protected function loadOAuthAccountData() {
$uri = new PhutilURI('https://api.github.com/user'); $uri = new PhutilURI('https://api.github.com/user');
$uri->replaceQueryParam('access_token', $this->getAccessToken());
$future = new HTTPSFuture($uri); $future = new HTTPSFuture($uri);
// NOTE: GitHub requires a User-Agent string. // NOTE: GitHub requires a User-Agent string.
$future->addHeader('User-Agent', __CLASS__); $future->addHeader('User-Agent', __CLASS__);
// See T13485. Circa early 2020, GitHub has deprecated use of the
// "access_token" URI parameter.
$token_header = sprintf('token %s', $this->getAccessToken());
$future->addHeader('Authorization', $token_header);
list($body) = $future->resolvex(); list($body) = $future->resolvex();
try { try {

View File

@@ -13,8 +13,23 @@ final class PhutilGoogleAuthAdapter extends PhutilOAuthAuthAdapter {
return 'google.com'; return 'google.com';
} }
public function getAccountID() { protected function newAccountIdentifiers() {
return $this->getAccountEmail(); $identifiers = array();
$account_id = $this->getOAuthAccountData('id');
if ($account_id !== null) {
$account_id = sprintf(
'id(%s)',
$account_id);
$identifiers[] = $this->newAccountIdentifier($account_id);
}
$email = $this->getAccountEmail();
if ($email !== null) {
$identifiers[] = $this->newAccountIdentifier($email);
}
return $identifiers;
} }
public function getAccountEmail() { public function getAccountEmail() {

View File

@@ -10,7 +10,6 @@ final class PhutilJIRAAuthAdapter extends PhutilOAuth1AuthAdapter {
private $jiraBaseURI; private $jiraBaseURI;
private $adapterDomain; private $adapterDomain;
private $currentSession;
private $userInfo; private $userInfo;
public function setJIRABaseURI($jira_base_uri) { public function setJIRABaseURI($jira_base_uri) {
@@ -22,12 +21,33 @@ final class PhutilJIRAAuthAdapter extends PhutilOAuth1AuthAdapter {
return $this->jiraBaseURI; return $this->jiraBaseURI;
} }
public function getAccountID() { protected function newAccountIdentifiers() {
// Make sure the handshake is finished; this method is used for its // Make sure the handshake is finished; this method is used for its
// side effect by Auth providers. // side effect by Auth providers.
$this->getHandshakeData(); $this->getHandshakeData();
return idx($this->getUserInfo(), 'key'); $info = $this->getUserInfo();
// See T13493. Older versions of JIRA provide a "key" with a username or
// email address. Newer versions of JIRA provide a GUID "accountId".
// Intermediate versions of JIRA provide both.
$identifiers = array();
$account_key = idx($info, 'key');
if ($account_key !== null) {
$identifiers[] = $this->newAccountIdentifier($account_key);
}
$account_id = idx($info, 'accountId');
if ($account_id !== null) {
$identifiers[] = $this->newAccountIdentifier(
sprintf(
'accountId(%s)',
$account_id));
}
return $identifiers;
} }
public function getAccountName() { public function getAccountName() {
@@ -85,23 +105,36 @@ final class PhutilJIRAAuthAdapter extends PhutilOAuth1AuthAdapter {
private function getUserInfo() { private function getUserInfo() {
if ($this->userInfo === null) { if ($this->userInfo === null) {
$this->currentSession = $this->newJIRAFuture('rest/auth/1/session', 'GET') $this->userInfo = $this->newUserInfo();
->resolveJSON();
// The session call gives us the username, but not the user key or other
// information. Make a second call to get additional information.
$params = array(
'username' => $this->currentSession['name'],
);
$this->userInfo = $this->newJIRAFuture('rest/api/2/user', 'GET', $params)
->resolveJSON();
} }
return $this->userInfo; return $this->userInfo;
} }
private function newUserInfo() {
// See T13493. Try a relatively modern (circa early 2020) API call first.
try {
return $this->newJIRAFuture('rest/api/3/myself', 'GET')
->resolveJSON();
} catch (Exception $ex) {
// If we failed the v3 call, assume the server version is too old
// to support this API and fall back to trying the older method.
}
$session = $this->newJIRAFuture('rest/auth/1/session', 'GET')
->resolveJSON();
// The session call gives us the username, but not the user key or other
// information. Make a second call to get additional information.
$params = array(
'username' => $session['name'],
);
return $this->newJIRAFuture('rest/api/2/user', 'GET', $params)
->resolveJSON();
}
public static function newJIRAKeypair() { public static function newJIRAKeypair() {
$config = array( $config = array(
'digest_alg' => 'sha512', 'digest_alg' => 'sha512',

View File

@@ -156,7 +156,7 @@ abstract class PhutilOAuth1AuthAdapter extends PhutilAuthAdapter {
$authorize_token_uri = new PhutilURI($this->getAuthorizeTokenURI()); $authorize_token_uri = new PhutilURI($this->getAuthorizeTokenURI());
$authorize_token_uri->replaceQueryParam('oauth_token', $this->getToken()); $authorize_token_uri->replaceQueryParam('oauth_token', $this->getToken());
return (string)$authorize_token_uri; return phutil_string_cast($authorize_token_uri);
} }
protected function finishOAuthHandshake() { protected function finishOAuthHandshake() {

View File

@@ -73,7 +73,7 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
'session/downgrade/' 'session/downgrade/'
=> 'PhabricatorAuthDowngradeSessionController', => 'PhabricatorAuthDowngradeSessionController',
'enroll/' => array( 'enroll/' => array(
'(?:(?P<pageKey>[^/]+)/)?(?:(?P<formSaved>saved)/)?' '(?:(?P<pageKey>[^/]+)/)?'
=> 'PhabricatorAuthNeedsMultiFactorController', => 'PhabricatorAuthNeedsMultiFactorController',
), ),
'sshkey/' => array( 'sshkey/' => array(

View File

@@ -63,6 +63,13 @@ final class PhabricatorCookies extends Phobject {
const COOKIE_INVITE = 'invite'; const COOKIE_INVITE = 'invite';
/**
* Stores a workflow completion across a redirect-after-POST following a
* form submission. This can be used to show "Changes Saved" messages.
*/
const COOKIE_SUBMIT = 'phfrm';
/* -( Client ID Cookie )--------------------------------------------------- */ /* -( Client ID Cookie )--------------------------------------------------- */

View File

@@ -197,22 +197,6 @@ abstract class PhabricatorAuthController extends PhabricatorController {
return array($account, $provider, $response); return array($account, $provider, $response);
} }
$other_account = id(new PhabricatorExternalAccount())->loadAllWhere(
'accountType = %s AND accountDomain = %s AND accountID = %s
AND id != %d',
$account->getAccountType(),
$account->getAccountDomain(),
$account->getAccountID(),
$account->getID());
if ($other_account) {
$response = $this->renderError(
pht(
'The account you are attempting to register with already belongs '.
'to another user.'));
return array($account, $provider, $response);
}
$config = $account->getProviderConfig(); $config = $account->getProviderConfig();
if (!$config->getIsEnabled()) { if (!$config->getIsEnabled()) {
$response = $this->renderError( $response = $this->renderError(

View File

@@ -116,14 +116,21 @@ final class PhabricatorAuthLoginController
} }
} else { } else {
// If the user already has a linked account of this type, prevent them // If the user already has a linked account on this provider, prevent
// from linking a second account. This can happen if they swap logins // them from linking a second account. This can happen if they swap
// and then refresh the account link. See T6707. We will eventually // logins and then refresh the account link.
// allow this after T2549.
// There's no technical reason we can't allow you to link multiple
// accounts from a single provider; disallowing this is currently a
// product deciison. See T2549.
$existing_accounts = id(new PhabricatorExternalAccountQuery()) $existing_accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer) ->setViewer($viewer)
->withUserPHIDs(array($viewer->getPHID())) ->withUserPHIDs(array($viewer->getPHID()))
->withAccountTypes(array($account->getAccountType())) ->withProviderConfigPHIDs(
array(
$provider->getProviderConfigPHID(),
))
->execute(); ->execute();
if ($existing_accounts) { if ($existing_accounts) {
return $this->renderError( return $this->renderError(

View File

@@ -346,6 +346,14 @@ final class PhabricatorAuthRegisterController
} }
} }
// blender hack
$root = dirname(phutil_get_library_root('phabricator'));
require $root.'/migration/dedup.php';
if (array_key_exists($request->getStr('username'), $migrate_dedup_users)) {
$e_username = pht('Duplicate');
$errors[] = pht('Username is already reserved.');
}
if (!$errors) { if (!$errors) {
if (!$is_setup) { if (!$is_setup) {
$image = $this->loadProfilePicture($account); $image = $this->loadProfilePicture($account);
@@ -457,7 +465,6 @@ final class PhabricatorAuthRegisterController
if (!$is_setup) { if (!$is_setup) {
$account->setUserPHID($user->getPHID()); $account->setUserPHID($user->getPHID());
$provider->willRegisterAccount($account);
$account->save(); $account->save();
} }

View File

@@ -67,7 +67,7 @@ final class PhabricatorAuthUnlinkController
->setWorkflowKey($workflow_key) ->setWorkflowKey($workflow_key)
->requireHighSecurityToken($viewer, $request, $done_uri); ->requireHighSecurityToken($viewer, $request, $done_uri);
$account->delete(); $account->unlinkAccount();
id(new PhabricatorAuthSessionEngine())->terminateLoginSessions( id(new PhabricatorAuthSessionEngine())->terminateLoginSessions(
$viewer, $viewer,

View File

@@ -181,6 +181,12 @@ final class PhabricatorAuthPasswordEngine
$normal_password = phutil_utf8_strtolower($raw_password); $normal_password = phutil_utf8_strtolower($raw_password);
if (strlen($normal_password) >= $minimum_similarity) { if (strlen($normal_password) >= $minimum_similarity) {
foreach ($normal_map as $term => $source) { foreach ($normal_map as $term => $source) {
// See T2312. This may be required if the term list includes numeric
// strings like "12345", which will be cast to integers when used as
// array keys.
$term = phutil_string_cast($term);
if (strpos($term, $normal_password) === false && if (strpos($term, $normal_password) === false &&
strpos($normal_password, $term) === false) { strpos($normal_password, $term) === false) {
continue; continue;

View File

@@ -56,9 +56,12 @@ final class PhabricatorAuthManagementLDAPWorkflow
$console->writeOut("\n"); $console->writeOut("\n");
$console->writeOut("%s\n", pht('Connecting to LDAP...')); $console->writeOut("%s\n", pht('Connecting to LDAP...'));
$account_id = $adapter->getAccountID(); $account_ids = $adapter->getAccountIdentifiers();
if ($account_id) { if ($account_ids) {
$console->writeOut("%s\n", pht('Found LDAP Account: %s', $account_id)); $value_list = mpull($account_ids, 'getIdentifierRaw');
$value_list = implode(', ', $value_list);
$console->writeOut("%s\n", pht('Found LDAP Account: %s', $value_list));
} else { } else {
$console->writeOut("%s\n", pht('Unable to find LDAP account!')); $console->writeOut("%s\n", pht('Unable to find LDAP account!'));
} }

View File

@@ -18,16 +18,6 @@ final class PhabricatorAuthManagementRefreshWorkflow
'param' => 'user', 'param' => 'user',
'help' => pht('Refresh tokens for a given user.'), 'help' => pht('Refresh tokens for a given user.'),
), ),
array(
'name' => 'type',
'param' => 'provider',
'help' => pht('Refresh tokens for a given provider type.'),
),
array(
'name' => 'domain',
'param' => 'domain',
'help' => pht('Refresh tokens for a given domain.'),
),
)); ));
} }
@@ -57,17 +47,6 @@ final class PhabricatorAuthManagementRefreshWorkflow
} }
} }
$type = $args->getArg('type');
if (strlen($type)) {
$query->withAccountTypes(array($type));
}
$domain = $args->getArg('domain');
if (strlen($domain)) {
$query->withAccountDomains(array($domain));
}
$accounts = $query->execute(); $accounts = $query->execute();
if (!$accounts) { if (!$accounts) {
@@ -82,25 +61,24 @@ final class PhabricatorAuthManagementRefreshWorkflow
} }
$providers = PhabricatorAuthProvider::getAllEnabledProviders(); $providers = PhabricatorAuthProvider::getAllEnabledProviders();
$providers = mpull($providers, null, 'getProviderConfigPHID');
foreach ($accounts as $account) { foreach ($accounts as $account) {
$console->writeOut( $console->writeOut(
"%s\n", "%s\n",
pht( pht(
'Refreshing account #%d (%s/%s).', 'Refreshing account #%d.',
$account->getID(), $account->getID()));
$account->getAccountType(),
$account->getAccountDomain()));
$key = $account->getProviderKey(); $config_phid = $account->getProviderConfigPHID();
if (empty($providers[$key])) { if (empty($providers[$config_phid])) {
$console->writeOut( $console->writeOut(
"> %s\n", "> %s\n",
pht('Skipping, provider is not enabled or does not exist.')); pht('Skipping, provider is not enabled or does not exist.'));
continue; continue;
} }
$provider = $providers[$key]; $provider = $providers[$config_phid];
if (!($provider instanceof PhabricatorOAuth2AuthProvider)) { if (!($provider instanceof PhabricatorOAuth2AuthProvider)) {
$console->writeOut( $console->writeOut(
"> %s\n", "> %s\n",

View File

@@ -20,6 +20,10 @@ abstract class PhabricatorAuthProvider extends Phobject {
return $this->providerConfig; return $this->providerConfig;
} }
public function getProviderConfigPHID() {
return $this->getProviderConfig()->getPHID();
}
public function getConfigurationHelp() { public function getConfigurationHelp() {
return null; return null;
} }
@@ -186,44 +190,86 @@ abstract class PhabricatorAuthProvider extends Phobject {
return; return;
} }
public function willRegisterAccount(PhabricatorExternalAccount $account) { final protected function newExternalAccountForIdentifiers(
return; array $identifiers) {
assert_instances_of($identifiers, 'PhabricatorExternalAccountIdentifier');
if (!$identifiers) {
throw new Exception(
pht(
'Authentication provider (of class "%s") is attempting to '.
'load or create an external account, but provided no account '.
'identifiers.',
get_class($this)));
}
$config = $this->getProviderConfig();
$viewer = PhabricatorUser::getOmnipotentUser();
$raw_identifiers = mpull($identifiers, 'getIdentifierRaw');
$accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withProviderConfigPHIDs(array($config->getPHID()))
->withRawAccountIdentifiers($raw_identifiers)
->needAccountIdentifiers(true)
->execute();
if (!$accounts) {
$account = $this->newExternalAccount();
} else if (count($accounts) === 1) {
$account = head($accounts);
} else {
throw new Exception(
pht(
'Authentication provider (of class "%s") is attempting to load '.
'or create an external account, but provided a list of '.
'account identifiers which map to more than one account: %s.',
get_class($this),
implode(', ', $raw_identifiers)));
}
// See T13493. Add all the identifiers to the account. In the case where
// an account initially has a lower-quality identifier (like an email
// address) and later adds a higher-quality identifier (like a GUID), this
// allows us to automatically upgrade toward the higher-quality identifier
// and survive API changes which remove the lower-quality identifier more
// gracefully.
foreach ($identifiers as $identifier) {
$account->appendIdentifier($identifier);
}
return $this->didUpdateAccount($account);
} }
protected function loadOrCreateAccount($account_id) { final protected function newExternalAccountForUser(PhabricatorUser $user) {
if (!strlen($account_id)) { $config = $this->getProviderConfig();
throw new Exception(pht('Empty account ID!'));
}
$adapter = $this->getAdapter(); // When a user logs in with a provider like username/password, they
$adapter_class = get_class($adapter); // always already have a Phabricator account (since there's no way they
// could have a username otherwise).
if (!strlen($adapter->getAdapterType())) { // These users should never go to registration, so we're building a
throw new Exception( // dummy "external account" which just links directly back to their
pht( // internal account.
"AuthAdapter (of class '%s') has an invalid implementation: ".
"no adapter type.",
$adapter_class));
}
if (!strlen($adapter->getAdapterDomain())) { $account = id(new PhabricatorExternalAccountQuery())
throw new Exception( ->setViewer($user)
pht( ->withProviderConfigPHIDs(array($config->getPHID()))
"AuthAdapter (of class '%s') has an invalid implementation: ". ->withUserPHIDs(array($user->getPHID()))
"no adapter domain.", ->executeOne();
$adapter_class));
}
$account = id(new PhabricatorExternalAccount())->loadOneWhere(
'accountType = %s AND accountDomain = %s AND accountID = %s',
$adapter->getAdapterType(),
$adapter->getAdapterDomain(),
$account_id);
if (!$account) { if (!$account) {
$account = $this->newExternalAccount() $account = $this->newExternalAccount()
->setAccountID($account_id); ->setUserPHID($user->getPHID());
} }
return $this->didUpdateAccount($account);
}
private function didUpdateAccount(PhabricatorExternalAccount $account) {
$adapter = $this->getAdapter();
$account->setUsername($adapter->getAccountName()); $account->setUsername($adapter->getAccountName());
$account->setRealName($adapter->getAccountRealName()); $account->setRealName($adapter->getAccountRealName());
$account->setEmail($adapter->getAccountEmail()); $account->setEmail($adapter->getAccountEmail());
@@ -240,6 +286,7 @@ abstract class PhabricatorAuthProvider extends Phobject {
// file entry for it, but there's no convenient way to do this with // file entry for it, but there's no convenient way to do this with
// PhabricatorFile right now. The storage will get shared, so the impact // PhabricatorFile right now. The storage will get shared, so the impact
// here is negligible. // here is negligible.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$image_file = PhabricatorFile::newFromFileDownload( $image_file = PhabricatorFile::newFromFileDownload(
$image_uri, $image_uri,
@@ -305,10 +352,23 @@ abstract class PhabricatorAuthProvider extends Phobject {
$config = $this->getProviderConfig(); $config = $this->getProviderConfig();
$adapter = $this->getAdapter(); $adapter = $this->getAdapter();
return id(new PhabricatorExternalAccount()) $account = id(new PhabricatorExternalAccount())
->setProviderConfigPHID($config->getPHID())
->attachAccountIdentifiers(array());
// TODO: Remove this when these columns are removed. They no longer have
// readers or writers (other than this callsite).
$account
->setAccountType($adapter->getAdapterType()) ->setAccountType($adapter->getAdapterType())
->setAccountDomain($adapter->getAdapterDomain()) ->setAccountDomain($adapter->getAdapterDomain());
->setProviderConfigPHID($config->getPHID());
// TODO: Remove this when "accountID" is removed; the column is not
// nullable.
$account->setAccountID('');
return $account;
} }
public function getLoginOrder() { public function getLoginOrder() {

View File

@@ -335,7 +335,7 @@ final class PhabricatorJIRAAuthProvider
public function getDoorkeeperURIRef(PhutilURI $uri) { public function getDoorkeeperURIRef(PhutilURI $uri) {
$uri_string = phutil_string_cast($uri); $uri_string = phutil_string_cast($uri);
$pattern = '((https?://\S+?)/browse/([A-Z]+-[1-9]\d*))'; $pattern = '((https?://\S+?)/browse/([A-Z][A-Z0-9]*-[1-9]\d*))';
$matches = null; $matches = null;
if (!preg_match($pattern, $uri_string, $matches)) { if (!preg_match($pattern, $uri_string, $matches)) {
return null; return null;

View File

@@ -164,7 +164,7 @@ final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider {
// See T3351. // See T3351.
DarkConsoleErrorLogPluginAPI::enableDiscardMode(); DarkConsoleErrorLogPluginAPI::enableDiscardMode();
$account_id = $adapter->getAccountID(); $identifiers = $adapter->getAccountIdentifiers();
DarkConsoleErrorLogPluginAPI::disableDiscardMode(); DarkConsoleErrorLogPluginAPI::disableDiscardMode();
} else { } else {
throw new Exception(pht('Username and password are required!')); throw new Exception(pht('Username and password are required!'));
@@ -180,7 +180,9 @@ final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider {
} }
} }
return array($this->loadOrCreateAccount($account_id), $response); $account = $this->newExternalAccountForIdentifiers($identifiers);
return array($account, $response);
} }

View File

@@ -100,13 +100,13 @@ abstract class PhabricatorOAuth1AuthProvider
// an access token. // an access token.
try { try {
$account_id = $adapter->getAccountID(); $identifiers = $adapter->getAccountIdentifiers();
} catch (Exception $ex) { } catch (Exception $ex) {
// TODO: Handle this in a more user-friendly way. // TODO: Handle this in a more user-friendly way.
throw $ex; throw $ex;
} }
if (!strlen($account_id)) { if (!$identifiers) {
$response = $controller->buildProviderErrorResponse( $response = $controller->buildProviderErrorResponse(
$this, $this,
pht( pht(
@@ -115,7 +115,9 @@ abstract class PhabricatorOAuth1AuthProvider
return array($account, $response); return array($account, $response);
} }
return array($this->loadOrCreateAccount($account_id), $response); $account = $this->newExternalAccountForIdentifiers($identifiers);
return array($account, $response);
} }
public function processEditForm( public function processEditForm(

View File

@@ -80,13 +80,13 @@ abstract class PhabricatorOAuth2AuthProvider
// an access token. // an access token.
try { try {
$account_id = $adapter->getAccountID(); $identifiers = $adapter->getAccountIdentifiers();
} catch (Exception $ex) { } catch (Exception $ex) {
// TODO: Handle this in a more user-friendly way. // TODO: Handle this in a more user-friendly way.
throw $ex; throw $ex;
} }
if (!strlen($account_id)) { if (!$identifiers) {
$response = $controller->buildProviderErrorResponse( $response = $controller->buildProviderErrorResponse(
$this, $this,
pht( pht(
@@ -95,7 +95,9 @@ abstract class PhabricatorOAuth2AuthProvider
return array($account, $response); return array($account, $response);
} }
return array($this->loadOrCreateAccount($account_id), $response); $account = $this->newExternalAccountForIdentifiers($identifiers);
return array($account, $response);
} }
public function processEditForm( public function processEditForm(
@@ -197,7 +199,7 @@ abstract class PhabricatorOAuth2AuthProvider
PhabricatorExternalAccount $account, PhabricatorExternalAccount $account,
$force_refresh = false) { $force_refresh = false) {
if ($account->getProviderKey() !== $this->getProviderKey()) { if ($account->getProviderConfigPHID() !== $this->getProviderConfigPHID()) {
throw new Exception(pht('Account does not match provider!')); throw new Exception(pht('Account does not match provider!'));
} }

View File

@@ -173,8 +173,8 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
$dialog = id(new AphrontDialogView()) $dialog = id(new AphrontDialogView())
->setSubmitURI($this->getLoginURI()) ->setSubmitURI($this->getLoginURI())
->setUser($viewer) ->setUser($viewer)
->setTitle(pht('Log In')) ->setTitle(pht('Login to developer.blender.org'))
->addSubmitButton(pht('Log In')); ->addSubmitButton(pht('Login'));
if ($this->shouldAllowRegistration()) { if ($this->shouldAllowRegistration()) {
$dialog->addCancelButton( $dialog->addCancelButton(
@@ -182,6 +182,11 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
pht('Register New Account')); pht('Register New Account'));
} }
$webroot = dirname(phutil_get_library_root('phabricator')).'/webroot/';
$dialog->addFooter(
phutil_safe_html(
FileSystem::readFile($webroot .'rsrc/custom/static/login.html')));
$dialog->addFooter( $dialog->addFooter(
phutil_tag( phutil_tag(
'a', 'a',
@@ -217,6 +222,28 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
$errors[] = pht('Username or password are incorrect.'); $errors[] = pht('Username or password are incorrect.');
} }
if (true) {
// blender hack
$root = dirname(phutil_get_library_root('phabricator'));
require $root.'/migration/dedup.php';
$missing_username = $request->getStr('username');
$find_user = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$missing_username);
if (!$find_user && array_key_exists($missing_username, $migrate_dedup_users)) {
$errors = array();
$errors[] = pht('This account was merged into account "' .
$migrate_dedup_users[$missing_username] .
'", because Phabricator does not support multiple accounts with the same email address. ' .
'Please login with that account instead ' .
'(optionally recovering your password if you forgot it). ' .
'After logging in you will be able to change your username in the User Settings.');
}
}
if ($errors) { if ($errors) {
$errors = id(new PHUIInfoView())->setErrors($errors); $errors = id(new PHUIInfoView())->setErrors($errors);
} }
@@ -305,7 +332,7 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
->setObject($user); ->setObject($user);
if ($engine->isValidPassword($envelope)) { if ($engine->isValidPassword($envelope)) {
$account = $this->loadOrCreateAccount($user->getPHID()); $account = $this->newExternalAccountForUser($user);
$log_user = $user; $log_user = $user;
} }
} }
@@ -339,16 +366,6 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
return true; return true;
} }
protected function willSaveAccount(PhabricatorExternalAccount $account) {
parent::willSaveAccount($account);
$account->setUserPHID($account->getAccountID());
}
public function willRegisterAccount(PhabricatorExternalAccount $account) {
parent::willRegisterAccount($account);
$account->setAccountID($account->getUserPHID());
}
public static function getPasswordProvider() { public static function getPasswordProvider() {
$providers = self::getAllEnabledProviders(); $providers = self::getAllEnabledProviders();
@@ -375,4 +392,5 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
public function shouldAllowEmailTrustConfiguration() { public function shouldAllowEmailTrustConfiguration() {
return false; return false;
} }
} }

View File

@@ -0,0 +1,94 @@
<?php
final class PhabricatorExternalAccountIdentifierQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $providerConfigPHIDs;
private $externalAccountPHIDs;
private $rawIdentifiers;
public function withIDs($ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withProviderConfigPHIDs(array $phids) {
$this->providerConfigPHIDs = $phids;
return $this;
}
public function withExternalAccountPHIDs(array $phids) {
$this->externalAccountPHIDs = $phids;
return $this;
}
public function withRawIdentifiers(array $identifiers) {
$this->rawIdentifiers = $identifiers;
return $this;
}
public function newResultObject() {
return new PhabricatorExternalAccountIdentifier();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->providerConfigPHIDs !== null) {
$where[] = qsprintf(
$conn,
'providerConfigPHID IN (%Ls)',
$this->providerConfigPHIDs);
}
if ($this->externalAccountPHIDs !== null) {
$where[] = qsprintf(
$conn,
'externalAccountPHID IN (%Ls)',
$this->externalAccountPHIDs);
}
if ($this->rawIdentifiers !== null) {
$hashes = array();
foreach ($this->rawIdentifiers as $raw_identifier) {
$hashes[] = PhabricatorHash::digestForIndex($raw_identifier);
}
$where[] = qsprintf(
$conn,
'identifierHash IN (%Ls)',
$hashes);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorPeopleApplication';
}
}

View File

@@ -15,34 +15,18 @@ final class PhabricatorExternalAccountQuery
private $ids; private $ids;
private $phids; private $phids;
private $accountTypes;
private $accountDomains;
private $accountIDs;
private $userPHIDs; private $userPHIDs;
private $needImages; private $needImages;
private $accountSecrets; private $accountSecrets;
private $providerConfigPHIDs; private $providerConfigPHIDs;
private $needAccountIdentifiers;
private $rawAccountIdentifiers;
public function withUserPHIDs(array $user_phids) { public function withUserPHIDs(array $user_phids) {
$this->userPHIDs = $user_phids; $this->userPHIDs = $user_phids;
return $this; return $this;
} }
public function withAccountIDs(array $account_ids) {
$this->accountIDs = $account_ids;
return $this;
}
public function withAccountDomains(array $account_domains) {
$this->accountDomains = $account_domains;
return $this;
}
public function withAccountTypes(array $account_types) {
$this->accountTypes = $account_types;
return $this;
}
public function withPHIDs(array $phids) { public function withPHIDs(array $phids) {
$this->phids = $phids; $this->phids = $phids;
return $this; return $this;
@@ -63,11 +47,21 @@ final class PhabricatorExternalAccountQuery
return $this; return $this;
} }
public function needAccountIdentifiers($need) {
$this->needAccountIdentifiers = $need;
return $this;
}
public function withProviderConfigPHIDs(array $phids) { public function withProviderConfigPHIDs(array $phids) {
$this->providerConfigPHIDs = $phids; $this->providerConfigPHIDs = $phids;
return $this; return $this;
} }
public function withRawAccountIdentifiers(array $identifiers) {
$this->rawAccountIdentifiers = $identifiers;
return $this;
}
public function newResultObject() { public function newResultObject() {
return new PhabricatorExternalAccount(); return new PhabricatorExternalAccount();
} }
@@ -132,6 +126,23 @@ final class PhabricatorExternalAccountQuery
} }
} }
if ($this->needAccountIdentifiers) {
$account_phids = mpull($accounts, 'getPHID');
$identifiers = id(new PhabricatorExternalAccountIdentifierQuery())
->setViewer($viewer)
->setParentQuery($this)
->withExternalAccountPHIDs($account_phids)
->execute();
$identifiers = mgroup($identifiers, 'getExternalAccountPHID');
foreach ($accounts as $account) {
$account_phid = $account->getPHID();
$account_identifiers = idx($identifiers, $account_phid, array());
$account->attachAccountIdentifiers($account_identifiers);
}
}
return $accounts; return $accounts;
} }
@@ -141,62 +152,98 @@ final class PhabricatorExternalAccountQuery
if ($this->ids !== null) { if ($this->ids !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'id IN (%Ld)', 'account.id IN (%Ld)',
$this->ids); $this->ids);
} }
if ($this->phids !== null) { if ($this->phids !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'phid IN (%Ls)', 'account.phid IN (%Ls)',
$this->phids); $this->phids);
} }
if ($this->accountTypes !== null) {
$where[] = qsprintf(
$conn,
'accountType IN (%Ls)',
$this->accountTypes);
}
if ($this->accountDomains !== null) {
$where[] = qsprintf(
$conn,
'accountDomain IN (%Ls)',
$this->accountDomains);
}
if ($this->accountIDs !== null) {
$where[] = qsprintf(
$conn,
'accountID IN (%Ls)',
$this->accountIDs);
}
if ($this->userPHIDs !== null) { if ($this->userPHIDs !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'userPHID IN (%Ls)', 'account.userPHID IN (%Ls)',
$this->userPHIDs); $this->userPHIDs);
} }
if ($this->accountSecrets !== null) { if ($this->accountSecrets !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'accountSecret IN (%Ls)', 'account.accountSecret IN (%Ls)',
$this->accountSecrets); $this->accountSecrets);
} }
if ($this->providerConfigPHIDs !== null) { if ($this->providerConfigPHIDs !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'providerConfigPHID IN (%Ls)', 'account.providerConfigPHID IN (%Ls)',
$this->providerConfigPHIDs); $this->providerConfigPHIDs);
// If we have a list of ProviderConfig PHIDs and are joining the
// identifiers table, also include the list as an additional constraint
// on the identifiers table.
// This does not change the query results (an Account and its
// Identifiers always have the same ProviderConfig PHID) but it allows
// us to use keys on the Identifier table more efficiently.
if ($this->shouldJoinIdentifiersTable()) {
$where[] = qsprintf(
$conn,
'identifier.providerConfigPHID IN (%Ls)',
$this->providerConfigPHIDs);
}
}
if ($this->rawAccountIdentifiers !== null) {
$hashes = array();
foreach ($this->rawAccountIdentifiers as $raw_identifier) {
$hashes[] = PhabricatorHash::digestForIndex($raw_identifier);
}
$where[] = qsprintf(
$conn,
'identifier.identifierHash IN (%Ls)',
$hashes);
} }
return $where; return $where;
} }
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinIdentifiersTable()) {
$joins[] = qsprintf(
$conn,
'JOIN %R identifier ON account.phid = identifier.externalAccountPHID',
new PhabricatorExternalAccountIdentifier());
}
return $joins;
}
protected function shouldJoinIdentifiersTable() {
return ($this->rawAccountIdentifiers !== null);
}
protected function shouldGroupQueryResultRows() {
if ($this->shouldJoinIdentifiersTable()) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
protected function getPrimaryTableAlias() {
return 'account';
}
public function getQueryApplicationClass() { public function getQueryApplicationClass() {
return 'PhabricatorPeopleApplication'; return 'PhabricatorPeopleApplication';
} }

View File

@@ -80,7 +80,7 @@ final class PhabricatorAuthSSHPrivateKey extends Phobject {
if (!$err) { if (!$err) {
if ($passphrase) { if ($passphrase) {
execx( execx(
'ssh-keygen -y -P %P -N %s -f %R', 'ssh-keygen -p -P %P -N %s -f %R',
$passphrase, $passphrase,
'', '',
$tmp); $tmp);

View File

@@ -125,7 +125,13 @@ final class PhabricatorAuthPassword
$hash = $hasher->getPasswordHashForStorage($digest); $hash = $hasher->getPasswordHashForStorage($digest);
$raw_hash = $hash->openEnvelope(); $raw_hash = $hash->openEnvelope();
return $this->setPasswordHash($raw_hash); $result = $this->setPasswordHash($raw_hash);
if ($result) {
if ($object instanceof PhabricatorUser) {
$object->updateHtaccessPassword($password);
}
}
return $result;
} }
public function comparePassword( public function comparePassword(

View File

@@ -4,7 +4,8 @@ final class PhabricatorAuthProviderConfig
extends PhabricatorAuthDAO extends PhabricatorAuthDAO
implements implements
PhabricatorApplicationTransactionInterface, PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface { PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
protected $providerClass; protected $providerClass;
protected $providerType; protected $providerType;
@@ -140,4 +141,33 @@ final class PhabricatorAuthProviderConfig
return false; return false;
} }
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$viewer = $engine->getViewer();
$config_phid = $this->getPHID();
$accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withProviderConfigPHIDs(array($config_phid))
->newIterator();
foreach ($accounts as $account) {
$engine->destroyObject($account);
}
$identifiers = id(new PhabricatorExternalAccountIdentifierQuery())
->setViewer($viewer)
->withProviderConfigPHIDs(array($config_phid))
->newIterator();
foreach ($identifiers as $identifier) {
$engine->destroyObject($identifier);
}
$this->delete();
}
} }

View File

@@ -37,8 +37,6 @@ final class PhabricatorAuthAccountView extends AphrontView {
$use_name = $username; $use_name = $username;
} else if (strlen($realname)) { } else if (strlen($realname)) {
$use_name = $realname; $use_name = $realname;
} else {
$use_name = $account->getAccountID();
} }
$content[] = phutil_tag( $content[] = phutil_tag(
@@ -61,8 +59,6 @@ final class PhabricatorAuthAccountView extends AphrontView {
), ),
array( array(
$prov_name, $prov_name,
" \xC2\xB7 ",
$account->getAccountID(),
)); ));
$account_uri = $account->getAccountURI(); $account_uri = $account->getAccountURI();

View File

@@ -849,8 +849,8 @@ final class PhutilICSParser extends Phobject {
); );
// Load the map of Windows timezones. // Load the map of Windows timezones.
$root_path = dirname(phutil_get_library_root('phutil')); $root_path = dirname(phutil_get_library_root('phabricator'));
$windows_path = $root_path.'/resources/timezones/windows_timezones.json'; $windows_path = $root_path.'/resources/timezones/windows-timezones.json';
$windows_data = Filesystem::readFile($windows_path); $windows_data = Filesystem::readFile($windows_path);
$windows_zones = phutil_json_decode($windows_data); $windows_zones = phutil_json_decode($windows_data);

View File

@@ -82,6 +82,7 @@ final class CelerityDefaultPostprocessor
'alphablack' => '0,0,0', 'alphablack' => '0,0,0',
// Base Greys // Base Greys
'thingreyborder' => '#dadee8',
'lightgreyborder' => '#C7CCD9', 'lightgreyborder' => '#C7CCD9',
'greyborder' => '#A1A6B0', 'greyborder' => '#A1A6B0',
'darkgreyborder' => '#676A70', 'darkgreyborder' => '#676A70',
@@ -207,6 +208,9 @@ final class CelerityDefaultPostprocessor
// Usually light yellow // Usually light yellow
'gentle.highlight' => '#fdf3da', 'gentle.highlight' => '#fdf3da',
'gentle.highlight.border' => '#c9b8a8', 'gentle.highlight.border' => '#c9b8a8',
'gentle.highlight.background' => '#fffdf6',
'highlight.bright' => '#fdf320',
'paste.content' => '#fffef5', 'paste.content' => '#fffef5',
'paste.border' => '#e9dbcd', 'paste.border' => '#e9dbcd',
@@ -240,6 +244,9 @@ final class CelerityDefaultPostprocessor
'document.border' => '#dedee1', 'document.border' => '#dedee1',
'delete-color' => '#c0392b',
'create-color' => '#139543',
); );
} }

View File

@@ -17,6 +17,9 @@ final class CelerityRedGreenPostprocessor
'new-bright' => 'rgba(152, 207, 235, .35)', 'new-bright' => 'rgba(152, 207, 235, .35)',
'old-background' => 'rgba(250, 212, 175, .3)', 'old-background' => 'rgba(250, 212, 175, .3)',
'old-bright' => 'rgba(250, 212, 175, .55)', 'old-bright' => 'rgba(250, 212, 175, .55)',
'delete-color' => '#e67e22',
'create-color' => '#2980b9',
); );
} }

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