From d1421bc3a1ee464b30d826c13d3ccb763394a8df Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 8 Apr 2017 10:23:30 -0700 Subject: [PATCH 01/52] Add "bin/storage optimize" to run OPTIMIZE TABLE on everything Summary: Even with `innodb_file_per_table` enabled, individual table files on disk don't normally shrink. For most tables, like `maniphest_task`, this is fine, since the data in the table normally never shrinks, or only shinks a tiny amount. However, some tables (like the "worker" and "daemon" tables) grow very large during a huge import but most of the data is later deleted by garbage collection. In these cases, this lost space can be reclaimed by running `OPTIMIZE TABLE` on the tables. Add a script to `OPTIMIZE TABLE` every table. My primary goal here is just to reduce storage pressure on `db001` since there are a couple of "import the linux kernel" installs on that host wasting a bunch of space. We're not in any trouble, but this should buy us a good chunk of headroom. Test Plan: Ran `bin/storage optimize` locally and manually ran `OPTIMIZE TABLE` in production, saw tables get optimized. Reviewers: chad Reviewed By: chad Subscribers: cspeckmim Differential Revision: https://secure.phabricator.com/D17640 --- src/__phutil_library_map__.php | 2 + ...catorStorageManagementOptimizeWorkflow.php | 86 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 src/infrastructure/storage/management/workflow/PhabricatorStorageManagementOptimizeWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b567e5a9e9..279e9d6c44 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3940,6 +3940,7 @@ phutil_register_library_map(array( 'PhabricatorStorageManagementDatabasesWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php', 'PhabricatorStorageManagementDestroyWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php', 'PhabricatorStorageManagementDumpWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php', + 'PhabricatorStorageManagementOptimizeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementOptimizeWorkflow.php', 'PhabricatorStorageManagementPartitionWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementPartitionWorkflow.php', 'PhabricatorStorageManagementProbeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php', 'PhabricatorStorageManagementQuickstartWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php', @@ -9286,6 +9287,7 @@ phutil_register_library_map(array( 'PhabricatorStorageManagementDatabasesWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementDestroyWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementDumpWorkflow' => 'PhabricatorStorageManagementWorkflow', + 'PhabricatorStorageManagementOptimizeWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementPartitionWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementProbeWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementQuickstartWorkflow' => 'PhabricatorStorageManagementWorkflow', diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementOptimizeWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementOptimizeWorkflow.php new file mode 100644 index 0000000000..5c1c49b0b2 --- /dev/null +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementOptimizeWorkflow.php @@ -0,0 +1,86 @@ +setName('optimize') + ->setExamples('**optimize**') + ->setSynopsis(pht('Run "OPTIMIZE TABLE" on tables to reclaim space.')); + } + + public function didExecute(PhutilArgumentParser $args) { + $api = $this->getSingleAPI(); + $conn = $api->getConn(null); + + $patches = $this->getPatches(); + $databases = $api->getDatabaseList($patches, true); + + $total_bytes = 0; + foreach ($databases as $database) { + queryfx($conn, 'USE %C', $database); + + $tables = queryfx_all($conn, 'SHOW TABLE STATUS'); + foreach ($tables as $table) { + $table_name = $table['Name']; + $old_bytes = + $table['Data_length'] + + $table['Index_length'] + + $table['Data_free']; + + $this->logInfo( + pht('OPTIMIZE'), + pht( + 'Optimizing table "%s"."%s"...', + $database, + $table_name)); + + $t_start = microtime(true); + queryfx( + $conn, + 'OPTIMIZE TABLE %T', + $table_name); + $t_end = microtime(true); + + $status = queryfx_one( + $conn, + 'SHOW TABLE STATUS LIKE %s', + $table_name); + + $new_bytes = + $status['Data_length'] + + $status['Index_length'] + + $status['Data_free']; + + $duration_ms = (int)(1000 * ($t_end - $t_start)); + + if ($old_bytes > $new_bytes) { + $this->logOkay( + pht('DONE'), + pht( + 'Compacted table by %s in %sms.', + phutil_format_bytes($old_bytes - $new_bytes), + new PhutilNumber($duration_ms))); + } else { + $this->logInfo( + pht('DONE'), + pht( + 'Optimized table (in %sms) but it had little effect.', + new PhutilNumber($duration_ms))); + } + + $total_bytes += ($old_bytes - $new_bytes); + } + } + + $this->logOkay( + pht('OPTIMIZED'), + pht( + 'Completed optimizations, reclaimed %s of disk space.', + phutil_format_bytes($total_bytes))); + + return 0; + } + +} From c9f51fd4052361092bf0a2baf3dce30f4310836b Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 9 Apr 2017 04:55:13 -0700 Subject: [PATCH 02/52] Write a "Developer Setup" guide for onboarding Summary: Fixes T11561. Collect guidance about local configuration which hasn't been obvious in the past. Test Plan: - Read document carefully. - Used `./bin/diviner generate` to generate documentation. - Previewed in Diviner locally: {F4795021} Reviewers: amckinley, chad Reviewed By: chad Subscribers: cspeckmim Maniphest Tasks: T11561 Differential Revision: https://secure.phabricator.com/D17641 --- src/docs/contributor/developer_setup.diviner | 112 +++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/docs/contributor/developer_setup.diviner diff --git a/src/docs/contributor/developer_setup.diviner b/src/docs/contributor/developer_setup.diviner new file mode 100644 index 0000000000..95508ccd19 --- /dev/null +++ b/src/docs/contributor/developer_setup.diviner @@ -0,0 +1,112 @@ +@title Developer Setup +@group developer + +How to configure a Phabricator development environment. + +Overview +======== + +There are some options and workflows that may be useful if you are developing +or debugging Phabricator. + + +Configuration +============= + +To adjust Phabricator for development: + + - Enable `phabricator.developer-mode` to enable some options and show + more debugging information. + - Enable `phabricator.show-prototypes` to show all the incomplete + applications. + - See @{article: Using DarkConsole} for instructions on enabling the + debugging console. + + +Error Handling +============== + +Errors normally go to DarkConsole (if enabled) and the webserver error log, +which is often located somewhere like `/var/log/apache/error_log`. This file +often contains relevant information after you encounter an error. + +When debugging, you can print information to the error log with `phlog(...)`. +You can `phlog(new Exception(...))` to get a stack trace. + +You can print information to the UI with `throw new Exception(...)`, +`print_r(...)`, or `var_dump(...)`. + +You can abort execution with `die(...)` if you want to make sure execution +does not make it past some point. Normally `throw` does this too, but callers +can `catch` exceptions; they can not catch `die(...)`. + + +Utilities +========= + +After adding, renaming, or moving classes, run `arc liberate` to rebuild +the class map: + +``` +phabricator/ $ arc liberate +``` + +Until you do this, Phabricator won't recognize your new, moved, or renamed +classes. You do not need to run this after modifying an existing class. + +After any modifications to static resources (CSS / JS) but before sending +changes for review or pushing them to the remote, run `bin/celerity map`: + +``` +phabricator/ $ ./bin/celerity map +``` + +This rebuilds the static resource map. + +If you forget to run these commands you'll normally be warned by unit tests, +but knowing about them may prevent confusion before you hit the warnings. + + +Command Line +============ + +Almost every script supports a `--trace` flag, which prints out service +calls and more detailed error information. This is often the best way to get +started with debugging command-line scripts. + + +Performance +=========== + +Although it is more user-focused than developer-focused, the +@{article:Troubleshooting Performance Problems} guide has useful information +on the tools available for diagnosing and understanding performance problems. + + +Custom Domains +============== + +If you're working with applications that support custom domains (like Phurl or +Phame) you can normally test them by adding more entries to your webserver +configuration that look exactly like the primary entry (or expanding the +primary entry to match more domains). + +Phabricator routes all requests based on host headers, so alternate domains +do not normally need any kind of special configuration. + +You may also need to add `/etc/hosts` entries for the domains themselves. + + +Creating Test Data +================== + +You can create test objects with the "Lipsum" utility: + +``` +phabricator/ $ ./bin/lipsum help generate +phabricator/ $ ./bin/lipsum generate ... +``` + +Test data can make your local install feel a little more realistic. With +`--quickly`, you can generate a large amount of test data to help test issues +with performance or scale. From d4d46501ed1b2e588746ba55b0b5cf27275b86fa Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 10 Apr 2017 04:35:34 -0700 Subject: [PATCH 03/52] Add a repair migration to fix legacy Calendar events without "utcInstanceEpoch" Summary: Fixes T12488. Some events appear to have survived earlier migrations without getting completely fixed. Fix them. Test Plan: - Ran migration locally with `bin/storage upgrade` (but: I could not reproduce this problem locally). - Ran migration in production and saw ICS import stop fataling. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12488 Differential Revision: https://secure.phabricator.com/D17642 --- .../20170410.calendar.01.repair.php | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 resources/sql/autopatches/20170410.calendar.01.repair.php diff --git a/resources/sql/autopatches/20170410.calendar.01.repair.php b/resources/sql/autopatches/20170410.calendar.01.repair.php new file mode 100644 index 0000000000..7d0000e581 --- /dev/null +++ b/resources/sql/autopatches/20170410.calendar.01.repair.php @@ -0,0 +1,42 @@ +establishConnection('w'); +$table_name = $table->getTableName(); + +$viewer = PhabricatorUser::getOmnipotentUser(); +$all_events = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->execute(); +foreach ($all_events as $event) { + $id = $event->getID(); + + if (!$event->getInstanceOfEventPHID()) { + // Not a child event, so no instance epoch. + continue; + } + + if ($event->getUTCInstanceEpoch()) { + // Already has an instance epoch. + continue; + } + + try { + $event->updateUTCEpochs(); + } catch (Exception $ex) { + phlog($ex); + continue; + } + + queryfx( + $conn, + 'UPDATE %T SET utcInstanceEpoch = %nd WHERE id = %d', + $table_name, + $event->getUTCInstanceEpoch(), + $id); +} From ab06a9681c263bfd17cd8f993bba7fcf65080b5b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 10 Apr 2017 04:56:03 -0700 Subject: [PATCH 04/52] Fix two issues with user Calendar event availability cache display Summary: Ref T11816. Two minor issues: - We used `$event`, not `$next_event`, as the event providing the PHID for "Busy at ". This rendered "Busy at " on the profile instead of "Busy at " correctly. - In DarkConsole "Services" tab, no longer saw unnecessary cache refills while attending an event. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11816 Differential Revision: https://secure.phabricator.com/D17643 --- .../calendar/storage/PhabricatorCalendarEvent.php | 4 ++++ src/applications/people/query/PhabricatorPeopleQuery.php | 6 +++--- src/applications/people/storage/PhabricatorUser.php | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index e566a77b76..3a09d2e021 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -391,6 +391,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return ($epoch - $window); } + public function getEndDateTimeEpochForCache() { + return $this->getEndDateTimeEpoch(); + } + protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index 7367382e50..e756a9a4fd 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -465,7 +465,7 @@ final class PhabricatorPeopleQuery while (true) { foreach ($events as $event) { $from = $event->getStartDateTimeEpochForCache(); - $to = $event->getEndDateTimeEpoch(); + $to = $event->getEndDateTimeEpochForCache(); if (($from <= $cursor) && ($to > $cursor)) { $cursor = $to; if (!$next_event) { @@ -483,7 +483,7 @@ final class PhabricatorPeopleQuery $availability_type = $invitee->getDisplayAvailability($next_event); $availability = array( 'until' => $cursor, - 'eventPHID' => $event->getPHID(), + 'eventPHID' => $next_event->getPHID(), 'availability' => $availability_type, ); @@ -496,7 +496,7 @@ final class PhabricatorPeopleQuery // simultaneously we should accommodate that. However, it's complex // to compute, rare, and probably not confusing most of the time. - $availability_ttl = $next_event->getStartDateTimeEpochForCache(); + $availability_ttl = $next_event->getEndDateTimeEpochForCache(); } else { $availability = array( 'until' => null, diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index 4a35d9956c..2a1c1394a6 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -1082,7 +1082,7 @@ final class PhabricatorUser 'UPDATE %T SET availabilityCache = %s, availabilityCacheTTL = %nd WHERE id = %d', $this->getTableName(), - json_encode($availability), + phutil_json_encode($availability), $ttl, $this->getID()); unset($unguarded); From 50e809e06f4b19f6dec980c1403487724fd9a64b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 10 Apr 2017 06:29:08 -0700 Subject: [PATCH 05/52] Fix an issue where recurring ghost events could go missing if queried with a limit Summary: Ref T11816. Depends on D17644. When you executed a query like "upcoming, limit 5 events" you might match some recurring events starting from, say, a year ago and repeating every month. We'd then generate the first 5 ghosts for these events (say, last January, February, ... May) and later throw them out, so the correct events in the query window (say, this April) would never get generated. Instead, generate ghosts beginning with the start of the window. The fix in D17644 to number results correctly allows us to do this. Test Plan: - Made a query panel showing 5 events, scheduled an event long in the past, did not visit any of the instances of it so they didn't generate concrete objects. - Before the patch, near-future instances failed to show; after the patch, they show. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11816 Differential Revision: https://secure.phabricator.com/D17645 --- .../calendar/query/PhabricatorCalendarEventQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index bf1d9b43e2..9b7189cfdf 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -226,7 +226,7 @@ final class PhabricatorCalendarEventQuery $set = $event->newRecurrenceSet(); $recurrences = $set->getEventsBetween( - null, + $start_date, $end_date, $limit + 1); From 00a1dec7a6dc5394ce64dbba80d872b820c9c0fc Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 10 Apr 2017 07:44:58 -0700 Subject: [PATCH 06/52] Render timezones in event reminder mail, and render them more nicely Summary: Fixes T12356. - In this mail, we currently render "6:00 AM". Instead, render "6:00 AM (PDT)" or similar. This is consistent with times in other modern Transaction mail. - Previously, we would render "UTC-7". Render "PDT" instead. For obscure zones with no known timezone abbreviation, fall back to "UTC-7". Test Plan: - Used `bin/calendar notify --minutes X` to trigger notifications, read email bodies. - Used this script to list all `T` values and checked them for sanity: ```lang=php setTimeZone($zone); printf( "%s (%s)\n", $locale, $now->format('T')); } ``` Reviewers: chad Reviewed By: chad Maniphest Tasks: T12356 Differential Revision: https://secure.phabricator.com/D17646 --- src/__phutil_library_map__.php | 1 + ...habricatorCalendarEventNotificationView.php | 8 ++++++++ .../PhabricatorCalendarNotificationEngine.php | 2 +- .../PhabricatorModularTransactionType.php | 15 ++++----------- src/view/viewutils.php | 18 ++++++++++++++++++ 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 279e9d6c44..60bbb25b86 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4692,6 +4692,7 @@ phutil_register_library_map(array( 'javelin_tag' => 'infrastructure/javelin/markup.php', 'phabricator_date' => 'view/viewutils.php', 'phabricator_datetime' => 'view/viewutils.php', + 'phabricator_datetimezone' => 'view/viewutils.php', 'phabricator_form' => 'infrastructure/javelin/markup.php', 'phabricator_format_local_time' => 'view/viewutils.php', 'phabricator_relative_date' => 'view/viewutils.php', diff --git a/src/applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php b/src/applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php index 7568f7d8dc..8c619c7fb2 100644 --- a/src/applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php +++ b/src/applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php @@ -58,4 +58,12 @@ final class PhabricatorCalendarEventNotificationView return phabricator_datetime($epoch, $viewer); } + public function getDisplayTimeWithTimezone() { + $viewer = $this->getViewer(); + + $epoch = $this->getEpoch(); + return phabricator_datetimezone($epoch, $viewer); + } + + } diff --git a/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php b/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php index b0a762b577..4ae007fc0c 100644 --- a/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php +++ b/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php @@ -268,7 +268,7 @@ final class PhabricatorCalendarNotificationEngine '%s is starting in %s minute(s), at %s.', $event->getEvent()->getName(), $event->getDisplayMinutes(), - $event->getDisplayTime())); + $event->getDisplayTimeWithTimezone())); $body->addLinkSection( pht('EVENT DETAIL'), diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index 55ad678539..a315a5b9cf 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -234,22 +234,15 @@ abstract class PhabricatorModularTransactionType if ($all_day) { $display = phabricator_date($epoch, $viewer); - } else { - $display = phabricator_datetime($epoch, $viewer); - + } else if ($this->isRenderingTargetExternal()) { // When rendering to text, we explicitly render the offset from UTC to // provide context to the date: the mail may be generating with the // server's settings, or the user may later refer back to it after // changing timezones. - if ($this->isRenderingTargetExternal()) { - $offset = $viewer->getTimeZoneOffsetInHours(); - if ($offset >= 0) { - $display = pht('%s (UTC+%d)', $display, $offset); - } else { - $display = pht('%s (UTC-%d)', $display, abs($offset)); - } - } + $display = phabricator_datetimezone($epoch, $viewer); + } else { + $display = phabricator_datetime($epoch, $viewer); } return $this->renderValue($display); diff --git a/src/view/viewutils.php b/src/view/viewutils.php index e0629800c7..7eee3e03fb 100644 --- a/src/view/viewutils.php +++ b/src/view/viewutils.php @@ -48,6 +48,24 @@ function phabricator_datetime($epoch, $user) { $user->getUserSetting($time_key))); } +function phabricator_datetimezone($epoch, $user) { + $datetime = phabricator_datetime($epoch, $user); + $timezone = phabricator_format_local_time($epoch, $user, 'T'); + + // Some obscure timezones just render as "+03" or "-09". Make these render + // as "UTC+3" instead. + if (preg_match('/^[+-]/', $timezone)) { + $timezone = (int)trim($timezone, '+'); + if ($timezone < 0) { + $timezone = pht('UTC-%s', $timezone); + } else { + $timezone = pht('UTC+%s', $timezone); + } + } + + return pht('%s (%s)', $datetime, $timezone); +} + /** * This function does not usually need to be called directly. Instead, call * @{function:phabricator_date}, @{function:phabricator_time}, or From dee9c33be260898a3cf72abaf6b01c6b70e2785a Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 10 Apr 2017 12:22:25 -0700 Subject: [PATCH 07/52] Suggest use of "usermod" rather than manually editing critical files in /etc Summary: Fixes T12529. Test Plan: O_O Reviewers: chad Reviewed By: chad Maniphest Tasks: T12529 Differential Revision: https://secure.phabricator.com/D17648 --- .../user/userguide/diffusion_hosting.diviner | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/docs/user/userguide/diffusion_hosting.diviner b/src/docs/user/userguide/diffusion_hosting.diviner index 1a9e7d5bdb..2443b475e8 100644 --- a/src/docs/user/userguide/diffusion_hosting.diviner +++ b/src/docs/user/userguide/diffusion_hosting.diviner @@ -175,15 +175,32 @@ If you're planning to use SSH, you should also edit `/etc/passwd` and account. The second field (which is the password field) must not be set to `!!`. This -value will prevent login. If it is set to `!!`, edit it and set it to `NP` ("no -password") instead. +value will prevent login. + +If you have `usermod` on your system, you can adjust this value with: + +``` +$ sudo usermod -p NP vcs-user +``` + +If you do not have `usermod`, carefully edit the file and set the field value +to `NP` ("no password") instead of `!!`. **`/etc/passwd`**: Open `/etc/passwd` and find the line for the `vcs-user` account. The last field (which is the login shell) must be set to a real shell. If it is set to something like `/bin/false`, then `sshd` will not be able to execute -commands. Instead, you should set it to a real shell, like `/bin/sh`. +commands. + +If you have `usermod` on your system, you can adjust this value with: + +``` +$ sudo usermod -s /bin/sh vcs-user +``` + +If you do not have `usermod`, carefully edit the file and change the field +to point at a real shell, usually `/bin/sh`. Configuring HTTP From 4a8495495738cbdffe3b15e2292334c77add6cdc Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 10 Apr 2017 14:39:36 -0700 Subject: [PATCH 08/52] Prevent Send on Enter in Fullscreen Remarkup Mode Summary: Fixes T12138. Test for the presence of being in fullscreen mode, and disable send on enter if present. Side note, I'd love a first class "hasClass" type Javelin function. Test Plan: - Go to Conpherence - Type some smack, see it send on enter - Go fullscreen like a boss - Let the words flow - Close fullscreen, then send on enter. - (might be nice someday to add a "submit" button to fullscreen editor) Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12138 Differential Revision: https://secure.phabricator.com/D17590 --- resources/celerity/map.php | 50 +++++++++---------- .../controller/ConpherenceViewController.php | 1 + .../control/PhabricatorRemarkupControl.php | 11 ++++ .../conpherence/behavior-pontificate.js | 23 --------- .../behavior-phabricator-remarkup-assist.js | 44 +++++++++++++--- 5 files changed, 75 insertions(+), 54 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index f45c16ec69..e9b50dc81e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,9 +8,9 @@ return array( 'names' => array( 'conpherence.pkg.css' => '82aca405', - 'conpherence.pkg.js' => '6249a1cf', + 'conpherence.pkg.js' => '281b1a73', 'core.pkg.css' => '1bf8fa70', - 'core.pkg.js' => '021685f1', + 'core.pkg.js' => 'fbc1c380', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', @@ -375,7 +375,7 @@ return array( 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'aa3bd034', 'rsrc/js/application/conpherence/behavior-menu.js' => '7524fcfa', 'rsrc/js/application/conpherence/behavior-participant-pane.js' => '8604caa8', - 'rsrc/js/application/conpherence/behavior-pontificate.js' => 'f2e58483', + 'rsrc/js/application/conpherence/behavior-pontificate.js' => '55616e04', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', 'rsrc/js/application/conpherence/behavior-toggle-widget.js' => '3dbf94d5', 'rsrc/js/application/countdown/timer.js' => 'e4cc26b3', @@ -503,7 +503,7 @@ return array( 'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 'rsrc/js/core/behavior-phabricator-nav.js' => '08675c6d', - 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'a0777ea3', + 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '0ca788bd', 'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207', 'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b', 'rsrc/js/core/behavior-remarkup-preview.js' => '4b700e9e', @@ -600,7 +600,7 @@ return array( 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-menu' => '7524fcfa', 'javelin-behavior-conpherence-participant-pane' => '8604caa8', - 'javelin-behavior-conpherence-pontificate' => 'f2e58483', + 'javelin-behavior-conpherence-pontificate' => '55616e04', 'javelin-behavior-conpherence-search' => '9bbf3762', 'javelin-behavior-countdown-timer' => 'e4cc26b3', 'javelin-behavior-dark-console' => 'f411b6ae', @@ -664,7 +664,7 @@ return array( 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => 'e0ec7f2f', 'javelin-behavior-phabricator-oncopy' => '2926fff2', - 'javelin-behavior-phabricator-remarkup-assist' => 'a0777ea3', + 'javelin-behavior-phabricator-remarkup-assist' => '0ca788bd', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => '06c32383', 'javelin-behavior-phabricator-show-older-transactions' => '94c65b72', @@ -988,6 +988,17 @@ return array( 'javelin-dom', 'javelin-router', ), + '0ca788bd' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phabricator-phtize', + 'phabricator-textareautils', + 'javelin-workflow', + 'javelin-vector', + 'phuix-autocomplete', + 'javelin-mask', + ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -1298,6 +1309,14 @@ return array( 'javelin-request', 'javelin-typeahead-source', ), + '55616e04' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-workflow', + 'javelin-stratcom', + 'conpherence-thread-manager', + ), '558829c2' => array( 'javelin-stratcom', 'javelin-behavior', @@ -1686,17 +1705,6 @@ return array( 'javelin-dom', 'javelin-vector', ), - 'a0777ea3' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'phabricator-phtize', - 'phabricator-textareautils', - 'javelin-workflow', - 'javelin-vector', - 'phuix-autocomplete', - 'javelin-mask', - ), 'a0b57eb8' => array( 'javelin-behavior', 'javelin-dom', @@ -2165,14 +2173,6 @@ return array( 'f12cbc9f' => array( 'phui-oi-list-view-css', ), - 'f2e58483' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-workflow', - 'javelin-stratcom', - 'conpherence-thread-manager', - ), 'f411b6ae' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 8d93f03b7d..1fbb338822 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -184,6 +184,7 @@ final class ConpherenceViewController extends id(new PhabricatorRemarkupControl()) ->setUser($user) ->setName('text') + ->setSendOnEnter(true) ->setValue($draft->getDraft())); $status_view = phutil_tag( diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php index 75054b9575..5d0cc079e7 100644 --- a/src/view/form/control/PhabricatorRemarkupControl.php +++ b/src/view/form/control/PhabricatorRemarkupControl.php @@ -5,6 +5,7 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { private $disableMacro = false; private $disableFullScreen = false; private $canPin; + private $sendOnEnter = false; public function setDisableMacros($disable) { $this->disableMacro = $disable; @@ -25,6 +26,15 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { return $this->canPin; } + public function setSendOnEnter($soe) { + $this->sendOnEnter = $soe; + return $this; + } + + public function getSendOnEnter() { + return $this->sendOnEnter; + } + protected function renderInput() { $id = $this->getID(); if (!$id) { @@ -78,6 +88,7 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { ), 'canPin' => $this->getCanPin(), 'disabled' => $this->getDisabled(), + 'sendOnEnter' => $this->getSendOnEnter(), 'rootID' => $root_id, 'autocompleteMap' => (object)array( 64 => array( // "@" diff --git a/webroot/rsrc/js/application/conpherence/behavior-pontificate.js b/webroot/rsrc/js/application/conpherence/behavior-pontificate.js index 5f9e24a915..537961ec71 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-pontificate.js +++ b/webroot/rsrc/js/application/conpherence/behavior-pontificate.js @@ -22,27 +22,4 @@ JX.behavior('conpherence-pontificate', function() { 'conpherence-pontificate', _sendMessage); - // Send on enter if the shift key is not held. - JX.Stratcom.listen( - 'keydown', - 'conpherence-pontificate', - function(e) { - if (e.getSpecialKey() != 'return') { - return; - } - - var raw = e.getRawEvent(); - if (raw.shiftKey) { - // If the shift key is pressed, let the browser write a newline into - // the textarea. - return; - } - - // From here on, interpret this as a "send" action, not a literal - // newline. - e.kill(); - - _sendMessage(e); - }); - }); diff --git a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js index f4cbc4f4fa..a53c567607 100644 --- a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js +++ b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js @@ -37,7 +37,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { // First, disable any active mode. if (edit_root) { - if (edit_mode == 'fa-arrows-alt') { + if (edit_mode == 'fullscreen') { JX.DOM.alterClass(edit_root, 'remarkup-control-fullscreen-mode', false); JX.DOM.alterClass(document.body, 'remarkup-fullscreen-mode', false); JX.Mask.hide('jx-light-mask'); @@ -58,7 +58,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { edit_mode = mode; // Now, apply the new mode. - if (mode == 'fa-arrows-alt') { + if (mode == 'fullscreen') { JX.DOM.alterClass(edit_root, 'remarkup-control-fullscreen-mode', true); JX.DOM.alterClass(document.body, 'remarkup-fullscreen-mode', true); JX.Mask.show('jx-light-mask'); @@ -118,7 +118,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { if (!edit_root) { return; } - if (edit_mode != 'fa-arrows-alt') { + if (edit_mode != 'fullscreen') { return; } @@ -140,7 +140,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { return; } - if (edit_mode != 'fa-arrows-alt') { + if (edit_mode != 'fullscreen') { return; } @@ -261,10 +261,10 @@ JX.behavior('phabricator-remarkup-assist', function(config) { break; case 'fa-arrows-alt': set_pinned_mode(root, false); - if (edit_mode == 'fa-arrows-alt') { + if (edit_mode == 'fullscreen') { set_edit_mode(root, 'normal'); } else { - set_edit_mode(root, 'fa-arrows-alt'); + set_edit_mode(root, 'fullscreen'); } break; case 'fa-eye': @@ -385,4 +385,36 @@ JX.behavior('phabricator-remarkup-assist', function(config) { .register(); } + if (config.sendOnEnter) { + // Send on enter if the shift key is not held. + JX.DOM.listen(area, 'keydown', null, + function(e) { + if (e.getSpecialKey() != 'return') { + return; + } + + var raw = e.getRawEvent(); + if (raw.shiftKey) { + // If the shift key is pressed, let the browser write a newline into + // the textarea. + return; + } + + if (edit_mode == 'fullscreen') { + // Don't send on enter in fullscreen + return; + } + + // From here on, interpret this as a "send" action, not a literal + // newline. + e.kill(); + + // This allows 'workflow' and similar actions to take effect. + // Such as pontificate in Conpherence + var form = e.getNode('tag:form'); + var r = JX.DOM.invoke(form, 'didSyntheticSubmit'); + + }); + } + }); From 49132b884b6ff6da079c2012a72719fc2e331466 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 10 Apr 2017 14:56:06 -0700 Subject: [PATCH 09/52] Sell Yellow! Buy Indigo! Summary: Fixes T12504. Replaces all tags with indigo. Test Plan: {F4849487} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12504 Differential Revision: https://secure.phabricator.com/D17649 --- .../files/controller/PhabricatorFileInfoController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php index 7217f284b4..f7e72be2aa 100644 --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -41,8 +41,8 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $ttl = $file->getTTL(); if ($ttl !== null) { $ttl_tag = id(new PHUITagView()) - ->setType(PHUITagView::TYPE_STATE) - ->setBackgroundColor(PHUITagView::COLOR_YELLOW) + ->setType(PHUITagView::TYPE_SHADE) + ->setShade(PHUITagView::COLOR_YELLOW) ->setName(pht('Temporary')); $header->addTag($ttl_tag); } @@ -50,8 +50,8 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $partial = $file->getIsPartial(); if ($partial) { $partial_tag = id(new PHUITagView()) - ->setType(PHUITagView::TYPE_STATE) - ->setBackgroundColor(PHUITagView::COLOR_ORANGE) + ->setType(PHUITagView::TYPE_SHADE) + ->setShade(PHUITagView::COLOR_ORANGE) ->setName(pht('Partial Upload')); $header->addTag($partial_tag); } From a7a068f84cd02d025fbc93e4fd314d475844cd0a Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 10 Apr 2017 15:46:35 -0700 Subject: [PATCH 10/52] Correct two parameter strictness issues with file uploads Summary: Fixes T12531. Strictness fallout from adding typechecking in D17616. - `chunkedHash` is not a real parameter, so the new typechecking was unhappy about it. - `mime-type` no longer allows `null`. Test Plan: - Ran `arc upload --conduit-uri ... 12MB.zero` on a 12MB file full of zeroes. - Before patch: badness, failure, fallback to one-shot uploads. - After patch: success and glory. Reviewers: chad Subscribers: joshuaspence Maniphest Tasks: T12531 Differential Revision: https://secure.phabricator.com/D17651 --- .../conduit/FileUploadChunkConduitAPIMethod.php | 15 ++++++++++----- .../files/storage/PhabricatorFile.php | 5 +++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php b/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php index 949bdde28a..b4d1a23a3f 100644 --- a/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php +++ b/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php @@ -61,15 +61,20 @@ final class FileUploadChunkConduitAPIMethod $mime_type = 'application/octet-stream'; } + $params = array( + 'name' => $file->getMonogram().'.chunk-'.$chunk->getID(), + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + ); + + if ($mime_type !== null) { + $params['mime-type'] = 'application/octet-stream'; + } + // NOTE: These files have a view policy which prevents normal access. They // are only accessed through the storage engine. $chunk_data = PhabricatorFile::newFromFileData( $data, - array( - 'name' => $file->getMonogram().'.chunk-'.$chunk->getID(), - 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, - 'mime-type' => $mime_type, - )); + $params); $chunk->setDataFilePHID($chunk_data->getPHID())->save(); diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 4faecbd557..db15fb43e0 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -251,6 +251,11 @@ final class PhabricatorFile extends PhabricatorFileDAO $file->setMimeType('application/octet-stream'); $chunked_hash = idx($params, 'chunkedHash'); + + // Get rid of this parameter now; we aren't passing it any further down + // the stack. + unset($params['chunkedHash']); + if ($chunked_hash) { $file->setContentHash($chunked_hash); } else { From 5cf53f7b3b424f1b988a60768e1ee0bc2781c571 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 10 Apr 2017 15:07:07 -0700 Subject: [PATCH 11/52] Fix some minor curtain overflow/wrap issues Summary: Fixes T12503. - Users with creative usernames like `MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM` could overflow "Subscribers" in the curtain UI. - Other content like packages could also overflow. - Users with interesting and unique names like `WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW` who were also away or disabled could get a linebreak between their availability dot and their username. Test Plan: See T12503 for "before" screenshots. Also tested mobile, which looked fine, but didn't screenshot it. {F4849900} {F4849912} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12503 Differential Revision: https://secure.phabricator.com/D17650 --- resources/celerity/map.php | 10 +++++----- .../rsrc/css/application/base/standard-page-view.css | 5 +++++ webroot/rsrc/css/phui/phui-curtain-view.css | 2 ++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e9b50dc81e..488b538f23 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '82aca405', 'conpherence.pkg.js' => '281b1a73', - 'core.pkg.css' => '1bf8fa70', + 'core.pkg.css' => 'b519db07', 'core.pkg.js' => 'fbc1c380', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '90b30783', @@ -37,7 +37,7 @@ return array( 'rsrc/css/application/base/main-menu-view.css' => '5294060f', 'rsrc/css/application/base/notification-menu.css' => '6a697e43', 'rsrc/css/application/base/phui-theme.css' => '9f261c6b', - 'rsrc/css/application/base/standard-page-view.css' => '894d8a25', + 'rsrc/css/application/base/standard-page-view.css' => '285cedf3', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => '0ede4c9b', @@ -139,7 +139,7 @@ return array( 'rsrc/css/phui/phui-comment-form.css' => '7d903c2d', 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb', - 'rsrc/css/phui/phui-curtain-view.css' => '947bf1a4', + 'rsrc/css/phui/phui-curtain-view.css' => '679743bb', 'rsrc/css/phui/phui-document-pro.css' => 'f56738ed', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'c32e8dec', @@ -798,7 +798,7 @@ return array( 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => '4383192f', - 'phabricator-standard-page-view' => '894d8a25', + 'phabricator-standard-page-view' => '285cedf3', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', 'phabricator-tooltip' => '8fadb715', @@ -839,7 +839,7 @@ return array( 'phui-comment-form-css' => '7d903c2d', 'phui-comment-panel-css' => 'f50152ad', 'phui-crumbs-view-css' => '6ece3bbb', - 'phui-curtain-view-css' => '947bf1a4', + 'phui-curtain-view-css' => '679743bb', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'c32e8dec', 'phui-document-view-pro-css' => 'f56738ed', diff --git a/webroot/rsrc/css/application/base/standard-page-view.css b/webroot/rsrc/css/application/base/standard-page-view.css index ebcf157ad4..f0e5e0cbe3 100644 --- a/webroot/rsrc/css/application/base/standard-page-view.css +++ b/webroot/rsrc/css/application/base/standard-page-view.css @@ -97,6 +97,11 @@ a.handle-status-closed:hover { font-size: {$smallerfontsize}; } +.phui-handle.phui-link-person { + /* Prevent linebreaks between user availability markers and usernames. */ + white-space: nowrap; +} + .phui-handle .phui-icon-view { display: inline-block; margin: 2px 2px -2px 0; diff --git a/webroot/rsrc/css/phui/phui-curtain-view.css b/webroot/rsrc/css/phui/phui-curtain-view.css index 7f42d53a3d..dbc706994a 100644 --- a/webroot/rsrc/css/phui/phui-curtain-view.css +++ b/webroot/rsrc/css/phui/phui-curtain-view.css @@ -24,6 +24,8 @@ .phui-curtain-panel-body { padding: 4px 0 0; + overflow: hidden; + text-overflow: ellipsis; } .device .phui-curtain-panel-body { From 26d6096e0a5bfd6c947e772209a7440b55db69bc Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 10 Apr 2017 17:09:22 -0700 Subject: [PATCH 12/52] When reviewing, always show "Accept" checkboxes for packages/projects, even if there's only one checkbox Summary: Fixes T12533. Test Plan: {F4853371} Reviewers: chad, lvital Reviewed By: lvital Maniphest Tasks: T12533 Differential Revision: https://secure.phabricator.com/D17652 --- .../xaction/DifferentialRevisionActionTransaction.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php index 8e1c437c53..ba769c2be5 100644 --- a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php @@ -124,10 +124,14 @@ abstract class DifferentialRevisionActionTransaction list($options, $value) = $this->getActionOptions($viewer, $revision); // Show the options if the user can select on behalf of two or more - // reviewers, or can force-accept on behalf of one or more reviewers. + // reviewers, or can force-accept on behalf of one or more reviewers, + // or can accept on behalf of a reviewer other than themselves (see + // T12533). $can_multi = (count($options) > 1); $can_force = (count($value) < count($options)); - if ($can_multi || $can_force) { + $not_self = (head_key($options) != $viewer->getPHID()); + + if ($can_multi || $can_force || $not_self) { $field->setOptions($options); $field->setValue($value); } From 28941b31059b9c3b961bcd31d631972196457be3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 11 Apr 2017 09:27:04 -0700 Subject: [PATCH 13/52] Update PhortuneMerchant to Modular Transactions Summary: Modernize PhortuneMerchant for Modular Transactions. Also changed the language of "Members" to "Managers", which I think fits better given the power/capability. Test Plan: - Create a new Merchant - Test not filling in a name, see error - Test removing myself, see error - Edit an existing Merchant - Add new managers - Test removing myself, see error - Replace Picture - Update various fields, contact info, email, footer - Verify transactions are now nice and pretty Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17584 --- src/__phutil_library_map__.php | 16 +- .../PhortuneMerchantPictureController.php | 3 +- .../PhortuneMerchantViewController.php | 2 +- .../PhortuneMerchantHasMemberEdgeType.php | 12 +- .../editor/PhortuneMerchantEditEngine.php | 25 +-- .../editor/PhortuneMerchantEditor.php | 147 +++--------------- .../storage/PhortuneMerchantTransaction.php | 84 +--------- ...PhortuneMerchantContactInfoTransaction.php | 56 +++++++ ...PhortuneMerchantDescriptionTransaction.php | 56 +++++++ ...hortuneMerchantInvoiceEmailTransaction.php | 94 +++++++++++ ...ortuneMerchantInvoiceFooterTransaction.php | 56 +++++++ .../PhortuneMerchantNameTransaction.php | 55 +++++++ .../PhortuneMerchantPictureTransaction.php | 33 ++++ .../PhortuneMerchantTransactionType.php | 4 + .../PhabricatorUSEnglishTranslation.php | 14 ++ 15 files changed, 431 insertions(+), 226 deletions(-) create mode 100644 src/applications/phortune/xaction/PhortuneMerchantContactInfoTransaction.php create mode 100644 src/applications/phortune/xaction/PhortuneMerchantDescriptionTransaction.php create mode 100644 src/applications/phortune/xaction/PhortuneMerchantInvoiceEmailTransaction.php create mode 100644 src/applications/phortune/xaction/PhortuneMerchantInvoiceFooterTransaction.php create mode 100644 src/applications/phortune/xaction/PhortuneMerchantNameTransaction.php create mode 100644 src/applications/phortune/xaction/PhortuneMerchantPictureTransaction.php create mode 100644 src/applications/phortune/xaction/PhortuneMerchantTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 60bbb25b86..3428de12bc 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4367,19 +4367,26 @@ phutil_register_library_map(array( 'PhortuneMemberHasMerchantEdgeType' => 'applications/phortune/edge/PhortuneMemberHasMerchantEdgeType.php', 'PhortuneMerchant' => 'applications/phortune/storage/PhortuneMerchant.php', 'PhortuneMerchantCapability' => 'applications/phortune/capability/PhortuneMerchantCapability.php', + 'PhortuneMerchantContactInfoTransaction' => 'applications/phortune/xaction/PhortuneMerchantContactInfoTransaction.php', 'PhortuneMerchantController' => 'applications/phortune/controller/merchant/PhortuneMerchantController.php', + 'PhortuneMerchantDescriptionTransaction' => 'applications/phortune/xaction/PhortuneMerchantDescriptionTransaction.php', 'PhortuneMerchantEditController' => 'applications/phortune/controller/merchant/PhortuneMerchantEditController.php', 'PhortuneMerchantEditEngine' => 'applications/phortune/editor/PhortuneMerchantEditEngine.php', 'PhortuneMerchantEditor' => 'applications/phortune/editor/PhortuneMerchantEditor.php', 'PhortuneMerchantHasMemberEdgeType' => 'applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php', 'PhortuneMerchantInvoiceCreateController' => 'applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php', + 'PhortuneMerchantInvoiceEmailTransaction' => 'applications/phortune/xaction/PhortuneMerchantInvoiceEmailTransaction.php', + 'PhortuneMerchantInvoiceFooterTransaction' => 'applications/phortune/xaction/PhortuneMerchantInvoiceFooterTransaction.php', 'PhortuneMerchantListController' => 'applications/phortune/controller/merchant/PhortuneMerchantListController.php', + 'PhortuneMerchantNameTransaction' => 'applications/phortune/xaction/PhortuneMerchantNameTransaction.php', 'PhortuneMerchantPHIDType' => 'applications/phortune/phid/PhortuneMerchantPHIDType.php', 'PhortuneMerchantPictureController' => 'applications/phortune/controller/merchant/PhortuneMerchantPictureController.php', + 'PhortuneMerchantPictureTransaction' => 'applications/phortune/xaction/PhortuneMerchantPictureTransaction.php', 'PhortuneMerchantQuery' => 'applications/phortune/query/PhortuneMerchantQuery.php', 'PhortuneMerchantSearchEngine' => 'applications/phortune/query/PhortuneMerchantSearchEngine.php', 'PhortuneMerchantTransaction' => 'applications/phortune/storage/PhortuneMerchantTransaction.php', 'PhortuneMerchantTransactionQuery' => 'applications/phortune/query/PhortuneMerchantTransactionQuery.php', + 'PhortuneMerchantTransactionType' => 'applications/phortune/xaction/PhortuneMerchantTransactionType.php', 'PhortuneMerchantViewController' => 'applications/phortune/controller/merchant/PhortuneMerchantViewController.php', 'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php', 'PhortuneOrderTableView' => 'applications/phortune/view/PhortuneOrderTableView.php', @@ -9820,19 +9827,26 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'PhortuneMerchantCapability' => 'PhabricatorPolicyCapability', + 'PhortuneMerchantContactInfoTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantController' => 'PhortuneController', + 'PhortuneMerchantDescriptionTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantEditController' => 'PhortuneMerchantController', 'PhortuneMerchantEditEngine' => 'PhabricatorEditEngine', 'PhortuneMerchantEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneMerchantHasMemberEdgeType' => 'PhabricatorEdgeType', 'PhortuneMerchantInvoiceCreateController' => 'PhortuneMerchantController', + 'PhortuneMerchantInvoiceEmailTransaction' => 'PhortuneMerchantTransactionType', + 'PhortuneMerchantInvoiceFooterTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantListController' => 'PhortuneMerchantController', + 'PhortuneMerchantNameTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantPHIDType' => 'PhabricatorPHIDType', 'PhortuneMerchantPictureController' => 'PhortuneMerchantController', + 'PhortuneMerchantPictureTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneMerchantSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhortuneMerchantTransaction' => 'PhabricatorApplicationTransaction', + 'PhortuneMerchantTransaction' => 'PhabricatorModularTransaction', 'PhortuneMerchantTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhortuneMerchantTransactionType' => 'PhabricatorModularTransactionType', 'PhortuneMerchantViewController' => 'PhortuneMerchantController', 'PhortuneMonthYearExpiryControl' => 'AphrontFormControl', 'PhortuneOrderTableView' => 'AphrontView', diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php index 74d7ea061c..041aae44e6 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php @@ -76,7 +76,8 @@ final class PhortuneMerchantPictureController $xactions = array(); $xactions[] = id(new PhortuneMerchantTransaction()) - ->setTransactionType(PhortuneMerchantTransaction::TYPE_PICTURE) + ->setTransactionType( + PhortuneMerchantPictureTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new PhortuneMerchantEditor()) diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php index 2f5232158d..230e36a47a 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php @@ -240,7 +240,7 @@ final class PhortuneMerchantViewController } $curtain->newPanel() - ->setHeaderText(pht('Members')) + ->setHeaderText(pht('Managers')) ->appendChild($member_list); return $curtain; diff --git a/src/applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php b/src/applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php index 8122b06827..2c94e69b83 100644 --- a/src/applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php +++ b/src/applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php @@ -14,7 +14,7 @@ final class PhortuneMerchantHasMemberEdgeType extends PhabricatorEdgeType { $add_edges) { return pht( - '%s added %s merchant member(s): %s.', + '%s added %s merchant manager(s): %s.', $actor, $add_count, $add_edges); @@ -26,7 +26,7 @@ final class PhortuneMerchantHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s removed %s merchant member(s): %s.', + '%s removed %s merchant manager(s): %s.', $actor, $rem_count, $rem_edges); @@ -41,7 +41,7 @@ final class PhortuneMerchantHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s edited %s merchant member(s), added %s: %s; removed %s: %s.', + '%s edited %s merchant manager(s), added %s: %s; removed %s: %s.', $actor, $total_count, $add_count, @@ -57,7 +57,7 @@ final class PhortuneMerchantHasMemberEdgeType extends PhabricatorEdgeType { $add_edges) { return pht( - '%s added %s merchant member(s) to %s: %s.', + '%s added %s merchant manager(s) to %s: %s.', $actor, $add_count, $object, @@ -71,7 +71,7 @@ final class PhortuneMerchantHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s removed %s merchant member(s) from %s: %s.', + '%s removed %s merchant manager(s) from %s: %s.', $actor, $rem_count, $object, @@ -88,7 +88,7 @@ final class PhortuneMerchantHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s edited %s merchant member(s) for %s, added %s: %s; removed %s: %s.', + '%s edited %s merchant manager(s) for %s, added %s: %s; removed %s: %s.', $actor, $total_count, $object, diff --git a/src/applications/phortune/editor/PhortuneMerchantEditEngine.php b/src/applications/phortune/editor/PhortuneMerchantEditEngine.php index bcc188f7e3..d56e385beb 100644 --- a/src/applications/phortune/editor/PhortuneMerchantEditEngine.php +++ b/src/applications/phortune/editor/PhortuneMerchantEditEngine.php @@ -81,21 +81,22 @@ final class PhortuneMerchantEditEngine ->setDescription(pht('Merchant name.')) ->setConduitTypeDescription(pht('New Merchant name.')) ->setIsRequired(true) - ->setTransactionType(PhortuneMerchantTransaction::TYPE_NAME) + ->setTransactionType( + PhortuneMerchantNameTransaction::TRANSACTIONTYPE) ->setValue($object->getName()), id(new PhabricatorUsersEditField()) ->setKey('members') - ->setAliases(array('memberPHIDs')) - ->setLabel(pht('Members')) + ->setAliases(array('memberPHIDs', 'managerPHIDs')) + ->setLabel(pht('Managers')) ->setUseEdgeTransactions(true) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue( 'edge:type', PhortuneMerchantHasMemberEdgeType::EDGECONST) - ->setDescription(pht('Initial merchant members.')) - ->setConduitDescription(pht('Set merchant members.')) - ->setConduitTypeDescription(pht('New list of members.')) + ->setDescription(pht('Initial merchant managers.')) + ->setConduitDescription(pht('Set merchant managers.')) + ->setConduitTypeDescription(pht('New list of managers.')) ->setInitialValue($object->getMemberPHIDs()) ->setValue($member_phids), @@ -104,7 +105,8 @@ final class PhortuneMerchantEditEngine ->setLabel(pht('Description')) ->setDescription(pht('Merchant description.')) ->setConduitTypeDescription(pht('New merchant description.')) - ->setTransactionType(PhortuneMerchantTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + PhortuneMerchantDescriptionTransaction::TRANSACTIONTYPE) ->setValue($object->getDescription()), id(new PhabricatorRemarkupEditField()) @@ -112,7 +114,8 @@ final class PhortuneMerchantEditEngine ->setLabel(pht('Contact Info')) ->setDescription(pht('Merchant contact information.')) ->setConduitTypeDescription(pht('Merchant contact information.')) - ->setTransactionType(PhortuneMerchantTransaction::TYPE_CONTACTINFO) + ->setTransactionType( + PhortuneMerchantContactInfoTransaction::TRANSACTIONTYPE) ->setValue($object->getContactInfo()), id(new PhabricatorTextEditField()) @@ -121,7 +124,8 @@ final class PhortuneMerchantEditEngine ->setDescription(pht('Email address invoices are sent from.')) ->setConduitTypeDescription( pht('Email address invoices are sent from.')) - ->setTransactionType(PhortuneMerchantTransaction::TYPE_INVOICEEMAIL) + ->setTransactionType( + PhortuneMerchantInvoiceEmailTransaction::TRANSACTIONTYPE) ->setValue($object->getInvoiceEmail()), id(new PhabricatorRemarkupEditField()) @@ -129,7 +133,8 @@ final class PhortuneMerchantEditEngine ->setLabel(pht('Invoice Footer')) ->setDescription(pht('Footer on invoice forms.')) ->setConduitTypeDescription(pht('Footer on invoice forms.')) - ->setTransactionType(PhortuneMerchantTransaction::TYPE_INVOICEFOOTER) + ->setTransactionType( + PhortuneMerchantInvoiceFooterTransaction::TRANSACTIONTYPE) ->setValue($object->getInvoiceFooter()), ); diff --git a/src/applications/phortune/editor/PhortuneMerchantEditor.php b/src/applications/phortune/editor/PhortuneMerchantEditor.php index 53fc7c94f2..954570be3f 100644 --- a/src/applications/phortune/editor/PhortuneMerchantEditor.php +++ b/src/applications/phortune/editor/PhortuneMerchantEditor.php @@ -11,104 +11,19 @@ final class PhortuneMerchantEditor return pht('Phortune Merchants'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this merchant.', $author); + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhortuneMerchantTransaction::TYPE_NAME; - $types[] = PhortuneMerchantTransaction::TYPE_DESCRIPTION; - $types[] = PhortuneMerchantTransaction::TYPE_CONTACTINFO; - $types[] = PhortuneMerchantTransaction::TYPE_PICTURE; - $types[] = PhortuneMerchantTransaction::TYPE_INVOICEEMAIL; - $types[] = PhortuneMerchantTransaction::TYPE_INVOICEFOOTER; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDGE; return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhortuneMerchantTransaction::TYPE_NAME: - return $object->getName(); - case PhortuneMerchantTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case PhortuneMerchantTransaction::TYPE_CONTACTINFO: - return $object->getContactInfo(); - case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: - return $object->getInvoiceEmail(); - case PhortuneMerchantTransaction::TYPE_INVOICEFOOTER: - return $object->getInvoiceFooter(); - case PhortuneMerchantTransaction::TYPE_PICTURE: - return $object->getProfileImagePHID(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhortuneMerchantTransaction::TYPE_NAME: - case PhortuneMerchantTransaction::TYPE_DESCRIPTION: - case PhortuneMerchantTransaction::TYPE_CONTACTINFO: - case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: - case PhortuneMerchantTransaction::TYPE_INVOICEFOOTER: - case PhortuneMerchantTransaction::TYPE_PICTURE: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhortuneMerchantTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - case PhortuneMerchantTransaction::TYPE_DESCRIPTION: - $object->setDescription($xaction->getNewValue()); - return; - case PhortuneMerchantTransaction::TYPE_CONTACTINFO: - $object->setContactInfo($xaction->getNewValue()); - return; - case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: - $object->setInvoiceEmail($xaction->getNewValue()); - return; - case PhortuneMerchantTransaction::TYPE_INVOICEFOOTER: - $object->setInvoiceFooter($xaction->getNewValue()); - return; - case PhortuneMerchantTransaction::TYPE_PICTURE: - $object->setProfileImagePHID($xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhortuneMerchantTransaction::TYPE_NAME: - case PhortuneMerchantTransaction::TYPE_DESCRIPTION: - case PhortuneMerchantTransaction::TYPE_CONTACTINFO: - case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: - case PhortuneMerchantTransaction::TYPE_INVOICEFOOTER: - case PhortuneMerchantTransaction::TYPE_PICTURE: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - protected function validateTransaction( PhabricatorLiskDAO $object, $type, @@ -117,48 +32,28 @@ final class PhortuneMerchantEditor $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { - case PhortuneMerchantTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Merchant name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: - $new_email = null; + case PhabricatorTransactions::TYPE_EDGE: foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: - $new_email = $xaction->getNewValue(); - break; - } - } - if (strlen($new_email)) { - $email = new PhutilEmailAddress($new_email); - $domain = $email->getDomainName(); - - if (!$domain) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('%s is not a valid email.', $new_email), - nonempty(last($xactions), null)); - - $errors[] = $error; + switch ($xaction->getMetadataValue('edge:type')) { + case PhortuneMerchantHasMemberEdgeType::EDGECONST: + $new = $xaction->getNewValue(); + $set = idx($new, '-', array()); + $actor_phid = $this->requireActor()->getPHID(); + foreach ($set as $phid) { + if ($actor_phid == $phid) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('You can not remove yourself as an merchant manager.'), + $xaction); + $errors[] = $error; + } + } + break; } } break; } - return $errors; } diff --git a/src/applications/phortune/storage/PhortuneMerchantTransaction.php b/src/applications/phortune/storage/PhortuneMerchantTransaction.php index c5f8db6d43..3befb12212 100644 --- a/src/applications/phortune/storage/PhortuneMerchantTransaction.php +++ b/src/applications/phortune/storage/PhortuneMerchantTransaction.php @@ -1,14 +1,7 @@ getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this merchant.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s renamed this merchant from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - case self::TYPE_DESCRIPTION: - return pht( - '%s updated the description for this merchant.', - $this->renderHandleLink($author_phid)); - case self::TYPE_CONTACTINFO: - return pht( - '%s updated the contact information for this merchant.', - $this->renderHandleLink($author_phid)); - case self::TYPE_INVOICEEMAIL: - return pht( - '%s updated the invoice email for this merchant.', - $this->renderHandleLink($author_phid)); - case self::TYPE_INVOICEFOOTER: - return pht( - '%s updated the invoice footer for this merchant.', - $this->renderHandleLink($author_phid)); - } - - return parent::getTitle(); - } - - public function shouldHide() { - $old = $this->getOldValue(); - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - case self::TYPE_CONTACTINFO: - case self::TYPE_INVOICEEMAIL: - case self::TYPE_INVOICEFOOTER: - return ($old === null); - } - return parent::shouldHide(); - } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($this->getOldValue() !== null); - case self::TYPE_CONTACTINFO: - return ($this->getOldValue() !== null); - case self::TYPE_INVOICEEMAIL: - return ($this->getOldValue() !== null); - case self::TYPE_INVOICEFOOTER: - return ($this->getOldValue() !== null); - } - - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); + public function getBaseTransactionClass() { + return 'PhortuneMerchantTransactionType'; } } diff --git a/src/applications/phortune/xaction/PhortuneMerchantContactInfoTransaction.php b/src/applications/phortune/xaction/PhortuneMerchantContactInfoTransaction.php new file mode 100644 index 0000000000..e46dc62102 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneMerchantContactInfoTransaction.php @@ -0,0 +1,56 @@ +getContactInfo(); + } + + public function applyInternalEffects($object, $value) { + $object->setContactInfo($value); + } + + public function getTitle() { + return pht( + '%s updated the merchant contact info.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the merchant contact info for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO MERCHANT CONTACT INFO'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneMerchantDescriptionTransaction.php b/src/applications/phortune/xaction/PhortuneMerchantDescriptionTransaction.php new file mode 100644 index 0000000000..34c94a6ea0 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneMerchantDescriptionTransaction.php @@ -0,0 +1,56 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + '%s updated the merchant description.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the merchant description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO MERCHANT DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneMerchantInvoiceEmailTransaction.php b/src/applications/phortune/xaction/PhortuneMerchantInvoiceEmailTransaction.php new file mode 100644 index 0000000000..d58167de25 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneMerchantInvoiceEmailTransaction.php @@ -0,0 +1,94 @@ +getInvoiceEmail(); + } + + public function applyInternalEffects($object, $value) { + $object->setInvoiceEmail($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($old) && strlen($new)) { + return pht( + '%s updated the invoice email from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } else if (strlen($old)) { + return pht( + '%s removed the invoice email.', + $this->renderAuthor()); + } else { + return pht( + '%s set the invoice email to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($old) && strlen($new)) { + return pht( + '%s updated %s invoice email from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } else if (strlen($old)) { + return pht( + '%s removed the invoice email for %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s set the invoice email for %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderNewValue()); + } + } + + public function getIcon() { + return 'fa-envelope'; + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('invoiceEmail'); + foreach ($xactions as $xaction) { + if (strlen($xaction->getNewValue())) { + $email = new PhutilEmailAddress($xaction->getNewValue()); + $domain = $email->getDomainName(); + if (!strlen($domain)) { + $errors[] = $this->newInvalidError( + pht('Invoice email "%s" must be a valid email.', + $xaction->getNewValue())); + } + + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The email can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + } + + return $errors; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneMerchantInvoiceFooterTransaction.php b/src/applications/phortune/xaction/PhortuneMerchantInvoiceFooterTransaction.php new file mode 100644 index 0000000000..6d8f584104 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneMerchantInvoiceFooterTransaction.php @@ -0,0 +1,56 @@ +getInvoiceFooter(); + } + + public function applyInternalEffects($object, $value) { + $object->setInvoiceFooter($value); + } + + public function getTitle() { + return pht( + '%s updated the merchant invoice footer.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the merchant invoice footer for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO MERCHANT INVOICE FOOTER'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneMerchantNameTransaction.php b/src/applications/phortune/xaction/PhortuneMerchantNameTransaction.php new file mode 100644 index 0000000000..b0115ace3e --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneMerchantNameTransaction.php @@ -0,0 +1,55 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s renamed this merchant from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s renamed %s merchant name from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Merchants must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The name can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneMerchantPictureTransaction.php b/src/applications/phortune/xaction/PhortuneMerchantPictureTransaction.php new file mode 100644 index 0000000000..7502592b3f --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneMerchantPictureTransaction.php @@ -0,0 +1,33 @@ +getProfileImagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setProfileImagePHID($value); + } + + public function getTitle() { + return pht( + '%s updated the picture.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the picture for merchant %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getIcon() { + return 'fa-camera-retro'; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneMerchantTransactionType.php b/src/applications/phortune/xaction/PhortuneMerchantTransactionType.php new file mode 100644 index 0000000000..e2c2eb85a5 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneMerchantTransactionType.php @@ -0,0 +1,4 @@ + '%s accepted this revision as: %3$s.', + + '%s added %s merchant manager(s): %s.' => array( + array( + '%s added a merchant manager: %3$s.', + '%s added merchant managers: %3$s.', + ), + ), + + '%s removed %s merchant manager(s): %s.' => array( + array( + '%s removed a merchant manager: %3$s.', + '%s removed merchant managers: %3$s.', + ), + ), ); } From af1d494d66e9743dcd89d8a872a31d8e1b71f208 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 11 Apr 2017 09:51:01 -0700 Subject: [PATCH 14/52] Fix an issue where rejecting reviewers weren't powerful enough Summary: Previously, "reject" and "reject older" were separate statuses. Now, they're both shades of "reject". Set the "older reject" flag properly when we find a non-current reject. Test Plan: - User A accepts a revision. - User B rejects it. - Author updates it. - Before patch: incorrectly transitions to "accepted" ("older" reject is ignored). - After patch: correctly transitions to "needs review". Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17653 --- .../differential/editor/DifferentialTransactionEditor.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index dd6666c653..0086c101e2 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -672,6 +672,8 @@ final class DifferentialTransactionEditor $active_phid = $active_diff->getPHID(); if ($reviewer->isRejected($active_phid)) { $has_rejecting_reviewer = true; + } else { + $has_rejecting_older_reviewer = true; } break; case DifferentialReviewerStatus::STATUS_REJECTED_OLDER: From 21709a2bbc1aca28c99786b15c488e8927e2aa16 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 11 Apr 2017 10:54:46 -0700 Subject: [PATCH 15/52] Remove 'isPartial' parameter with no effect Summary: Fixes T12536. Nothing reads this parameter; `PhabricatorFile::newChunkedFile` sets the `isPartial` flag automatically. Test Plan: Grepped for `isPartial`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12536 Differential Revision: https://secure.phabricator.com/D17654 --- .../files/uploadsource/PhabricatorFileUploadSource.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php index b4578e9e56..0ef33a2521 100644 --- a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php +++ b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php @@ -137,10 +137,6 @@ abstract class PhabricatorFileUploadSource $parameters = $this->getNewFileParameters(); - $parameters = array( - 'isPartial' => true, - ) + $parameters; - $data_length = $this->getDataLength(); if ($data_length !== null) { $length = $data_length; From 5dd18a7ec1f970cdac00df1bc3cbe0745f4c3847 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 11 Apr 2017 11:53:42 -0700 Subject: [PATCH 16/52] Modernize PhortuneAccount with EditEngine/Modular Transactions Summary: This updates the backend of PhortuneAccount to use EditEngine and Modular Transactions and updates language to "account manager" for clarity of role. Test Plan: - Wiped `phortune_account` table - Visit Phortune, see new account automatically created. - Edit name and managers - Try to set no name or remove myself as a manager, get error messages - Visit `/phortune/` and create another new account Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17585 --- src/__phutil_library_map__.php | 8 +- .../PhabricatorPhortuneApplication.php | 3 +- .../account/PhortuneAccountEditController.php | 136 +----------------- .../account/PhortuneAccountViewController.php | 2 +- .../PhortuneMerchantPictureController.php | 2 +- .../edge/PhortuneAccountHasMemberEdgeType.php | 12 +- .../editor/PhortuneAccountEditEngine.php | 108 ++++++++++++++ .../phortune/editor/PhortuneAccountEditor.php | 116 ++++++--------- .../editor/PhortuneMerchantEditEngine.php | 2 +- .../phortune/storage/PhortuneAccount.php | 7 +- .../storage/PhortuneAccountTransaction.php | 29 +--- .../phortune/storage/PhortuneMerchant.php | 3 +- .../PhortuneAccountNameTransaction.php | 55 +++++++ .../PhortuneAccountTransactionType.php | 4 + .../PhabricatorUSEnglishTranslation.php | 15 ++ 15 files changed, 255 insertions(+), 247 deletions(-) create mode 100644 src/applications/phortune/editor/PhortuneAccountEditEngine.php create mode 100644 src/applications/phortune/xaction/PhortuneAccountNameTransaction.php create mode 100644 src/applications/phortune/xaction/PhortuneAccountTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3428de12bc..46aed7ce45 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4322,13 +4322,16 @@ phutil_register_library_map(array( 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', 'PhortuneAccountChargeListController' => 'applications/phortune/controller/account/PhortuneAccountChargeListController.php', 'PhortuneAccountEditController' => 'applications/phortune/controller/account/PhortuneAccountEditController.php', + 'PhortuneAccountEditEngine' => 'applications/phortune/editor/PhortuneAccountEditEngine.php', 'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php', 'PhortuneAccountHasMemberEdgeType' => 'applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php', 'PhortuneAccountListController' => 'applications/phortune/controller/account/PhortuneAccountListController.php', + 'PhortuneAccountNameTransaction' => 'applications/phortune/xaction/PhortuneAccountNameTransaction.php', 'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php', 'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php', 'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php', 'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php', + 'PhortuneAccountTransactionType' => 'applications/phortune/xaction/PhortuneAccountTransactionType.php', 'PhortuneAccountViewController' => 'applications/phortune/controller/account/PhortuneAccountViewController.php', 'PhortuneAdHocCart' => 'applications/phortune/cart/PhortuneAdHocCart.php', 'PhortuneAdHocProduct' => 'applications/phortune/product/PhortuneAdHocProduct.php', @@ -9771,13 +9774,16 @@ phutil_register_library_map(array( ), 'PhortuneAccountChargeListController' => 'PhortuneController', 'PhortuneAccountEditController' => 'PhortuneController', + 'PhortuneAccountEditEngine' => 'PhabricatorEditEngine', 'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneAccountHasMemberEdgeType' => 'PhabricatorEdgeType', 'PhortuneAccountListController' => 'PhortuneController', + 'PhortuneAccountNameTransaction' => 'PhortuneAccountTransactionType', 'PhortuneAccountPHIDType' => 'PhabricatorPHIDType', 'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', - 'PhortuneAccountTransaction' => 'PhabricatorApplicationTransaction', + 'PhortuneAccountTransaction' => 'PhabricatorModularTransaction', 'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhortuneAccountTransactionType' => 'PhabricatorModularTransactionType', 'PhortuneAccountViewController' => 'PhortuneController', 'PhortuneAdHocCart' => 'PhortuneCartImplementation', 'PhortuneAdHocProduct' => 'PhortuneProductImplementation', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 15c312773b..0733d65ac4 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -67,7 +67,8 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { ), 'account/' => array( '' => 'PhortuneAccountListController', - 'edit/(?:(?P\d+)/)?' => 'PhortuneAccountEditController', + $this->getEditRoutePattern('edit/') + => 'PhortuneAccountEditController', ), 'product/' => array( '' => 'PhortuneProductListController', diff --git a/src/applications/phortune/controller/account/PhortuneAccountEditController.php b/src/applications/phortune/controller/account/PhortuneAccountEditController.php index 104623219b..d90abcbd64 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountEditController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountEditController.php @@ -1,137 +1,11 @@ getViewer(); - $id = $request->getURIData('id'); - - if ($id) { - $account = id(new PhortuneAccountQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$account) { - return new Aphront404Response(); - } - $is_new = false; - } else { - $account = PhortuneAccount::initializeNewAccount($viewer); - $account->attachMemberPHIDs(array($viewer->getPHID())); - $is_new = true; - } - - $v_name = $account->getName(); - $e_name = true; - - $v_members = $account->getMemberPHIDs(); - $e_members = null; - - $validation_exception = null; - - if ($request->isFormPost()) { - $v_name = $request->getStr('name'); - $v_members = $request->getArr('memberPHIDs'); - - $type_name = PhortuneAccountTransaction::TYPE_NAME; - $type_edge = PhabricatorTransactions::TYPE_EDGE; - - $xactions = array(); - $xactions[] = id(new PhortuneAccountTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new PhortuneAccountTransaction()) - ->setTransactionType($type_edge) - ->setMetadataValue( - 'edge:type', - PhortuneAccountHasMemberEdgeType::EDGECONST) - ->setNewValue( - array( - '=' => array_fuse($v_members), - )); - - $editor = id(new PhortuneAccountEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $editor->applyTransactions($account, $xactions); - - $account_uri = $this->getApplicationURI($account->getID().'/'); - return id(new AphrontRedirectResponse())->setURI($account_uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - $e_name = $ex->getShortMessage($type_name); - $e_members = $ex->getShortMessage($type_edge); - } - } - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->setBorder(true); - - if ($is_new) { - $cancel_uri = $this->getApplicationURI('account/'); - $crumbs->addTextCrumb(pht('Accounts'), $cancel_uri); - $crumbs->addTextCrumb(pht('Create Account')); - - $title = pht('Create Payment Account'); - $submit_button = pht('Create Account'); - } else { - $cancel_uri = $this->getApplicationURI($account->getID().'/'); - $crumbs->addTextCrumb($account->getName(), $cancel_uri); - $crumbs->addTextCrumb(pht('Edit')); - - $title = pht('Edit %s', $account->getName()); - $submit_button = pht('Save Changes'); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('name') - ->setLabel(pht('Name')) - ->setValue($v_name) - ->setError($e_name)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setLabel(pht('Members')) - ->setName('memberPHIDs') - ->setValue($v_members) - ->setError($e_members)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue($submit_button) - ->addCancelButton($cancel_uri)); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Account')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setValidationException($validation_exception) - ->setForm($form); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-pencil'); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); + return id(new PhortuneAccountEditEngine()) + ->setController($this) + ->buildResponse(); } - } diff --git a/src/applications/phortune/controller/account/PhortuneAccountViewController.php b/src/applications/phortune/controller/account/PhortuneAccountViewController.php index 97ea1beea8..d0ef03fc9a 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountViewController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountViewController.php @@ -129,7 +129,7 @@ final class PhortuneAccountViewController extends PhortuneController { ->appendChild($status_view); $curtain->newPanel() - ->setHeaderText(pht('Members')) + ->setHeaderText(pht('Managers')) ->appendChild($member_list); return $curtain; diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php index 041aae44e6..587395fb2c 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php @@ -21,7 +21,7 @@ final class PhortuneMerchantPictureController return new Aphront404Response(); } - $uri = $merchant->getViewURI(); + $uri = $merchant->getURI(); $supported_formats = PhabricatorFile::getTransformableImageFormats(); $e_file = true; diff --git a/src/applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php b/src/applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php index 89b14ae67f..71a0e56a33 100644 --- a/src/applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php +++ b/src/applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php @@ -14,7 +14,7 @@ final class PhortuneAccountHasMemberEdgeType extends PhabricatorEdgeType { $add_edges) { return pht( - '%s added %s account member(s): %s.', + '%s added %s account manager(s): %s.', $actor, $add_count, $add_edges); @@ -26,7 +26,7 @@ final class PhortuneAccountHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s removed %s account member(s): %s.', + '%s removed %s account manager(s): %s.', $actor, $rem_count, $rem_edges); @@ -41,7 +41,7 @@ final class PhortuneAccountHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s edited %s account member(s), added %s: %s; removed %s: %s.', + '%s edited %s account manager(s), added %s: %s; removed %s: %s.', $actor, $total_count, $add_count, @@ -57,7 +57,7 @@ final class PhortuneAccountHasMemberEdgeType extends PhabricatorEdgeType { $add_edges) { return pht( - '%s added %s account member(s) to %s: %s.', + '%s added %s account manager(s) to %s: %s.', $actor, $add_count, $object, @@ -71,7 +71,7 @@ final class PhortuneAccountHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s removed %s account member(s) from %s: %s.', + '%s removed %s account manager(s) from %s: %s.', $actor, $rem_count, $object, @@ -88,7 +88,7 @@ final class PhortuneAccountHasMemberEdgeType extends PhabricatorEdgeType { $rem_edges) { return pht( - '%s edited %s account member(s) for %s, added %s: %s; removed %s: %s.', + '%s edited %s account manager(s) for %s, added %s: %s; removed %s: %s.', $actor, $total_count, $object, diff --git a/src/applications/phortune/editor/PhortuneAccountEditEngine.php b/src/applications/phortune/editor/PhortuneAccountEditEngine.php new file mode 100644 index 0000000000..ed0e4b01be --- /dev/null +++ b/src/applications/phortune/editor/PhortuneAccountEditEngine.php @@ -0,0 +1,108 @@ +getViewer()); + } + + protected function newObjectQuery() { + return new PhortuneAccountQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Payment Account'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Account: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return $object->getName(); + } + + protected function getObjectCreateShortText() { + return pht('Create Account'); + } + + protected function getObjectName() { + return pht('Account'); + } + + protected function getObjectCreateCancelURI($object) { + return $this->getApplication()->getApplicationURI('/'); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('edit/'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + + if ($this->getIsCreate()) { + $member_phids = array($viewer->getPHID()); + } else { + $member_phids = $object->getMemberPHIDs(); + } + + $fields = array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Account name.')) + ->setConduitTypeDescription(pht('New account name.')) + ->setTransactionType( + PhortuneAccountNameTransaction::TRANSACTIONTYPE) + ->setValue($object->getName()) + ->setIsRequired(true), + + id(new PhabricatorUsersEditField()) + ->setKey('managers') + ->setAliases(array('memberPHIDs', 'managerPHIDs')) + ->setLabel(pht('Managers')) + ->setUseEdgeTransactions(true) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue( + 'edge:type', + PhortuneAccountHasMemberEdgeType::EDGECONST) + ->setDescription(pht('Initial account managers.')) + ->setConduitDescription(pht('Set account managers.')) + ->setConduitTypeDescription(pht('New list of managers.')) + ->setInitialValue($object->getMemberPHIDs()) + ->setValue($member_phids), + ); + + return $fields; + + } + +} diff --git a/src/applications/phortune/editor/PhortuneAccountEditor.php b/src/applications/phortune/editor/PhortuneAccountEditor.php index 556df99fbf..8672c62417 100644 --- a/src/applications/phortune/editor/PhortuneAccountEditor.php +++ b/src/applications/phortune/editor/PhortuneAccountEditor.php @@ -1,6 +1,5 @@ getTransactionType()) { - case PhortuneAccountTransaction::TYPE_NAME: - return $object->getName(); - } - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhortuneAccountTransaction::TYPE_NAME: - return $xaction->getNewValue(); - } - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhortuneAccountTransaction::TYPE_NAME: - $object->setName($xaction->getNewValue()); - return; - } - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhortuneAccountTransaction::TYPE_NAME: - return; - } - return parent::applyCustomExternalTransaction($object, $xaction); - } - protected function validateTransaction( PhabricatorLiskDAO $object, $type, @@ -71,47 +29,53 @@ final class PhortuneAccountEditor $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { - case PhortuneAccountTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Account name is required.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; case PhabricatorTransactions::TYPE_EDGE: foreach ($xactions as $xaction) { switch ($xaction->getMetadataValue('edge:type')) { case PhortuneAccountHasMemberEdgeType::EDGECONST: - // TODO: This is a bit cumbersome, but validation happens before - // transaction normalization. Maybe provide a cleaner attack on - // this eventually? There's no way to generate "+" or "-" - // transactions right now. + $actor_phid = $this->requireActor()->getPHID(); $new = $xaction->getNewValue(); - $set = idx($new, '=', array()); + $old = $object->getMemberPHIDs(); - if (empty($set[$this->requireActor()->getPHID()])) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('You can not remove yourself as an account member.'), - $xaction); - $errors[] = $error; + // Check if user is trying to not set themselves on creation + if (!$old) { + $set = idx($new, '+', array()); + $actor_set = false; + foreach ($set as $phid) { + if ($actor_phid == $phid) { + $actor_set = true; + } + } + if (!$actor_set) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('You can not remove yourself as an account manager.'), + $xaction); + $errors[] = $error; + + } } - break; + + // Check if user is trying to remove themselves on edit + $set = idx($new, '-', array()); + foreach ($set as $phid) { + if ($actor_phid == $phid) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('You can not remove yourself as an account manager.'), + $xaction); + $errors[] = $error; + + } + } + break; } } break; } - return $errors; } + } diff --git a/src/applications/phortune/editor/PhortuneMerchantEditEngine.php b/src/applications/phortune/editor/PhortuneMerchantEditEngine.php index d56e385beb..596f842574 100644 --- a/src/applications/phortune/editor/PhortuneMerchantEditEngine.php +++ b/src/applications/phortune/editor/PhortuneMerchantEditEngine.php @@ -58,7 +58,7 @@ final class PhortuneMerchantEditEngine } protected function getObjectViewURI($object) { - return $object->getViewURI(); + return $object->getURI(); } public function isEngineConfigurable() { diff --git a/src/applications/phortune/storage/PhortuneAccount.php b/src/applications/phortune/storage/PhortuneAccount.php index e86fd53df2..822673ea94 100644 --- a/src/applications/phortune/storage/PhortuneAccount.php +++ b/src/applications/phortune/storage/PhortuneAccount.php @@ -17,7 +17,6 @@ final class PhortuneAccount extends PhortuneDAO public static function initializeNewAccount(PhabricatorUser $actor) { $account = id(new PhortuneAccount()); - $account->memberPHIDs = array(); return $account; @@ -31,7 +30,7 @@ final class PhortuneAccount extends PhortuneDAO $xactions = array(); $xactions[] = id(new PhortuneAccountTransaction()) - ->setTransactionType(PhortuneAccountTransaction::TYPE_NAME) + ->setTransactionType(PhortuneAccountNameTransaction::TRANSACTIONTYPE) ->setNewValue(pht('Default Account')); $xactions[] = id(new PhortuneAccountTransaction()) @@ -96,6 +95,10 @@ final class PhortuneAccount extends PhortuneDAO return $this; } + public function getURI() { + return '/phortune/'.$this->getID().'/'; + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/phortune/storage/PhortuneAccountTransaction.php b/src/applications/phortune/storage/PhortuneAccountTransaction.php index 5d4a746ad9..6733cbe879 100644 --- a/src/applications/phortune/storage/PhortuneAccountTransaction.php +++ b/src/applications/phortune/storage/PhortuneAccountTransaction.php @@ -1,9 +1,7 @@ getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if ($old === null) { - return pht( - '%s created this account.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s renamed this account from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'PhortuneAccountTransactionType'; } } diff --git a/src/applications/phortune/storage/PhortuneMerchant.php b/src/applications/phortune/storage/PhortuneMerchant.php index 69675c2ebc..81d0391370 100644 --- a/src/applications/phortune/storage/PhortuneMerchant.php +++ b/src/applications/phortune/storage/PhortuneMerchant.php @@ -53,7 +53,7 @@ final class PhortuneMerchant extends PhortuneDAO return $this; } - public function getViewURI() { + public function getURI() { return '/phortune/merchant/'.$this->getID().'/'; } @@ -70,6 +70,7 @@ final class PhortuneMerchant extends PhortuneDAO return $this->assertAttached($this->profileImageFile); } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/phortune/xaction/PhortuneAccountNameTransaction.php b/src/applications/phortune/xaction/PhortuneAccountNameTransaction.php new file mode 100644 index 0000000000..08b8b69422 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneAccountNameTransaction.php @@ -0,0 +1,55 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($old) && strlen($new)) { + return pht( + '%s renamed this account from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } else { + return pht( + '%s created this account.', + $this->renderAuthor()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Accounts must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('name'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newRequiredError( + pht('The name can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneAccountTransactionType.php b/src/applications/phortune/xaction/PhortuneAccountTransactionType.php new file mode 100644 index 0000000000..910ed8d136 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneAccountTransactionType.php @@ -0,0 +1,4 @@ + array( + array( + '%s added an account manager: %3$s.', + '%s added account managers: %3$s.', + ), + ), + + '%s removed %s account manager(s): %s.' => array( + array( + '%s removed an account manager: %3$s.', + '%s removed account managers: %3$s.', + ), + ), + ); } From 58a127f2f9dd83073b13a096625d331d9aa2d8bd Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 11 Apr 2017 10:57:40 -0700 Subject: [PATCH 17/52] Update Phortune Merchant UI Summary: Builds out Phortune Merchant pages to have a sidenav and sub-pages for further expansion. For now this links Orders and Subscriptions to the query engine pages, but could be split out to be more informative (unpaid, upcoming, etc). Test Plan: Create a new merchant, edit some information, add a manager in new UI, edit logo, click through to subscriptions, orders. {F4883013} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17655 --- src/__phutil_library_map__.php | 12 ++- .../PhabricatorPhortuneApplication.php | 4 + .../PhortuneMerchantAddManagerController.php | 76 +++++++++++++++ ...hortuneMerchantInvoiceCreateController.php | 9 +- .../PhortuneMerchantManagerController.php | 91 ++++++++++++++++++ .../PhortuneMerchantPictureController.php | 14 +-- .../PhortuneMerchantProfileController.php | 92 +++++++++++++++++++ .../PhortuneMerchantViewController.php | 31 ++----- 8 files changed, 292 insertions(+), 37 deletions(-) create mode 100644 src/applications/phortune/controller/merchant/PhortuneMerchantAddManagerController.php create mode 100644 src/applications/phortune/controller/merchant/PhortuneMerchantManagerController.php create mode 100644 src/applications/phortune/controller/merchant/PhortuneMerchantProfileController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 46aed7ce45..399b143451 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4369,6 +4369,7 @@ phutil_register_library_map(array( 'PhortuneMemberHasAccountEdgeType' => 'applications/phortune/edge/PhortuneMemberHasAccountEdgeType.php', 'PhortuneMemberHasMerchantEdgeType' => 'applications/phortune/edge/PhortuneMemberHasMerchantEdgeType.php', 'PhortuneMerchant' => 'applications/phortune/storage/PhortuneMerchant.php', + 'PhortuneMerchantAddManagerController' => 'applications/phortune/controller/merchant/PhortuneMerchantAddManagerController.php', 'PhortuneMerchantCapability' => 'applications/phortune/capability/PhortuneMerchantCapability.php', 'PhortuneMerchantContactInfoTransaction' => 'applications/phortune/xaction/PhortuneMerchantContactInfoTransaction.php', 'PhortuneMerchantController' => 'applications/phortune/controller/merchant/PhortuneMerchantController.php', @@ -4381,10 +4382,12 @@ phutil_register_library_map(array( 'PhortuneMerchantInvoiceEmailTransaction' => 'applications/phortune/xaction/PhortuneMerchantInvoiceEmailTransaction.php', 'PhortuneMerchantInvoiceFooterTransaction' => 'applications/phortune/xaction/PhortuneMerchantInvoiceFooterTransaction.php', 'PhortuneMerchantListController' => 'applications/phortune/controller/merchant/PhortuneMerchantListController.php', + 'PhortuneMerchantManagerController' => 'applications/phortune/controller/merchant/PhortuneMerchantManagerController.php', 'PhortuneMerchantNameTransaction' => 'applications/phortune/xaction/PhortuneMerchantNameTransaction.php', 'PhortuneMerchantPHIDType' => 'applications/phortune/phid/PhortuneMerchantPHIDType.php', 'PhortuneMerchantPictureController' => 'applications/phortune/controller/merchant/PhortuneMerchantPictureController.php', 'PhortuneMerchantPictureTransaction' => 'applications/phortune/xaction/PhortuneMerchantPictureTransaction.php', + 'PhortuneMerchantProfileController' => 'applications/phortune/controller/merchant/PhortuneMerchantProfileController.php', 'PhortuneMerchantQuery' => 'applications/phortune/query/PhortuneMerchantQuery.php', 'PhortuneMerchantSearchEngine' => 'applications/phortune/query/PhortuneMerchantSearchEngine.php', 'PhortuneMerchantTransaction' => 'applications/phortune/storage/PhortuneMerchantTransaction.php', @@ -9832,6 +9835,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), + 'PhortuneMerchantAddManagerController' => 'PhortuneController', 'PhortuneMerchantCapability' => 'PhabricatorPolicyCapability', 'PhortuneMerchantContactInfoTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantController' => 'PhortuneController', @@ -9840,20 +9844,22 @@ phutil_register_library_map(array( 'PhortuneMerchantEditEngine' => 'PhabricatorEditEngine', 'PhortuneMerchantEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneMerchantHasMemberEdgeType' => 'PhabricatorEdgeType', - 'PhortuneMerchantInvoiceCreateController' => 'PhortuneMerchantController', + 'PhortuneMerchantInvoiceCreateController' => 'PhortuneMerchantProfileController', 'PhortuneMerchantInvoiceEmailTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantInvoiceFooterTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantListController' => 'PhortuneMerchantController', + 'PhortuneMerchantManagerController' => 'PhortuneMerchantProfileController', 'PhortuneMerchantNameTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantPHIDType' => 'PhabricatorPHIDType', - 'PhortuneMerchantPictureController' => 'PhortuneMerchantController', + 'PhortuneMerchantPictureController' => 'PhortuneMerchantProfileController', 'PhortuneMerchantPictureTransaction' => 'PhortuneMerchantTransactionType', + 'PhortuneMerchantProfileController' => 'PhortuneController', 'PhortuneMerchantQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneMerchantSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhortuneMerchantTransaction' => 'PhabricatorModularTransaction', 'PhortuneMerchantTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortuneMerchantTransactionType' => 'PhabricatorModularTransactionType', - 'PhortuneMerchantViewController' => 'PhortuneMerchantController', + 'PhortuneMerchantViewController' => 'PhortuneMerchantProfileController', 'PhortuneMonthYearExpiryControl' => 'AphrontFormControl', 'PhortuneOrderTableView' => 'AphrontView', 'PhortunePayPalPaymentProvider' => 'PhortunePaymentProvider', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 0733d65ac4..4d6030d21d 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -88,6 +88,10 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { => 'PhortuneMerchantEditController', 'orders/(?P\d+)/(?:query/(?P[^/]+)/)?' => 'PhortuneCartListController', + 'manager/' => array( + '(?:(?P\d+)/)?' => 'PhortuneMerchantManagerController', + 'add/(?:(?P\d+)/)?' => 'PhortuneMerchantAddManagerController', + ), '(?P\d+)/' => array( 'cart/(?P\d+)/' => array( '' => 'PhortuneCartViewController', diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantAddManagerController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantAddManagerController.php new file mode 100644 index 0000000000..3ef0a53874 --- /dev/null +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantAddManagerController.php @@ -0,0 +1,76 @@ +getViewer(); + $id = $request->getURIData('id'); + + $merchant = id(new PhortuneMerchantQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->needProfileImage(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$merchant) { + return new Aphront404Response(); + } + + $v_members = array(); + $e_members = null; + $merchant_uri = $this->getApplicationURI("/merchant/manager/{$id}/"); + + if ($request->isFormPost()) { + $xactions = array(); + $v_members = $request->getArr('memberPHIDs'); + $type_edge = PhabricatorTransactions::TYPE_EDGE; + + $xactions[] = id(new PhortuneMerchantTransaction()) + ->setTransactionType($type_edge) + ->setMetadataValue( + 'edge:type', + PhortuneMerchantHasMemberEdgeType::EDGECONST) + ->setNewValue( + array( + '+' => array_fuse($v_members), + )); + + $editor = id(new PhortuneMerchantEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + try { + $editor->applyTransactions($merchant, $xactions); + + return id(new AphrontRedirectResponse())->setURI($merchant_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + $e_members = $ex->getShortMessage($type_edge); + } + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new PhabricatorPeopleDatasource()) + ->setLabel(pht('Members')) + ->setName('memberPHIDs') + ->setValue($v_members) + ->setError($e_members)); + + return $this->newDialog() + ->setTitle(pht('Add New Manager')) + ->appendForm($form) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->addCancelButton($merchant_uri) + ->addSubmitButton(pht('Add Manager')); + + } + +} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php index ccdc4f7ac1..300525cdd2 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php @@ -1,7 +1,7 @@ getUser(); @@ -11,6 +11,7 @@ final class PhortuneMerchantInvoiceCreateController return new Aphront404Response(); } + $this->setMerchant($merchant); $merchant_id = $merchant->getID(); $cancel_uri = $this->getApplicationURI("/merchant/{$merchant_id}/"); @@ -88,8 +89,7 @@ final class PhortuneMerchantInvoiceCreateController $title = pht('New Invoice'); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($merchant->getName()); - $crumbs->setBorder(true); + $crumbs->addTextCrumb($title); $v_title = $request->getStr('title'); $e_title = true; @@ -245,9 +245,12 @@ final class PhortuneMerchantInvoiceCreateController $box, )); + $navigation = $this->buildSideNavView('orders'); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($navigation) ->appendChild($view); } diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantManagerController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantManagerController.php new file mode 100644 index 0000000000..0b9c0c6598 --- /dev/null +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantManagerController.php @@ -0,0 +1,91 @@ +getViewer(); + $id = $request->getURIData('id'); + + $merchant = id(new PhortuneMerchantQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->needProfileImage(true) + ->executeOne(); + if (!$merchant) { + return new Aphront404Response(); + } + + $this->setMerchant($merchant); + $header = $this->buildHeaderView(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Managers')); + + $header = $this->buildHeaderView(); + $members = $this->buildMembersSection($merchant); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $members, + )); + + $navigation = $this->buildSideNavView('managers'); + + return $this->newPage() + ->setTitle(pht('Managers')) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + + } + + private function buildMembersSection(PhortuneMerchant $merchant) { + $viewer = $this->getViewer(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $merchant, + PhabricatorPolicyCapability::CAN_EDIT); + + $id = $merchant->getID(); + + $add = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('New Manager')) + ->setIcon('fa-plus') + ->setWorkflow(true) + ->setHref("/phortune/merchant/manager/add/{$id}/"); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Merchant Account Managers')) + ->addActionLink($add); + + $list = id(new PHUIObjectItemListView()) + ->setUser($viewer); + + $member_phids = $merchant->getMemberPHIDs(); + $handles = $viewer->loadHandles($member_phids); + + foreach ($member_phids as $member_phid) { + $image_uri = $handles[$member_phid]->getImageURI(); + $image_href = $handles[$member_phid]->getURI(); + $person = $handles[$member_phid]; + + $member = id(new PHUIObjectItemView()) + ->setImageURI($image_uri) + ->setHref($image_href) + ->setHeader($person->getFullName()) + ->addAttribute(pht('Merchant Manager')); + + $list->addItem($member); + } + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); + } + +} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php index 587395fb2c..469887172f 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php @@ -1,7 +1,7 @@ getViewer(); @@ -21,6 +21,7 @@ final class PhortuneMerchantPictureController return new Aphront404Response(); } + $this->setMerchant($merchant); $uri = $merchant->getURI(); $supported_formats = PhabricatorFile::getTransformableImageFormats(); @@ -92,7 +93,7 @@ final class PhortuneMerchantPictureController } } - $title = pht('Edit Merchant Picture'); + $title = pht('Edit Logo'); $form = id(new PHUIFormLayoutView()) ->setUser($viewer); @@ -208,12 +209,10 @@ final class PhortuneMerchantPictureController ->setForm($upload_form); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($merchant->getName(), $uri); - $crumbs->addTextCrumb(pht('Merchant Logo')); - $crumbs->setBorder(true); + $crumbs->addTextCrumb(pht('Edit Logo')); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Edit Merchant Logo')) + ->setHeader(pht('Edit Logo')) ->setHeaderIcon('fa-camera'); $view = id(new PHUITwoColumnView()) @@ -223,9 +222,12 @@ final class PhortuneMerchantPictureController $upload_box, )); + $navigation = $this->buildSideNavView(); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($navigation) ->appendChild( array( $view, diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantProfileController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantProfileController.php new file mode 100644 index 0000000000..45911bfef9 --- /dev/null +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantProfileController.php @@ -0,0 +1,92 @@ +merchant = $merchant; + return $this; + } + + public function getMerchant() { + return $this->merchant; + } + + public function buildApplicationMenu() { + return $this->buildSideNavView()->getMenu(); + } + + protected function buildHeaderView() { + $viewer = $this->getViewer(); + $merchant = $this->getMerchant(); + $title = $merchant->getName(); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setUser($viewer) + ->setPolicyObject($merchant) + ->setImage($merchant->getProfileImageURI()); + + return $header; + } + + protected function buildApplicationCrumbs() { + $merchant = $this->getMerchant(); + $id = $merchant->getID(); + $merchant_uri = $this->getApplicationURI("/merchant/{$id}/"); + + $crumbs = parent::buildApplicationCrumbs(); + $crumbs->addTextCrumb($merchant->getName(), $merchant_uri); + $crumbs->setBorder(true); + return $crumbs; + } + + protected function buildSideNavView($filter = null) { + $viewer = $this->getViewer(); + $merchant = $this->getMerchant(); + $id = $merchant->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $merchant, + PhabricatorPolicyCapability::CAN_EDIT); + + $nav = id(new AphrontSideNavFilterView()) + ->setBaseURI(new PhutilURI($this->getApplicationURI())); + + $nav->addLabel(pht('Merchant')); + + $nav->addFilter( + 'overview', + pht('Overview'), + $this->getApplicationURI("/merchant/{$id}/"), + 'fa-building-o'); + + if ($can_edit) { + $nav->addFilter( + 'orders', + pht('Orders'), + $this->getApplicationURI("merchant/orders/{$id}/"), + 'fa-retweet'); + + $nav->addFilter( + 'subscriptions', + pht('Subscriptions'), + $this->getApplicationURI("merchant/{$id}/subscription/"), + 'fa-shopping-cart'); + + $nav->addFilter( + 'managers', + pht('Managers'), + $this->getApplicationURI("/merchant/manager/{$id}/"), + 'fa-group'); + } + + $nav->selectFilter($filter); + + return $nav; + } + +} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php index 230e36a47a..10dee4b0ea 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php @@ -1,7 +1,7 @@ getViewer(); @@ -16,21 +16,15 @@ final class PhortuneMerchantViewController return new Aphront404Response(); } + $this->setMerchant($merchant); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($merchant->getName()); - $crumbs->setBorder(true); + $header = $this->buildHeaderView(); $title = pht( 'Merchant %d %s', $merchant->getID(), $merchant->getName()); - $header = id(new PHUIHeaderView()) - ->setHeader($merchant->getName()) - ->setUser($viewer) - ->setPolicyObject($merchant) - ->setImage($merchant->getProfileImageURI()); - $providers = id(new PhortunePaymentProviderConfigQuery()) ->setViewer($viewer) ->withMerchantPHIDs(array($merchant->getPHID())) @@ -48,6 +42,8 @@ final class PhortuneMerchantViewController new PhortuneMerchantTransactionQuery()); $timeline->setShouldTerminate(true); + $navigation = $this->buildSideNavView('overview'); + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) @@ -60,6 +56,7 @@ final class PhortuneMerchantViewController return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($navigation) ->appendChild($view); } @@ -196,22 +193,6 @@ final class PhortuneMerchantViewController ->setWorkflow(!$can_edit) ->setHref($this->getApplicationURI("merchant/picture/{$id}/"))); - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View Orders')) - ->setIcon('fa-shopping-cart') - ->setHref($this->getApplicationURI("merchant/orders/{$id}/")) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View Subscriptions')) - ->setIcon('fa-moon-o') - ->setHref($this->getApplicationURI("merchant/{$id}/subscription/")) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('New Invoice')) From eeef60a67867beeedd2c3144b52f4e637e9e694a Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 11 Apr 2017 14:53:20 -0700 Subject: [PATCH 18/52] Update "bin/policy show" to use PolicyCodex Summary: Fixes T12541. `describeAutomaticCapability()` is no longer required to implement `PolicyInterface`. Use PolicyCodex instead. Test Plan: {F4889642} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12541 Differential Revision: https://secure.phabricator.com/D17658 --- ...habricatorPolicyManagementShowWorkflow.php | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/applications/policy/management/PhabricatorPolicyManagementShowWorkflow.php b/src/applications/policy/management/PhabricatorPolicyManagementShowWorkflow.php index 1529d4904c..208f1ae964 100644 --- a/src/applications/policy/management/PhabricatorPolicyManagementShowWorkflow.php +++ b/src/applications/policy/management/PhabricatorPolicyManagementShowWorkflow.php @@ -65,16 +65,20 @@ final class PhabricatorPolicyManagementShowWorkflow $console->writeOut(" %s\n", PhabricatorPolicy::getPolicyExplanation($viewer, $policy->getPHID())); $console->writeOut("\n"); - - $more = (array)$object->describeAutomaticCapability($capability); - if ($more) { - foreach ($more as $line) { - $console->writeOut(" %s\n", $line); - } - $console->writeOut("\n"); - } } + if ($object instanceof PhabricatorPolicyCodexInterface) { + $codex = PhabricatorPolicyCodex::newFromObject($object, $viewer); + + $rules = $codex->getPolicySpecialRuleDescriptions(); + foreach ($rules as $rule) { + echo tsprintf( + " - %s\n", + $rule->getDescription()); + } + + echo "\n"; + } } } From 149c1a6de75c2011cc45e2d0c9701f330dfe3340 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 11 Apr 2017 22:46:40 +0000 Subject: [PATCH 19/52] Correctly initialize new PhortuneAccount automatically Summary: There is currently a validation error triggered if you initialize a new account without a member set. I think this is the correct fix, but let me know. Test Plan: truncate phortune_account database, navigate to phortune, see account automatically created to "Default Account". Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17657 --- src/applications/phortune/storage/PhortuneAccount.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/phortune/storage/PhortuneAccount.php b/src/applications/phortune/storage/PhortuneAccount.php index 822673ea94..0560104972 100644 --- a/src/applications/phortune/storage/PhortuneAccount.php +++ b/src/applications/phortune/storage/PhortuneAccount.php @@ -17,7 +17,7 @@ final class PhortuneAccount extends PhortuneDAO public static function initializeNewAccount(PhabricatorUser $actor) { $account = id(new PhortuneAccount()); - $account->memberPHIDs = array(); + $account->memberPHIDs = array($actor->getPHID() => $actor->getPHID()); return $account; } From 6886e9c12d8f1739e54691717e198807ec701c22 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Tue, 11 Apr 2017 16:38:11 -0700 Subject: [PATCH 20/52] Remove "Destroy" action for Countdown objects Summary: fixes T12523 Test Plan: - view Countdown edit screen, Destroy action missing - checked that `./bin/remove destroy ` removes the DB rows as expected Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12523 Differential Revision: https://secure.phabricator.com/D17659 --- src/__phutil_library_map__.php | 3 +- .../PhabricatorCountdownApplication.php | 2 - .../PhabricatorCountdownController.php | 2 - .../PhabricatorCountdownDeleteController.php | 45 ------------------- .../PhabricatorCountdownViewController.php | 8 ---- .../storage/PhabricatorCountdown.php | 14 +++++- 6 files changed, 14 insertions(+), 60 deletions(-) delete mode 100644 src/applications/countdown/controller/PhabricatorCountdownDeleteController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 399b143451..cd37164ba2 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2408,7 +2408,6 @@ phutil_register_library_map(array( 'PhabricatorCountdownDAO' => 'applications/countdown/storage/PhabricatorCountdownDAO.php', 'PhabricatorCountdownDefaultEditCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultEditCapability.php', 'PhabricatorCountdownDefaultViewCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultViewCapability.php', - 'PhabricatorCountdownDeleteController' => 'applications/countdown/controller/PhabricatorCountdownDeleteController.php', 'PhabricatorCountdownEditController' => 'applications/countdown/controller/PhabricatorCountdownEditController.php', 'PhabricatorCountdownEditEngine' => 'applications/countdown/editor/PhabricatorCountdownEditEngine.php', 'PhabricatorCountdownEditor' => 'applications/countdown/editor/PhabricatorCountdownEditor.php', @@ -7492,6 +7491,7 @@ phutil_register_library_map(array( 'PhabricatorTokenReceiverInterface', 'PhabricatorSpacesInterface', 'PhabricatorProjectInterface', + 'PhabricatorDestructibleInterface', ), 'PhabricatorCountdownApplication' => 'PhabricatorApplication', 'PhabricatorCountdownController' => 'PhabricatorController', @@ -7499,7 +7499,6 @@ phutil_register_library_map(array( 'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO', 'PhabricatorCountdownDefaultEditCapability' => 'PhabricatorPolicyCapability', 'PhabricatorCountdownDefaultViewCapability' => 'PhabricatorPolicyCapability', - 'PhabricatorCountdownDeleteController' => 'PhabricatorCountdownController', 'PhabricatorCountdownEditController' => 'PhabricatorCountdownController', 'PhabricatorCountdownEditEngine' => 'PhabricatorEditEngine', 'PhabricatorCountdownEditor' => 'PhabricatorApplicationTransactionEditor', diff --git a/src/applications/countdown/application/PhabricatorCountdownApplication.php b/src/applications/countdown/application/PhabricatorCountdownApplication.php index a446c88a88..2e7b8d1bbd 100644 --- a/src/applications/countdown/application/PhabricatorCountdownApplication.php +++ b/src/applications/countdown/application/PhabricatorCountdownApplication.php @@ -48,8 +48,6 @@ final class PhabricatorCountdownApplication extends PhabricatorApplication { => 'PhabricatorCountdownCommentController', $this->getEditRoutePattern('edit/') => 'PhabricatorCountdownEditController', - 'delete/(?P[1-9]\d*)/' - => 'PhabricatorCountdownDeleteController', ), ); } diff --git a/src/applications/countdown/controller/PhabricatorCountdownController.php b/src/applications/countdown/controller/PhabricatorCountdownController.php index 4d09d7ed5b..c6afe744b5 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownController.php @@ -6,6 +6,4 @@ abstract class PhabricatorCountdownController extends PhabricatorController { return $this->newApplicationMenu() ->setSearchEngine(new PhabricatorCountdownSearchEngine()); } - - } diff --git a/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php b/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php deleted file mode 100644 index 81129f3beb..0000000000 --- a/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php +++ /dev/null @@ -1,45 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $countdown = id(new PhabricatorCountdownQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - - if (!$countdown) { - return new Aphront404Response(); - } - - if ($request->isFormPost()) { - $countdown->delete(); - return id(new AphrontRedirectResponse()) - ->setURI('/countdown/'); - } - - $inst = pht( - 'Are you sure you want to delete the countdown %s?', - $countdown->getTitle()); - - $dialog = new AphrontDialogView(); - $dialog->setUser($request->getUser()); - $dialog->setTitle(pht('Really delete this countdown?')); - $dialog->appendChild(phutil_tag('p', array(), $inst)); - $dialog->addSubmitButton(pht('Delete')); - $dialog->addCancelButton('/countdown/'); - $dialog->setSubmitURI($request->getPath()); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - -} diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php index 56911bf43b..3e8f28e0cf 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php @@ -102,14 +102,6 @@ final class PhabricatorCountdownViewController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $curtain->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-times') - ->setName(pht('Delete Countdown')) - ->setHref($this->getApplicationURI("delete/{$id}/")) - ->setDisabled(!$can_edit) - ->setWorkflow(true)); - return $curtain; } diff --git a/src/applications/countdown/storage/PhabricatorCountdown.php b/src/applications/countdown/storage/PhabricatorCountdown.php index 34249b9ab4..fc7218aeac 100644 --- a/src/applications/countdown/storage/PhabricatorCountdown.php +++ b/src/applications/countdown/storage/PhabricatorCountdown.php @@ -8,7 +8,8 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO PhabricatorApplicationTransactionInterface, PhabricatorTokenReceiverInterface, PhabricatorSpacesInterface, - PhabricatorProjectInterface { + PhabricatorProjectInterface, + PhabricatorDestructibleInterface { protected $title; protected $authorPHID; @@ -141,8 +142,19 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO /* -( PhabricatorSpacesInterface )------------------------------------------- */ + public function getSpacePHID() { return $this->spacePHID; } +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + + $this->openTransaction(); + $this->delete(); + $this->saveTransaction(); + } } From cd7547dc5760bd0fde42f38118dcb9af3ddc17a0 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 11 Apr 2017 16:51:02 -0700 Subject: [PATCH 21/52] Update UI for PhortuneAccount Summary: Primarily, this splits individual sections of the single account page into a more managable and robust sidenav for subscriptions, billing, and managers. The functionality on the subpages is light, but I expect to build on then in coming diffs. This also starts building out a more effective "status" area on the lead page. Test Plan: - Load up default account - Make some edits - Click on each of the new navigation items - Verify links to "see all" work - Test overdue and no payment states for status {F4337317} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17589 --- src/__phutil_library_map__.php | 12 +- .../PhabricatorPhortuneApplication.php | 10 + .../PhortuneAccountAddManagerController.php | 75 ++++++ .../PhortuneAccountBillingController.php | 178 ++++++++++++++ .../PhortuneAccountManagerController.php | 101 ++++++++ .../PhortuneAccountProfileController.php | 84 +++++++ .../PhortuneAccountSubscriptionController.php | 79 ++++++ .../account/PhortuneAccountViewController.php | 228 +++--------------- .../phortune/view/PhortuneChargeTableView.php | 1 + 9 files changed, 574 insertions(+), 194 deletions(-) create mode 100644 src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php create mode 100644 src/applications/phortune/controller/account/PhortuneAccountBillingController.php create mode 100644 src/applications/phortune/controller/account/PhortuneAccountManagerController.php create mode 100644 src/applications/phortune/controller/account/PhortuneAccountProfileController.php create mode 100644 src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index cd37164ba2..91ceb48819 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4319,15 +4319,20 @@ phutil_register_library_map(array( 'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php', 'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php', 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', + 'PhortuneAccountAddManagerController' => 'applications/phortune/controller/account/PhortuneAccountAddManagerController.php', + 'PhortuneAccountBillingController' => 'applications/phortune/controller/account/PhortuneAccountBillingController.php', 'PhortuneAccountChargeListController' => 'applications/phortune/controller/account/PhortuneAccountChargeListController.php', 'PhortuneAccountEditController' => 'applications/phortune/controller/account/PhortuneAccountEditController.php', 'PhortuneAccountEditEngine' => 'applications/phortune/editor/PhortuneAccountEditEngine.php', 'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php', 'PhortuneAccountHasMemberEdgeType' => 'applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php', 'PhortuneAccountListController' => 'applications/phortune/controller/account/PhortuneAccountListController.php', + 'PhortuneAccountManagerController' => 'applications/phortune/controller/account/PhortuneAccountManagerController.php', 'PhortuneAccountNameTransaction' => 'applications/phortune/xaction/PhortuneAccountNameTransaction.php', 'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php', + 'PhortuneAccountProfileController' => 'applications/phortune/controller/account/PhortuneAccountProfileController.php', 'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php', + 'PhortuneAccountSubscriptionController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionController.php', 'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php', 'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php', 'PhortuneAccountTransactionType' => 'applications/phortune/xaction/PhortuneAccountTransactionType.php', @@ -9774,19 +9779,24 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), + 'PhortuneAccountAddManagerController' => 'PhortuneController', + 'PhortuneAccountBillingController' => 'PhortuneAccountProfileController', 'PhortuneAccountChargeListController' => 'PhortuneController', 'PhortuneAccountEditController' => 'PhortuneController', 'PhortuneAccountEditEngine' => 'PhabricatorEditEngine', 'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneAccountHasMemberEdgeType' => 'PhabricatorEdgeType', 'PhortuneAccountListController' => 'PhortuneController', + 'PhortuneAccountManagerController' => 'PhortuneAccountProfileController', 'PhortuneAccountNameTransaction' => 'PhortuneAccountTransactionType', 'PhortuneAccountPHIDType' => 'PhabricatorPHIDType', + 'PhortuneAccountProfileController' => 'PhortuneController', 'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhortuneAccountSubscriptionController' => 'PhortuneAccountProfileController', 'PhortuneAccountTransaction' => 'PhabricatorModularTransaction', 'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortuneAccountTransactionType' => 'PhabricatorModularTransactionType', - 'PhortuneAccountViewController' => 'PhortuneController', + 'PhortuneAccountViewController' => 'PhortuneAccountProfileController', 'PhortuneAdHocCart' => 'PhortuneCartImplementation', 'PhortuneAdHocProduct' => 'PhortuneProductImplementation', 'PhortuneCart' => array( diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 4d6030d21d..0ffc138f65 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -69,6 +69,16 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { '' => 'PhortuneAccountListController', $this->getEditRoutePattern('edit/') => 'PhortuneAccountEditController', + 'edit/(?:(?P\d+)/)?' => 'PhortuneAccountEditController', + 'add/manager/(?:(?P\d+)/)?' + => 'PhortuneAccountAddManagerController', + 'billing/(?:(?P\d+)/)?' => 'PhortuneAccountBillingController', + 'subscription/(?:(?P\d+)/)?' + => 'PhortuneAccountSubscriptionController', + 'manager/' => array( + '(?:(?P\d+)/)?' => 'PhortuneAccountManagerController', + 'add/(?:(?P\d+)/)?' => 'PhortuneAccountAddManagerController', + ), ), 'product/' => array( '' => 'PhortuneProductListController', diff --git a/src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php b/src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php new file mode 100644 index 0000000000..34bb0a480b --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php @@ -0,0 +1,75 @@ +getViewer(); + $id = $request->getURIData('id'); + + $account = id(new PhortuneAccountQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$account) { + return new Aphront404Response(); + } + + $v_managers = array(); + $e_managers = null; + $account_uri = $this->getApplicationURI("/account/manager/{$id}/"); + + if ($request->isFormPost()) { + $xactions = array(); + $v_managers = $request->getArr('managerPHIDs'); + $type_edge = PhabricatorTransactions::TYPE_EDGE; + + $xactions[] = id(new PhortuneAccountTransaction()) + ->setTransactionType($type_edge) + ->setMetadataValue( + 'edge:type', + PhortuneAccountHasMemberEdgeType::EDGECONST) + ->setNewValue( + array( + '+' => array_fuse($v_managers), + )); + + $editor = id(new PhortuneAccountEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + try { + $editor->applyTransactions($account, $xactions); + + return id(new AphrontRedirectResponse())->setURI($account_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + $e_managers = $ex->getShortMessage($type_edge); + } + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new PhabricatorPeopleDatasource()) + ->setLabel(pht('Managers')) + ->setName('managerPHIDs') + ->setValue($v_managers) + ->setError($e_managers)); + + return $this->newDialog() + ->setTitle(pht('Add New Manager')) + ->appendForm($form) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->addCancelButton($account_uri) + ->addSubmitButton(pht('Add Manager')); + + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountBillingController.php b/src/applications/phortune/controller/account/PhortuneAccountBillingController.php new file mode 100644 index 0000000000..2d662668a4 --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountBillingController.php @@ -0,0 +1,178 @@ +getViewer(); + + // TODO: Currently, you must be able to edit an account to view the detail + // page, because the account must be broadly visible so merchants can + // process orders but merchants should not be able to see all the details + // of an account. Ideally this page should be visible to merchants, too, + // just with less information. + $can_edit = true; + + $account = id(new PhortuneAccountQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$account) { + return new Aphront404Response(); + } + + $this->setAccount($account); + $title = $account->getName(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Billing')); + + $header = $this->buildHeaderView(); + $methods = $this->buildPaymentMethodsSection($account); + $charge_history = $this->buildChargeHistorySection($account); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $methods, + $charge_history, + )); + + $navigation = $this->buildSideNavView('billing'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + + } + + private function buildPaymentMethodsSection(PhortuneAccount $account) { + $viewer = $this->getViewer(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $account, + PhabricatorPolicyCapability::CAN_EDIT); + + $id = $account->getID(); + + // TODO: Allow adding a card here directly + $add = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('New Payment Method')) + ->setIcon('fa-plus') + ->setHref($this->getApplicationURI("{$id}/card/new/")); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Payment Methods')); + + $list = id(new PHUIObjectItemListView()) + ->setUser($viewer) + ->setFlush(true) + ->setNoDataString( + pht('No payment methods associated with this account.')); + + $methods = id(new PhortunePaymentMethodQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->withStatuses( + array( + PhortunePaymentMethod::STATUS_ACTIVE, + )) + ->execute(); + + foreach ($methods as $method) { + $id = $method->getID(); + + $item = new PHUIObjectItemView(); + $item->setHeader($method->getFullDisplayName()); + + switch ($method->getStatus()) { + case PhortunePaymentMethod::STATUS_ACTIVE: + $item->setStatusIcon('fa-check green'); + + $disable_uri = $this->getApplicationURI('card/'.$id.'/disable/'); + $item->addAction( + id(new PHUIListItemView()) + ->setIcon('fa-times') + ->setHref($disable_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); + break; + case PhortunePaymentMethod::STATUS_DISABLED: + $item->setStatusIcon('fa-ban lightbluetext'); + $item->setDisabled(true); + break; + } + + $provider = $method->buildPaymentProvider(); + $item->addAttribute($provider->getPaymentMethodProviderDescription()); + + $edit_uri = $this->getApplicationURI('card/'.$id.'/edit/'); + + $item->addAction( + id(new PHUIListItemView()) + ->setIcon('fa-pencil') + ->setHref($edit_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + $list->addItem($item); + } + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); + } + + private function buildChargeHistorySection(PhortuneAccount $account) { + $viewer = $this->getViewer(); + + $charges = id(new PhortuneChargeQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->needCarts(true) + ->setLimit(10) + ->execute(); + + $phids = array(); + foreach ($charges as $charge) { + $phids[] = $charge->getProviderPHID(); + $phids[] = $charge->getCartPHID(); + $phids[] = $charge->getMerchantPHID(); + $phids[] = $charge->getPaymentMethodPHID(); + } + + $handles = $this->loadViewerHandles($phids); + + $charges_uri = $this->getApplicationURI($account->getID().'/charge/'); + + $table = id(new PhortuneChargeTableView()) + ->setUser($viewer) + ->setCharges($charges) + ->setHandles($handles); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Charge History')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-list') + ->setHref($charges_uri) + ->setText(pht('View All Charges'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountManagerController.php b/src/applications/phortune/controller/account/PhortuneAccountManagerController.php new file mode 100644 index 0000000000..b5f8a38f64 --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountManagerController.php @@ -0,0 +1,101 @@ +getViewer(); + + // TODO: Currently, you must be able to edit an account to view the detail + // page, because the account must be broadly visible so merchants can + // process orders but merchants should not be able to see all the details + // of an account. Ideally this page should be visible to merchants, too, + // just with less information. + $can_edit = true; + + $account = id(new PhortuneAccountQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$account) { + return new Aphront404Response(); + } + + $this->setAccount($account); + $title = $account->getName(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Managers')); + + $header = $this->buildHeaderView(); + $members = $this->buildMembersSection($account); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $members, + )); + + $navigation = $this->buildSideNavView('managers'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + + } + + private function buildMembersSection(PhortuneAccount $account) { + $viewer = $this->getViewer(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $account, + PhabricatorPolicyCapability::CAN_EDIT); + + $id = $account->getID(); + + $add = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('New Manager')) + ->setIcon('fa-plus') + ->setWorkflow(true) + ->setHref("/phortune/account/manager/add/{$id}/"); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Account Managers')) + ->addActionLink($add); + + $list = id(new PHUIObjectItemListView()) + ->setUser($viewer); + + $member_phids = $account->getMemberPHIDs(); + $handles = $viewer->loadHandles($member_phids); + + foreach ($member_phids as $member_phid) { + $image_uri = $handles[$member_phid]->getImageURI(); + $image_href = $handles[$member_phid]->getURI(); + $person = $handles[$member_phid]; + + $member = id(new PHUIObjectItemView()) + ->setImageURI($image_uri) + ->setHref($image_href) + ->setHeader($person->getFullName()) + ->addAttribute(pht('Account Manager')); + + $list->addItem($member); + } + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php new file mode 100644 index 0000000000..895b76bdc6 --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php @@ -0,0 +1,84 @@ +account = $account; + return $this; + } + + public function getAccount() { + return $this->account; + } + + public function buildApplicationMenu() { + return $this->buildSideNavView()->getMenu(); + } + + protected function buildHeaderView() { + $viewer = $this->getViewer(); + $account = $this->getAccount(); + $title = $account->getName(); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setHeader($title) + ->setHeaderIcon('fa-user-circle'); + + return $header; + } + + protected function buildApplicationCrumbs() { + $account = $this->getAccount(); + $id = $account->getID(); + $account_uri = $this->getApplicationURI("/{$id}/"); + + $crumbs = parent::buildApplicationCrumbs(); + $crumbs->addTextCrumb($account->getName(), $account_uri); + $crumbs->setBorder(true); + return $crumbs; + } + + protected function buildSideNavView($filter = null) { + $viewer = $this->getViewer(); + $account = $this->getAccount(); + $id = $account->getID(); + + $nav = id(new AphrontSideNavFilterView()) + ->setBaseURI(new PhutilURI($this->getApplicationURI())); + + $nav->addLabel(pht('Account')); + + $nav->addFilter( + 'overview', + pht('Overview'), + $this->getApplicationURI("/{$id}/"), + 'fa-user-circle'); + + $nav->addFilter( + 'subscriptions', + pht('Subscriptions'), + $this->getApplicationURI("/account/subscription/{$id}/"), + 'fa-retweet'); + + $nav->addFilter( + 'billing', + pht('Billing / History'), + $this->getApplicationURI("/account/billing/{$id}/"), + 'fa-credit-card'); + + $nav->addFilter( + 'managers', + pht('Managers'), + $this->getApplicationURI("/account/manager/{$id}/"), + 'fa-group'); + + $nav->selectFilter($filter); + + return $nav; + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php new file mode 100644 index 0000000000..145c5eb5db --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php @@ -0,0 +1,79 @@ +getViewer(); + + // TODO: Currently, you must be able to edit an account to view the detail + // page, because the account must be broadly visible so merchants can + // process orders but merchants should not be able to see all the details + // of an account. Ideally this page should be visible to merchants, too, + // just with less information. + $can_edit = true; + + $account = id(new PhortuneAccountQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$account) { + return new Aphront404Response(); + } + + $this->setAccount($account); + $title = $account->getName(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Subscriptions')); + + $header = $this->buildHeaderView(); + $subscriptions = $this->buildSubscriptionsSection($account); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $subscriptions, + )); + + $navigation = $this->buildSideNavView('subscriptions'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + + } + + private function buildSubscriptionsSection(PhortuneAccount $account) { + $viewer = $this->getViewer(); + + $subscriptions = id(new PhortuneSubscriptionQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->setLimit(25) + ->execute(); + + $handles = $this->loadViewerHandles(mpull($subscriptions, 'getPHID')); + + $table = id(new PhortuneSubscriptionTableView()) + ->setUser($viewer) + ->setHandles($handles) + ->setSubscriptions($subscriptions); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Subscriptions')); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountViewController.php b/src/applications/phortune/controller/account/PhortuneAccountViewController.php index d0ef03fc9a..efb94551c3 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountViewController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountViewController.php @@ -1,9 +1,11 @@ getViewer(); + $id = $request->getURIData('accountID'); // TODO: Currently, you must be able to edit an account to view the detail // page, because the account must be broadly visible so merchants can @@ -14,7 +16,7 @@ final class PhortuneAccountViewController extends PhortuneController { $account = id(new PhortuneAccountQuery()) ->setViewer($viewer) - ->withIDs(array($request->getURIData('accountID'))) + ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -25,6 +27,7 @@ final class PhortuneAccountViewController extends PhortuneController { return new Aphront404Response(); } + $this->setAccount($account); $title = $account->getName(); $invoices = id(new PhortuneCartQuery()) @@ -35,19 +38,14 @@ final class PhortuneAccountViewController extends PhortuneController { ->execute(); $crumbs = $this->buildApplicationCrumbs(); - $this->addAccountCrumb($crumbs, $account, $link = false); $crumbs->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-credit-card'); + $header = $this->buildHeaderView(); - $curtain = $this->buildCurtainView($account, $invoices); + $curtain = $this->buildCurtainView($account); + $status = $this->buildStatusView($account, $invoices); $invoices = $this->buildInvoicesSection($account, $invoices); $purchase_history = $this->buildPurchaseHistorySection($account); - $charge_history = $this->buildChargeHistorySection($account); - $subscriptions = $this->buildSubscriptionsSection($account); - $payment_methods = $this->buildPaymentMethodsSection($account); $timeline = $this->buildTransactionTimeline( $account, @@ -58,22 +56,34 @@ final class PhortuneAccountViewController extends PhortuneController { ->setHeader($header) ->setCurtain($curtain) ->setMainColumn(array( + $status, $invoices, $purchase_history, - $charge_history, - $subscriptions, - $payment_methods, $timeline, )); + $navigation = $this->buildSideNavView('overview'); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) + ->setNavigation($navigation) ->appendChild($view); } - private function buildCurtainView(PhortuneAccount $account, $invoices) { + private function buildStatusView(PhortuneAccount $account, $invoices) { + $status_items = $this->getStatusItemsForAccount($account, $invoices); + $view = array(); + foreach ($status_items as $item) { + $view[] = id(new PHUIInfoView()) + ->setSeverity(idx($item, 'severity')) + ->appendChild(idx($item, 'note')); + } + return $view; + } + + private function buildCurtainView(PhortuneAccount $account) { $viewer = $this->getViewer(); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -92,19 +102,6 @@ final class PhortuneAccountViewController extends PhortuneController { ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $status_items = $this->getStatusItemsForAccount($account, $invoices); - $status_view = new PHUIStatusListView(); - foreach ($status_items as $item) { - $status_view->addItem( - id(new PHUIStatusItemView()) - ->setIcon( - idx($item, 'icon'), - idx($item, 'color'), - idx($item, 'label')) - ->setTarget(idx($item, 'target')) - ->setNote(idx($item, 'note'))); - } - $member_phids = $account->getMemberPHIDs(); $handles = $viewer->loadHandles($member_phids); @@ -124,10 +121,6 @@ final class PhortuneAccountViewController extends PhortuneController { $member_list->addItem($member); } - $curtain->newPanel() - ->setHeaderText(pht('Status')) - ->appendChild($status_view); - $curtain->newPanel() ->setHeaderText(pht('Managers')) ->appendChild($member_list); @@ -135,79 +128,6 @@ final class PhortuneAccountViewController extends PhortuneController { return $curtain; } - private function buildPaymentMethodsSection(PhortuneAccount $account) { - $viewer = $this->getViewer(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $account, - PhabricatorPolicyCapability::CAN_EDIT); - - $id = $account->getID(); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Payment Methods')); - - $list = id(new PHUIObjectItemListView()) - ->setUser($viewer) - ->setFlush(true) - ->setNoDataString( - pht('No payment methods associated with this account.')); - - $methods = id(new PhortunePaymentMethodQuery()) - ->setViewer($viewer) - ->withAccountPHIDs(array($account->getPHID())) - ->withStatuses( - array( - PhortunePaymentMethod::STATUS_ACTIVE, - )) - ->execute(); - - foreach ($methods as $method) { - $id = $method->getID(); - - $item = new PHUIObjectItemView(); - $item->setHeader($method->getFullDisplayName()); - - switch ($method->getStatus()) { - case PhortunePaymentMethod::STATUS_ACTIVE: - $item->setStatusIcon('fa-check green'); - - $disable_uri = $this->getApplicationURI('card/'.$id.'/disable/'); - $item->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-times') - ->setHref($disable_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(true)); - break; - case PhortunePaymentMethod::STATUS_DISABLED: - $item->setStatusIcon('fa-ban lightbluetext'); - $item->setDisabled(true); - break; - } - - $provider = $method->buildPaymentProvider(); - $item->addAttribute($provider->getPaymentMethodProviderDescription()); - - $edit_uri = $this->getApplicationURI('card/'.$id.'/edit/'); - - $item->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-pencil') - ->setHref($edit_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - - $list->addItem($item); - } - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setObjectList($list); - } - private function buildInvoicesSection( PhortuneAccount $account, array $carts) { @@ -289,84 +209,6 @@ final class PhortuneAccountViewController extends PhortuneController { ->setTable($table); } - private function buildChargeHistorySection(PhortuneAccount $account) { - $viewer = $this->getViewer(); - - $charges = id(new PhortuneChargeQuery()) - ->setViewer($viewer) - ->withAccountPHIDs(array($account->getPHID())) - ->needCarts(true) - ->setLimit(10) - ->execute(); - - $phids = array(); - foreach ($charges as $charge) { - $phids[] = $charge->getProviderPHID(); - $phids[] = $charge->getCartPHID(); - $phids[] = $charge->getMerchantPHID(); - $phids[] = $charge->getPaymentMethodPHID(); - } - - $handles = $this->loadViewerHandles($phids); - - $charges_uri = $this->getApplicationURI($account->getID().'/charge/'); - - $table = id(new PhortuneChargeTableView()) - ->setUser($viewer) - ->setCharges($charges) - ->setHandles($handles); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Recent Charges')) - ->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-list') - ->setHref($charges_uri) - ->setText(pht('View All Charges'))); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); - } - - private function buildSubscriptionsSection(PhortuneAccount $account) { - $viewer = $this->getViewer(); - - $subscriptions = id(new PhortuneSubscriptionQuery()) - ->setViewer($viewer) - ->withAccountPHIDs(array($account->getPHID())) - ->setLimit(10) - ->execute(); - - $subscriptions_uri = $this->getApplicationURI( - $account->getID().'/subscription/'); - - $handles = $this->loadViewerHandles(mpull($subscriptions, 'getPHID')); - - $table = id(new PhortuneSubscriptionTableView()) - ->setUser($viewer) - ->setHandles($handles) - ->setSubscriptions($subscriptions); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Recent Subscriptions')) - ->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setIcon( - id(new PHUIIconView()) - ->setIcon('fa-list')) - ->setHref($subscriptions_uri) - ->setText(pht('View All Subscriptions'))); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); - } - protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); @@ -382,25 +224,25 @@ final class PhortuneAccountViewController extends PhortuneController { private function getStatusItemsForAccount( PhortuneAccount $account, array $invoices) { + $viewer = $this->getViewer(); assert_instances_of($invoices, 'PhortuneCart'); - $items = array(); + $methods = id(new PhortunePaymentMethodQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->withStatuses( + array( + PhortunePaymentMethod::STATUS_ACTIVE, + )) + ->execute(); + if ($invoices) { $items[] = array( - 'icon' => PHUIStatusItemView::ICON_WARNING, - 'color' => 'yellow', - 'target' => pht('Invoices'), + 'severity' => PHUIInfoView::SEVERITY_ERROR, 'note' => pht('You have %d unpaid invoice(s).', count($invoices)), ); - } else { - $items[] = array( - 'icon' => PHUIStatusItemView::ICON_ACCEPT, - 'color' => 'green', - 'target' => pht('Invoices'), - 'note' => pht('This account has no unpaid invoices.'), - ); } // TODO: If a payment method has expired or is expiring soon, we should diff --git a/src/applications/phortune/view/PhortuneChargeTableView.php b/src/applications/phortune/view/PhortuneChargeTableView.php index 4e82404cc6..663c470a81 100644 --- a/src/applications/phortune/view/PhortuneChargeTableView.php +++ b/src/applications/phortune/view/PhortuneChargeTableView.php @@ -55,6 +55,7 @@ final class PhortuneChargeTableView extends AphrontView { } $table = id(new AphrontTableView($rows)) + ->setNoDataString(pht('No charges found.')) ->setHeaders( array( pht('ID'), From 578ebe6c2fae3f40b3f0b555581970c9cd6773cd Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 11 Apr 2017 20:08:28 -0700 Subject: [PATCH 22/52] Clean up minor form spacing Summary: Drops the inset box shadow and bumps standard UI elements from 28px to 30px. more room for activities. Test Plan: Spaces, Editing tasks, typeaheads, mobile, desktop. {F4897792} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17660 --- resources/celerity/map.php | 14 +++++++------- webroot/rsrc/css/phui/phui-button.css | 8 ++++---- webroot/rsrc/css/phui/phui-comment-form.css | 4 ++-- webroot/rsrc/css/phui/phui-form.css | 8 ++------ 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 488b538f23..37c10ec9a1 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '82aca405', 'conpherence.pkg.js' => '281b1a73', - 'core.pkg.css' => 'b519db07', + 'core.pkg.css' => '476a4ec7', 'core.pkg.js' => 'fbc1c380', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '90b30783', @@ -133,10 +133,10 @@ return array( 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '269cbc99', - 'rsrc/css/phui/phui-button.css' => '14bfba79', + 'rsrc/css/phui/phui-button.css' => '96787bae', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', - 'rsrc/css/phui/phui-comment-form.css' => '7d903c2d', + 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb', 'rsrc/css/phui/phui-curtain-view.css' => '679743bb', @@ -146,7 +146,7 @@ return array( 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', 'rsrc/css/phui/phui-fontkit.css' => '1320ed01', 'rsrc/css/phui/phui-form-view.css' => '6175808d', - 'rsrc/css/phui/phui-form.css' => 'b62c01d8', + 'rsrc/css/phui/phui-form.css' => 'a5570f70', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', 'rsrc/css/phui/phui-header-view.css' => '9cf828ce', 'rsrc/css/phui/phui-hovercard.css' => 'ae091fc5', @@ -829,14 +829,14 @@ return array( 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '269cbc99', - 'phui-button-css' => '14bfba79', + 'phui-button-css' => '96787bae', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', 'phui-calendar-month-css' => '8e10e92c', 'phui-chart-css' => '6bf6f78e', 'phui-cms-css' => '504b4b23', - 'phui-comment-form-css' => '7d903c2d', + 'phui-comment-form-css' => '57af2e14', 'phui-comment-panel-css' => 'f50152ad', 'phui-crumbs-view-css' => '6ece3bbb', 'phui-curtain-view-css' => '679743bb', @@ -846,7 +846,7 @@ return array( 'phui-feed-story-css' => '44a9c8e9', 'phui-font-icon-base-css' => '870a7360', 'phui-fontkit-css' => '1320ed01', - 'phui-form-css' => 'b62c01d8', + 'phui-form-css' => 'a5570f70', 'phui-form-view-css' => '6175808d', 'phui-head-thing-view-css' => 'fd311e5f', 'phui-header-view-css' => '9cf828ce', diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/phui-button.css index 21bf741225..f726487a2d 100644 --- a/webroot/rsrc/css/phui/phui-button.css +++ b/webroot/rsrc/css/phui/phui-button.css @@ -41,7 +41,7 @@ input[type="submit"] { font-weight: bold; font-size: {$normalfontsize}; display: inline-block; - padding: 3px 12px 4px; + padding: 4px 16px 5px; text-align: center; white-space: nowrap; border-radius: 3px; @@ -285,7 +285,7 @@ a.policy-control .phui-button-text { .dropdown .caret { margin-top: 7px; - margin-left: 6px; + margin-right: -4px; } .small.dropdown .caret { @@ -304,8 +304,8 @@ a.policy-control .phui-button-text { .button .phui-icon-view { display: inline-block; position: absolute; - top: 6px; - left: 10px; + top: 7px; + left: 12px; } .button.icon-last .phui-icon-view { diff --git a/webroot/rsrc/css/phui/phui-comment-form.css b/webroot/rsrc/css/phui/phui-comment-form.css index a49d033c3e..860dc9dcaf 100644 --- a/webroot/rsrc/css/phui/phui-comment-form.css +++ b/webroot/rsrc/css/phui/phui-comment-form.css @@ -155,8 +155,8 @@ body.device .phui-box.phui-object-box.phui-comment-form-view { .phui-comment-form-view .phui-form-view label.aphront-form-label, .phui-comment-form-view .phui-form-view .aphront-form-error { - height: 26px; - line-height: 26px; + height: 28x; + line-height: 28px; padding: 0; } diff --git a/webroot/rsrc/css/phui/phui-form.css b/webroot/rsrc/css/phui/phui-form.css index 810ad32c2c..8a6f705ec9 100644 --- a/webroot/rsrc/css/phui/phui-form.css +++ b/webroot/rsrc/css/phui/phui-form.css @@ -20,7 +20,7 @@ input[type="tel"], input[type="color"], div.jx-tokenizer-container { display: inline-block; - height: 28px; + height: 30px; line-height: 18px; color: #333; vertical-align: middle; @@ -49,10 +49,6 @@ div.jx-tokenizer-container { border: 1px solid {$greyborder}; border-radius: 3px; - -webkit-box-shadow: inset 0 1px 1px rgba({$alphablack}, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba({$alphablack}, 0.075); - box-shadow: inset 0 1px 1px rgba({$alphablack}, 0.075); - -webkit-transition: border linear .05s, box-shadow linear .05s; -moz-transition: border linear .05s, box-shadow linear .05s; -o-transition: border linear .05s, box-shadow linear .05s; @@ -114,7 +110,7 @@ select { border-radius: 3px; color: {$darkbluetext}; border: 1px solid {$greyborder}; - height: 28px; + height: 30px; padding: 0 24px 0 8px; margin: 0; min-width: 180px; From 099c90e7ec9d60cb4c684ab19234c0afae326975 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 12 Apr 2017 09:07:45 -0700 Subject: [PATCH 23/52] Remove "First Message" from New Conpherence Room workflow Summary: Removes this feature, makes creating a room simpler and less confusing. Test Plan: Create a room on Conpherence. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17661 --- .../ConpherenceNewRoomController.php | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceNewRoomController.php b/src/applications/conpherence/controller/ConpherenceNewRoomController.php index 25ae99a468..347c81f1e1 100644 --- a/src/applications/conpherence/controller/ConpherenceNewRoomController.php +++ b/src/applications/conpherence/controller/ConpherenceNewRoomController.php @@ -7,7 +7,6 @@ final class ConpherenceNewRoomController extends ConpherenceController { $title = pht('New Room'); $e_title = true; - $v_message = null; $validation_exception = null; $conpherence = ConpherenceThread::initializeNewRoom($user); @@ -39,15 +38,6 @@ final class ConpherenceNewRoomController extends ConpherenceController { ->setTransactionType(PhabricatorTransactions::TYPE_JOIN_POLICY) ->setNewValue($request->getStr('joinPolicy')); - $v_message = $request->getStr('message'); - if (strlen($v_message)) { - $message_xactions = $editor->generateTransactionsFromText( - $user, - $conpherence, - $v_message); - $xactions = array_merge($xactions, $message_xactions); - } - try { $editor ->setContentSourceFromRequest($request) @@ -125,13 +115,7 @@ final class ConpherenceNewRoomController extends ConpherenceController { ->setName('joinPolicy') ->setPolicyObject($conpherence) ->setCapability(PhabricatorPolicyCapability::CAN_JOIN) - ->setPolicies($policies)) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($user) - ->setName('message') - ->setLabel(pht('First Message')) - ->setValue($v_message)); + ->setPolicies($policies)); $dialog->appendChild($form); From 75303567b389ed38acb3df5d9a6b5f10feeb7cf6 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 12 Apr 2017 12:30:15 -0700 Subject: [PATCH 24/52] Add a Conpherence Profile Menu Item Summary: Builds a Conpherence Profile Menu Item, complete with counts for the unreads. This allows pinning to home as well as swapping out thread list in Conpherence for pinning eventually. Test Plan: Add a menu item, chat in room, log into other account, see room count. Room count disappears after viewing. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17662 --- resources/celerity/map.php | 6 +- src/__phutil_library_map__.php | 2 + .../PhabricatorConpherenceProfileMenuItem.php | 174 ++++++++++++++++++ .../rsrc/css/application/conpherence/menu.css | 8 +- 4 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 37c10ec9a1..d7ad426263 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => '82aca405', + 'conpherence.pkg.css' => 'b5ee2073', 'conpherence.pkg.js' => '281b1a73', 'core.pkg.css' => '476a4ec7', 'core.pkg.js' => 'fbc1c380', @@ -47,7 +47,7 @@ return array( 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 'rsrc/css/application/conpherence/durable-column.css' => '292c71f0', 'rsrc/css/application/conpherence/header-pane.css' => '4082233d', - 'rsrc/css/application/conpherence/menu.css' => '3d8e5c9c', + 'rsrc/css/application/conpherence/menu.css' => '5abfb32d', 'rsrc/css/application/conpherence/message-pane.css' => 'd1fc13e1', 'rsrc/css/application/conpherence/notification.css' => '965db05b', 'rsrc/css/application/conpherence/participant-pane.css' => '604a8b02', @@ -554,7 +554,7 @@ return array( 'config-page-css' => 'c1d5121b', 'conpherence-durable-column-view' => '292c71f0', 'conpherence-header-pane-css' => '4082233d', - 'conpherence-menu-css' => '3d8e5c9c', + 'conpherence-menu-css' => '5abfb32d', 'conpherence-message-pane-css' => 'd1fc13e1', 'conpherence-notification-css' => '965db05b', 'conpherence-participant-pane-css' => '604a8b02', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 91ceb48819..4ef610fd7e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2387,6 +2387,7 @@ phutil_register_library_map(array( 'PhabricatorConpherenceColumnVisibleSetting' => 'applications/settings/setting/PhabricatorConpherenceColumnVisibleSetting.php', 'PhabricatorConpherenceNotificationsSetting' => 'applications/settings/setting/PhabricatorConpherenceNotificationsSetting.php', 'PhabricatorConpherencePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php', + 'PhabricatorConpherenceProfileMenuItem' => 'applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php', 'PhabricatorConpherenceThreadPHIDType' => 'applications/conpherence/phid/PhabricatorConpherenceThreadPHIDType.php', 'PhabricatorConpherenceWidgetVisibleSetting' => 'applications/settings/setting/PhabricatorConpherenceWidgetVisibleSetting.php', 'PhabricatorConsoleApplication' => 'applications/console/application/PhabricatorConsoleApplication.php', @@ -7473,6 +7474,7 @@ phutil_register_library_map(array( 'PhabricatorConpherenceColumnVisibleSetting' => 'PhabricatorInternalSetting', 'PhabricatorConpherenceNotificationsSetting' => 'PhabricatorSelectSetting', 'PhabricatorConpherencePreferencesSettingsPanel' => 'PhabricatorEditEngineSettingsPanel', + 'PhabricatorConpherenceProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorConpherenceThreadPHIDType' => 'PhabricatorPHIDType', 'PhabricatorConpherenceWidgetVisibleSetting' => 'PhabricatorInternalSetting', 'PhabricatorConsoleApplication' => 'PhabricatorApplication', diff --git a/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php new file mode 100644 index 0000000000..32755d736f --- /dev/null +++ b/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php @@ -0,0 +1,174 @@ +conpherence = $conpherence; + return $this; + } + + public function getConpherence() { + $conpherence = $this->conpherence; + + if (!$conpherence) { + return null; + } + + return $conpherence; + } + + public function willBuildNavigationItems(array $items) { + require_celerity_resource('conpherence-menu-css'); + $viewer = $this->getViewer(); + $room_phids = array(); + foreach ($items as $item) { + $room_phids[] = $item->getMenuItemProperty('conpherence'); + } + + $rooms = id(new ConpherenceThreadQuery()) + ->setViewer($viewer) + ->withPHIDs($room_phids) + ->needParticipantCache(true) + ->needProfileImage(true) + ->execute(); + + $rooms = mpull($rooms, null, 'getPHID'); + foreach ($items as $item) { + $room_phid = $item->getMenuItemProperty('conpherence'); + $room = idx($rooms, $room_phid, null); + $item->getMenuItem()->attachConpherence($room); + } + } + + public function getDisplayName( + PhabricatorProfileMenuItemConfiguration $config) { + $room = $this->getConpherence($config); + if (!$room) { + return pht('(Restricted/Invalid Conpherence)'); + } + + $name = $this->getName($config); + if (strlen($name)) { + return $name; + } + + return $room->getTitle(); + } + + public function buildEditEngineFields( + PhabricatorProfileMenuItemConfiguration $config) { + return array( + id(new PhabricatorDatasourceEditField()) + ->setKey(self::FIELD_CONPHERENCE) + ->setLabel(pht('Conpherence Room')) + ->setDatasource(new ConpherenceThreadDatasource()) + ->setIsRequired(true) + ->setSingleValue($config->getMenuItemProperty('conpherence')), + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setValue($this->getName($config)), + ); + } + + private function getName( + PhabricatorProfileMenuItemConfiguration $config) { + return $config->getMenuItemProperty('name'); + } + + protected function newNavigationMenuItems( + PhabricatorProfileMenuItemConfiguration $config) { + $viewer = $this->getViewer(); + $room = $this->getConpherence($config); + if (!$room) { + return array(); + } + + $data = $room->getDisplayData($viewer); + $unread_count = $data['unread_count']; + + $count = null; + if ($unread_count) { + $count = phutil_tag( + 'span', + array( + 'class' => 'conpherence-menu-item-count', + ), + $unread_count); + } + + $item = $this->newItem() + ->setHref('/'.$room->getMonogram()) + ->setName($this->getDisplayName($config)) + ->setIcon('fa-comments') + ->appendChild($count); + + return array( + $item, + ); + } + + public function validateTransactions( + PhabricatorProfileMenuItemConfiguration $config, + $field_key, + $value, + array $xactions) { + + $viewer = $this->getViewer(); + $errors = array(); + + if ($field_key == self::FIELD_CONPHERENCE) { + if ($this->isEmptyTransaction($value, $xactions)) { + $errors[] = $this->newRequiredError( + pht('You must choose a room.'), + $field_key); + } + + foreach ($xactions as $xaction) { + $new = $xaction['new']; + + if (!$new) { + continue; + } + + if ($new === $value) { + continue; + } + + $rooms = id(new ConpherenceThreadQuery()) + ->setViewer($viewer) + ->withPHIDs(array($new)) + ->execute(); + if (!$rooms) { + $errors[] = $this->newInvalidError( + pht( + 'Room "%s" is not a valid room which you have '. + 'permission to see.', + $new), + $xaction['xaction']); + } + } + } + + return $errors; + } + +} diff --git a/webroot/rsrc/css/application/conpherence/menu.css b/webroot/rsrc/css/application/conpherence/menu.css index e0719cdcc8..775dfaed43 100644 --- a/webroot/rsrc/css/application/conpherence/menu.css +++ b/webroot/rsrc/css/application/conpherence/menu.css @@ -167,7 +167,8 @@ } .conpherence-menu .conpherence-menu-item-view -.conpherence-menu-item-unread-count { +.conpherence-menu-item-unread-count, +.conpherence-menu-item-count { position: absolute; right: 4px; top: 10px; @@ -179,6 +180,11 @@ font-size: {$smallestfontsize}; } +.phui-list-item-view .conpherence-menu-item-count { + right: 7px; + top: 7px; +} + .conpherence-menu .hide-unread-count .conpherence-menu-item-unread-count, .conpherence-menu .conpherence-selected .conpherence-menu-item-unread-count { display: none; From 6bf595b95154b3f21f89beab88feeee8f7f1210e Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 12 Apr 2017 13:21:47 -0700 Subject: [PATCH 25/52] Check is viewer is a participant before showing count Summary: In Conpherence ProfileMenuItem we show an unread count if you're a participant, but all message count if you're not. Just remove that. Test Plan: Log out of room in Conpherence, leave messages on second account, check menu item on both accounts. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17664 --- .../menuitem/PhabricatorConpherenceProfileMenuItem.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php index 32755d736f..18342cb357 100644 --- a/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php @@ -102,8 +102,13 @@ final class PhabricatorConpherenceProfileMenuItem return array(); } - $data = $room->getDisplayData($viewer); - $unread_count = $data['unread_count']; + $participants = $room->getParticipants(); + $viewer_phid = $viewer->getPHID(); + $unread_count = null; + if (isset($participants[$viewer_phid])) { + $data = $room->getDisplayData($viewer); + $unread_count = $data['unread_count']; + } $count = null; if ($unread_count) { From c6c25b055b3660607e593a556a6b2182b9e283eb Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 12 Apr 2017 13:24:41 -0700 Subject: [PATCH 26/52] Cleanup Countdown manual construction of monograms/uris Summary: looked for places where Countdown monograms/uris were being constructed by hand, and updated with modern versions Test Plan: clicked around the Countdown UI, looking for broken links Reviewers: epriestley Reviewed By: epriestley Subscribers: chad, Korvin Maniphest Tasks: T12524 Differential Revision: https://secure.phabricator.com/D17665 --- .../controller/PhabricatorCountdownViewController.php | 2 +- .../phid/PhabricatorCountdownCountdownPHIDType.php | 6 +++--- .../countdown/query/PhabricatorCountdownSearchEngine.php | 2 +- .../countdown/view/PhabricatorCountdownView.php | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php index 3e8f28e0cf..db63613df2 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php @@ -28,7 +28,7 @@ final class PhabricatorCountdownViewController $crumbs = $this ->buildApplicationCrumbs() - ->addTextCrumb("C{$id}") + ->addTextCrumb($countdown->getMonogram()) ->setBorder(true); $epoch = $countdown->getEpoch(); diff --git a/src/applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php b/src/applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php index 573d89c843..7e6278c8ea 100644 --- a/src/applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php +++ b/src/applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php @@ -35,9 +35,9 @@ final class PhabricatorCountdownCountdownPHIDType extends PhabricatorPHIDType { $name = $countdown->getTitle(); $id = $countdown->getID(); - $handle->setName("C{$id}"); - $handle->setFullName("C{$id}: {$name}"); - $handle->setURI("/countdown/{$id}/"); + $handle->setName($countdown->getMonogram()); + $handle->setFullName(pht('%s: %s', $countdown->getMonogram(), $name)); + $handle->setURI($countdown->getURI()); } } diff --git a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php index 0abf9e453a..00512cb07c 100644 --- a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php +++ b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php @@ -108,7 +108,7 @@ final class PhabricatorCountdownSearchEngine $item = id(new PHUIObjectItemView()) ->setUser($viewer) ->setObject($countdown) - ->setObjectName("C{$id}") + ->setObjectName($countdown->getMonogram()) ->setHeader($countdown->getTitle()) ->setHref($this->getApplicationURI("{$id}/")) ->addByline( diff --git a/src/applications/countdown/view/PhabricatorCountdownView.php b/src/applications/countdown/view/PhabricatorCountdownView.php index 4e975c3626..f88c5a9d9c 100644 --- a/src/applications/countdown/view/PhabricatorCountdownView.php +++ b/src/applications/countdown/view/PhabricatorCountdownView.php @@ -14,12 +14,12 @@ final class PhabricatorCountdownView extends AphrontView { require_celerity_resource('phabricator-countdown-css'); $header_text = array( - 'C'.$countdown->getID(), + $countdown->getMonogram(), ' ', phutil_tag( 'a', array( - 'href' => '/countdown/'.$countdown->getID(), + 'href' => $countdown->getURI(), ), $countdown->getTitle()), ); From a9845b0b1d83129e252142b180ded5b160fb9feb Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 12 Apr 2017 14:10:18 -0700 Subject: [PATCH 27/52] Remove picture crop transaction from Conpherence Summary: Fixes T11730. Removes an old transaction that hasn't been used in a year. Test Plan: Run sql, check various rooms. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T11730 Differential Revision: https://secure.phabricator.com/D17666 --- .../sql/autopatches/20170412.conpherence.01.picturecrop.sql | 2 ++ .../conpherence/storage/ConpherenceTransaction.php | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 resources/sql/autopatches/20170412.conpherence.01.picturecrop.sql diff --git a/resources/sql/autopatches/20170412.conpherence.01.picturecrop.sql b/resources/sql/autopatches/20170412.conpherence.01.picturecrop.sql new file mode 100644 index 0000000000..760a19f119 --- /dev/null +++ b/resources/sql/autopatches/20170412.conpherence.01.picturecrop.sql @@ -0,0 +1,2 @@ +DELETE FROM {$NAMESPACE}_conpherence.conpherence_transaction + WHERE transactionType = 'picture-crop'; diff --git a/src/applications/conpherence/storage/ConpherenceTransaction.php b/src/applications/conpherence/storage/ConpherenceTransaction.php index b311c80ca5..3706b4a210 100644 --- a/src/applications/conpherence/storage/ConpherenceTransaction.php +++ b/src/applications/conpherence/storage/ConpherenceTransaction.php @@ -7,7 +7,6 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { const TYPE_PARTICIPANTS = 'participants'; const TYPE_DATE_MARKER = 'date-marker'; const TYPE_PICTURE = 'picture'; - const TYPE_PICTURE_CROP = 'picture-crop'; // TODO: Nuke these from DB. public function getApplicationName() { return 'conpherence'; @@ -43,8 +42,6 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { case self::TYPE_PICTURE: case self::TYPE_DATE_MARKER: return false; - case self::TYPE_PICTURE_CROP: - return true; } return parent::shouldHide(); From a7ebfc12c02562adc3b0f74eff95af1c850ed3e9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 12 Apr 2017 16:33:43 -0700 Subject: [PATCH 28/52] Modernize Conpherence with Modular Transactions Summary: Begin converting Conpherence to ModularTransactions, this converts title, topic, and picture to use modular transactions. Participants seems hairy so I'll do that in another diff Test Plan: Create a room with a topic, change room name, topic. Add people, remove people. Set a room image. Unset topic. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17668 --- resources/celerity/map.php | 6 +- src/__phutil_library_map__.php | 10 +- .../__tests__/ConpherenceRoomTestCase.php | 3 +- ...onpherenceUpdateThreadConduitAPIMethod.php | 3 +- .../ConpherenceNewRoomController.php | 7 +- .../ConpherenceRoomPictureController.php | 3 +- .../ConpherenceUpdateController.php | 6 +- .../conpherence/editor/ConpherenceEditor.php | 94 +++++----------- .../conpherence/storage/ConpherenceThread.php | 6 +- .../storage/ConpherenceTransaction.php | 106 +----------------- .../view/ConpherenceTransactionView.php | 15 +-- .../ConpherenceThreadPictureTransaction.php | 29 +++++ .../ConpherenceThreadTitleTransaction.php | 75 +++++++++++++ .../ConpherenceThreadTopicTransaction.php | 69 ++++++++++++ .../ConpherenceThreadTransactionType.php | 4 + webroot/rsrc/css/phui/phui-timeline-view.css | 4 +- 16 files changed, 248 insertions(+), 192 deletions(-) create mode 100644 src/applications/conpherence/xaction/ConpherenceThreadPictureTransaction.php create mode 100644 src/applications/conpherence/xaction/ConpherenceThreadTitleTransaction.php create mode 100644 src/applications/conpherence/xaction/ConpherenceThreadTopicTransaction.php create mode 100644 src/applications/conpherence/xaction/ConpherenceThreadTransactionType.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d7ad426263..afa1f20dc8 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'b5ee2073', 'conpherence.pkg.js' => '281b1a73', - 'core.pkg.css' => '476a4ec7', + 'core.pkg.css' => '30a64ed6', 'core.pkg.js' => 'fbc1c380', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '90b30783', @@ -167,7 +167,7 @@ return array( 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => '84d65f26', - 'rsrc/css/phui/phui-timeline-view.css' => 'bf45789e', + 'rsrc/css/phui/phui-timeline-view.css' => '1d7ef61d', 'rsrc/css/phui/phui-two-column-view.css' => 'ce9fa0b7', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', @@ -877,7 +877,7 @@ return array( 'phui-status-list-view-css' => 'd5263e49', 'phui-tag-view-css' => '84d65f26', 'phui-theme-css' => '9f261c6b', - 'phui-timeline-view-css' => 'bf45789e', + 'phui-timeline-view-css' => '1d7ef61d', 'phui-two-column-view-css' => 'ce9fa0b7', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4ef610fd7e..f6ca7a0010 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -320,11 +320,15 @@ phutil_register_library_map(array( 'ConpherenceThreadListView' => 'applications/conpherence/view/ConpherenceThreadListView.php', 'ConpherenceThreadMailReceiver' => 'applications/conpherence/mail/ConpherenceThreadMailReceiver.php', 'ConpherenceThreadMembersPolicyRule' => 'applications/conpherence/policyrule/ConpherenceThreadMembersPolicyRule.php', + 'ConpherenceThreadPictureTransaction' => 'applications/conpherence/xaction/ConpherenceThreadPictureTransaction.php', 'ConpherenceThreadQuery' => 'applications/conpherence/query/ConpherenceThreadQuery.php', 'ConpherenceThreadRemarkupRule' => 'applications/conpherence/remarkup/ConpherenceThreadRemarkupRule.php', 'ConpherenceThreadSearchController' => 'applications/conpherence/controller/ConpherenceThreadSearchController.php', 'ConpherenceThreadSearchEngine' => 'applications/conpherence/query/ConpherenceThreadSearchEngine.php', 'ConpherenceThreadTitleNgrams' => 'applications/conpherence/storage/ConpherenceThreadTitleNgrams.php', + 'ConpherenceThreadTitleTransaction' => 'applications/conpherence/xaction/ConpherenceThreadTitleTransaction.php', + 'ConpherenceThreadTopicTransaction' => 'applications/conpherence/xaction/ConpherenceThreadTopicTransaction.php', + 'ConpherenceThreadTransactionType' => 'applications/conpherence/xaction/ConpherenceThreadTransactionType.php', 'ConpherenceTransaction' => 'applications/conpherence/storage/ConpherenceTransaction.php', 'ConpherenceTransactionComment' => 'applications/conpherence/storage/ConpherenceTransactionComment.php', 'ConpherenceTransactionQuery' => 'applications/conpherence/query/ConpherenceTransactionQuery.php', @@ -5099,12 +5103,16 @@ phutil_register_library_map(array( 'ConpherenceThreadListView' => 'AphrontView', 'ConpherenceThreadMailReceiver' => 'PhabricatorObjectMailReceiver', 'ConpherenceThreadMembersPolicyRule' => 'PhabricatorPolicyRule', + 'ConpherenceThreadPictureTransaction' => 'ConpherenceThreadTransactionType', 'ConpherenceThreadQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'ConpherenceThreadRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'ConpherenceThreadSearchController' => 'ConpherenceController', 'ConpherenceThreadSearchEngine' => 'PhabricatorApplicationSearchEngine', 'ConpherenceThreadTitleNgrams' => 'PhabricatorSearchNgrams', - 'ConpherenceTransaction' => 'PhabricatorApplicationTransaction', + 'ConpherenceThreadTitleTransaction' => 'ConpherenceThreadTransactionType', + 'ConpherenceThreadTopicTransaction' => 'ConpherenceThreadTransactionType', + 'ConpherenceThreadTransactionType' => 'PhabricatorModularTransactionType', + 'ConpherenceTransaction' => 'PhabricatorModularTransaction', 'ConpherenceTransactionComment' => 'PhabricatorApplicationTransactionComment', 'ConpherenceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'ConpherenceTransactionRenderer' => 'Phobject', diff --git a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php index f34dd25110..1fb4dbabaa 100644 --- a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php +++ b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php @@ -123,7 +123,8 @@ final class ConpherenceRoomTestCase extends ConpherenceTestCase { ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('+' => $participant_phids)); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) + ->setTransactionType( + ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) ->setNewValue(pht('Test')); id(new ConpherenceEditor()) diff --git a/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php index 01b86f9a42..c92cc464ed 100644 --- a/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php +++ b/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php @@ -83,7 +83,8 @@ final class ConpherenceUpdateThreadConduitAPIMethod } if ($title) { $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) + ->setTransactionType( + ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); } if ($message) { diff --git a/src/applications/conpherence/controller/ConpherenceNewRoomController.php b/src/applications/conpherence/controller/ConpherenceNewRoomController.php index 347c81f1e1..1f27a6807e 100644 --- a/src/applications/conpherence/controller/ConpherenceNewRoomController.php +++ b/src/applications/conpherence/controller/ConpherenceNewRoomController.php @@ -16,7 +16,7 @@ final class ConpherenceNewRoomController extends ConpherenceController { $xactions = array(); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) + ->setTransactionType(ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) ->setNewValue($request->getStr('title')); $participants = $request->getArr('participants'); @@ -26,7 +26,7 @@ final class ConpherenceNewRoomController extends ConpherenceController { ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) ->setNewValue(array('+' => $participants)); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TOPIC) + ->setTransactionType(ConpherenceThreadTopicTransaction::TRANSACTIONTYPE) ->setNewValue($request->getStr('topic')); $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) @@ -50,7 +50,8 @@ final class ConpherenceNewRoomController extends ConpherenceController { } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; - $e_title = $ex->getShortMessage(ConpherenceTransaction::TYPE_TITLE); + $e_title = $ex->getShortMessage( + ConpherenceThreadTitleTransaction::TRANSACTIONTYPE); $conpherence->setViewPolicy($request->getStr('viewPolicy')); $conpherence->setEditPolicy($request->getStr('editPolicy')); diff --git a/src/applications/conpherence/controller/ConpherenceRoomPictureController.php b/src/applications/conpherence/controller/ConpherenceRoomPictureController.php index 8ddeae7098..6a39481377 100644 --- a/src/applications/conpherence/controller/ConpherenceRoomPictureController.php +++ b/src/applications/conpherence/controller/ConpherenceRoomPictureController.php @@ -76,7 +76,8 @@ final class ConpherenceRoomPictureController $xactions = array(); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_PICTURE) + ->setTransactionType( + ConpherenceThreadPictureTransaction::TRANSACTIONTYPE) ->setNewValue($new_value); $editor = id(new ConpherenceEditor()) diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 724b802566..44e6c3cf4b 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -140,10 +140,12 @@ final class ConpherenceUpdateController $title = $request->getStr('title'); $topic = $request->getStr('topic'); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) + ->setTransactionType( + ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TOPIC) + ->setTransactionType( + ConpherenceThreadTopicTransaction::TRANSACTIONTYPE) ->setNewValue($topic); $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 2a57711840..3ffb193c86 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -41,12 +41,14 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { ->setNewValue(array('+' => $participant_phids)); if ($title) { $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TITLE) + ->setTransactionType( + ConpherenceThreadTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); } if (strlen($topic)) { $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_TOPIC) + ->setTransactionType( + ConpherenceThreadTopicTransaction::TRANSACTIONTYPE) ->setNewValue($topic); } @@ -87,10 +89,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $types[] = PhabricatorTransactions::TYPE_COMMENT; - $types[] = ConpherenceTransaction::TYPE_TITLE; - $types[] = ConpherenceTransaction::TYPE_TOPIC; $types[] = ConpherenceTransaction::TYPE_PARTICIPANTS; - $types[] = ConpherenceTransaction::TYPE_PICTURE; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; @@ -98,17 +97,34 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return $types; } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this room.', $author); + } + + protected function shouldPublishFeedStory( + PhabricatorLiskDAO $object, + array $xactions) { + + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case ConpherenceThreadTitleTransaction::TRANSACTIONTYPE: + case ConpherenceThreadTopicTransaction::TRANSACTIONTYPE: + case ConpherenceThreadPictureTransaction::TRANSACTIONTYPE: + return true; + default: + return false; + } + } + return true; + } + + + protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_TITLE: - return $object->getTitle(); - case ConpherenceTransaction::TYPE_TOPIC: - return $object->getTopic(); - case ConpherenceTransaction::TYPE_PICTURE: - return $object->getProfileImagePHID(); case ConpherenceTransaction::TYPE_PARTICIPANTS: if ($this->getIsNewObject()) { return array(); @@ -122,10 +138,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_TITLE: - case ConpherenceTransaction::TYPE_TOPIC: - case ConpherenceTransaction::TYPE_PICTURE: - return $xaction->getNewValue(); case ConpherenceTransaction::TYPE_PARTICIPANTS: return $this->getPHIDTransactionNewValue($xaction); } @@ -211,15 +223,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $make_author_recent_participant = true; switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_TITLE: - $object->setTitle($xaction->getNewValue()); - break; - case ConpherenceTransaction::TYPE_TOPIC: - $object->setTopic($xaction->getNewValue()); - break; - case ConpherenceTransaction::TYPE_PICTURE: - $object->setProfileImagePHID($xaction->getNewValue()); - break; case ConpherenceTransaction::TYPE_PARTICIPANTS: if (!$this->getIsNewObject()) { $old_map = array_fuse($xaction->getOldValue()); @@ -418,8 +421,8 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { PhabricatorPolicyCapability::CAN_EDIT); } break; - case ConpherenceTransaction::TYPE_TITLE: - case ConpherenceTransaction::TYPE_TOPIC: + case ConpherenceThreadTitleTransaction::TRANSACTIONTYPE: + case ConpherenceThreadTopicTransaction::TRANSACTIONTYPE: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, @@ -434,8 +437,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $type = $u->getTransactionType(); switch ($type) { - case ConpherenceTransaction::TYPE_TITLE: - return $v; case ConpherenceTransaction::TYPE_PARTICIPANTS: return $this->mergePHIDOrEdgeTransactions($u, $v); } @@ -541,23 +542,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return PhabricatorEnv::getEnvConfig('metamta.conpherence.subject-prefix'); } - protected function shouldPublishFeedStory( - PhabricatorLiskDAO $object, - array $xactions) { - - foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_TITLE: - case ConpherenceTransaction::TYPE_TOPIC: - case ConpherenceTransaction::TYPE_PICTURE: - return true; - default: - return false; - } - } - return false; - } - protected function supportsSearch() { return true; } @@ -570,26 +554,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { - case ConpherenceTransaction::TYPE_TITLE: - if (empty($xactions)) { - break; - } - $missing = $this->validateIsEmptyTextField( - $object->getTitle(), - $xactions); - - if ($missing) { - $detail = pht('Room title is required.'); - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - $detail, - last($xactions)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; case ConpherenceTransaction::TYPE_PARTICIPANTS: foreach ($xactions as $xaction) { $new_phids = $this->getPHIDTransactionNewValue($xaction, array()); diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index e912f52591..b8d5a8dce0 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -268,9 +268,9 @@ final class ConpherenceThread extends ConpherenceDAO case PhabricatorTransactions::TYPE_COMMENT: $message_transaction = $transaction; break; - case ConpherenceTransaction::TYPE_TITLE: - case ConpherenceTransaction::TYPE_TOPIC: - case ConpherenceTransaction::TYPE_PICTURE: + case ConpherenceThreadTitleTransaction::TRANSACTIONTYPE: + case ConpherenceThreadTopicTransaction::TRANSACTIONTYPE: + case ConpherenceThreadPictureTransaction::TRANSACTIONTYPE: case ConpherenceTransaction::TYPE_PARTICIPANTS: $action_transaction = $transaction; break; diff --git a/src/applications/conpherence/storage/ConpherenceTransaction.php b/src/applications/conpherence/storage/ConpherenceTransaction.php index 3706b4a210..b449037235 100644 --- a/src/applications/conpherence/storage/ConpherenceTransaction.php +++ b/src/applications/conpherence/storage/ConpherenceTransaction.php @@ -1,12 +1,10 @@ getTransactionType()) { case self::TYPE_PARTICIPANTS: @@ -37,9 +39,6 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { switch ($this->getTransactionType()) { case self::TYPE_PARTICIPANTS: return ($old === null); - case self::TYPE_TITLE: - case self::TYPE_TOPIC: - case self::TYPE_PICTURE: case self::TYPE_DATE_MARKER: return false; } @@ -54,12 +53,9 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - case self::TYPE_TOPIC: case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: - case self::TYPE_PICTURE: return $this->getRoomTitle(); break; case self::TYPE_PARTICIPANTS: @@ -94,55 +90,6 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { return parent::getTitle(); } - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_TITLE: - if (strlen($old) && strlen($new)) { - return pht( - '%s renamed %s from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $old, - $new); - } else { - return pht( - '%s created the room %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - break; - case self::TYPE_TOPIC: - if (strlen($new)) { - return pht( - '%s set the topic of %s to "%s".', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $new); - } else if (strlen($old)) { - return pht( - '%s deleted the topic in %s', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; - case self::TYPE_PICTURE: - return pht( - '%s updated the room image for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; - } - return parent::getTitleForFeed(); - } - private function getRoomTitle() { $author_phid = $this->getAuthorPHID(); @@ -150,45 +97,6 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old && $new) { - $title = pht( - '%s renamed this room from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } else if ($old) { - $title = pht( - '%s deleted the room name "%s".', - $this->renderHandleLink($author_phid), - $old); - } else { - $title = pht( - '%s named this room "%s".', - $this->renderHandleLink($author_phid), - $new); - } - return $title; - break; - case self::TYPE_TOPIC: - if ($new) { - $title = pht( - '%s set the topic of this room to "%s".', - $this->renderHandleLink($author_phid), - $new); - } else if ($old) { - $title = pht( - '%s deleted the room topic "%s"', - $this->renderHandleLink($author_phid), - $old); - } - return $title; - break; - case self::TYPE_PICTURE: - return pht( - '%s updated the room image.', - $this->renderHandleLink($author_phid)); - break; case PhabricatorTransactions::TYPE_VIEW_POLICY: return pht( '%s changed the visibility of this room from "%s" to "%s".', @@ -221,8 +129,6 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction { $phids[] = $this->getAuthorPHID(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - case self::TYPE_PICTURE: case self::TYPE_DATE_MARKER: break; case self::TYPE_PARTICIPANTS: diff --git a/src/applications/conpherence/view/ConpherenceTransactionView.php b/src/applications/conpherence/view/ConpherenceTransactionView.php index c18a672ca2..28dcf5c361 100644 --- a/src/applications/conpherence/view/ConpherenceTransactionView.php +++ b/src/applications/conpherence/view/ConpherenceTransactionView.php @@ -216,17 +216,6 @@ final class ConpherenceTransactionView extends AphrontView { $content = null; $handles = $this->getHandles(); switch ($transaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_TITLE: - case ConpherenceTransaction::TYPE_TOPIC: - case ConpherenceTransaction::TYPE_PICTURE: - case ConpherenceTransaction::TYPE_PARTICIPANTS: - case PhabricatorTransactions::TYPE_VIEW_POLICY: - case PhabricatorTransactions::TYPE_EDIT_POLICY: - case PhabricatorTransactions::TYPE_JOIN_POLICY: - case PhabricatorTransactions::TYPE_EDGE: - $content = $transaction->getTitle(); - $this->addClass('conpherence-edited'); - break; case PhabricatorTransactions::TYPE_COMMENT: $this->addClass('conpherence-comment'); $author = $handles[$transaction->getAuthorPHID()]; @@ -236,6 +225,10 @@ final class ConpherenceTransactionView extends AphrontView { PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); $content_class = 'conpherence-message'; break; + default: + $content = $transaction->getTitle(); + $this->addClass('conpherence-edited'); + break; } $view = phutil_tag( diff --git a/src/applications/conpherence/xaction/ConpherenceThreadPictureTransaction.php b/src/applications/conpherence/xaction/ConpherenceThreadPictureTransaction.php new file mode 100644 index 0000000000..65f7deac5e --- /dev/null +++ b/src/applications/conpherence/xaction/ConpherenceThreadPictureTransaction.php @@ -0,0 +1,29 @@ +getProfileImagePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setProfileImagePHID($value); + } + + public function getTitle() { + return pht( + '%s updated the room image.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the room image for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + +} diff --git a/src/applications/conpherence/xaction/ConpherenceThreadTitleTransaction.php b/src/applications/conpherence/xaction/ConpherenceThreadTitleTransaction.php new file mode 100644 index 0000000000..130a841a23 --- /dev/null +++ b/src/applications/conpherence/xaction/ConpherenceThreadTitleTransaction.php @@ -0,0 +1,75 @@ +getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setTitle($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($old) && strlen($new)) { + return pht( + '%s renamed this room from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } else { + return pht( + '%s created this room.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($old) && strlen($new)) { + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } else { + return pht( + '%s created %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getTitle(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Rooms must have a title.')); + } + + $max_length = $object->getColumnMaximumByteLength('title'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The title can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/conpherence/xaction/ConpherenceThreadTopicTransaction.php b/src/applications/conpherence/xaction/ConpherenceThreadTopicTransaction.php new file mode 100644 index 0000000000..23c590f409 --- /dev/null +++ b/src/applications/conpherence/xaction/ConpherenceThreadTopicTransaction.php @@ -0,0 +1,69 @@ +getTopic(); + } + + public function applyInternalEffects($object, $value) { + $object->setTopic($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($new)) { + return pht( + '%s set the room topic to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s removed the room topic.', + $this->renderAuthor()); + } + + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($new)) { + return pht( + '%s set the room topic to %s in %s.', + $this->renderAuthor(), + $this->renderNewValue(), + $this->renderObject()); + } else { + return pht( + '%s removed the room topic for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $max_length = $object->getColumnMaximumByteLength('topic'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('The topic can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/conpherence/xaction/ConpherenceThreadTransactionType.php b/src/applications/conpherence/xaction/ConpherenceThreadTransactionType.php new file mode 100644 index 0000000000..82f4f63d4e --- /dev/null +++ b/src/applications/conpherence/xaction/ConpherenceThreadTransactionType.php @@ -0,0 +1,4 @@ + Date: Wed, 12 Apr 2017 14:48:18 -0700 Subject: [PATCH 29/52] Fix pagination of fulltext search results Summary: Fixes T8285. Fulltext search relies on an underlying engine which can not realistically use cursor paging. This is unusual and creates some oddness. Tweak a few numbers -- and how offsets are handled -- to separate the filtered offset and unfiltered offset. Test Plan: - Set page size to 2. - Ran a query. - Paged forward and backward through results sensibly, seeing the full result set. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8285 Differential Revision: https://secure.phabricator.com/D17667 --- src/__phutil_library_map__.php | 2 +- .../query/PhabricatorSearchDocumentQuery.php | 37 +++++++++++-------- src/view/phui/PHUIPagerView.php | 2 +- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f6ca7a0010..75f0db1381 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -9148,7 +9148,7 @@ phutil_register_library_map(array( 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentFieldType' => 'Phobject', - 'PhabricatorSearchDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorSearchDocumentQuery' => 'PhabricatorPolicyAwareQuery', 'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentTypeDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorSearchEditController' => 'PhabricatorSearchBaseController', diff --git a/src/applications/search/query/PhabricatorSearchDocumentQuery.php b/src/applications/search/query/PhabricatorSearchDocumentQuery.php index d4700904c9..fb6e23c6d4 100644 --- a/src/applications/search/query/PhabricatorSearchDocumentQuery.php +++ b/src/applications/search/query/PhabricatorSearchDocumentQuery.php @@ -1,10 +1,11 @@ savedQuery = $query; @@ -20,11 +21,27 @@ final class PhabricatorSearchDocumentQuery if ($this->objectCapabilities) { return $this->objectCapabilities; } + return $this->getRequiredCapabilities(); } + protected function willExecute() { + $this->unfilteredOffset = 0; + } + protected function loadPage() { - $phids = $this->loadDocumentPHIDsWithoutPolicyChecks(); + // NOTE: The offset and limit information in the inherited properties of + // this object represent a policy-filtered offset and limit, but the + // underlying query engine needs an unfiltered offset and limit. We keep + // track of an unfiltered result offset internally. + + $query = id(clone($this->savedQuery)) + ->setParameter('offset', $this->unfilteredOffset) + ->setParameter('limit', $this->getRawResultLimit()); + + $phids = PhabricatorSearchService::executeSearch($query); + + $this->unfilteredOffset += count($phids); $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->getViewer()) @@ -69,25 +86,13 @@ final class PhabricatorSearchDocumentQuery return $handles; } - public function loadDocumentPHIDsWithoutPolicyChecks() { - $query = id(clone($this->savedQuery)) - ->setParameter('offset', $this->getOffset()) - ->setParameter('limit', $this->getRawResultLimit()); - return PhabricatorSearchService::executeSearch($query); - } - public function getQueryApplicationClass() { return 'PhabricatorSearchApplication'; } - protected function getResultCursor($result) { - throw new Exception( - pht( - 'This query does not support cursor paging; it must be offset paged.')); - } - protected function nextPage(array $page) { - $this->setOffset($this->getOffset() + count($page)); + // We already updated the internal offset in `loadPage()` after loading + // results, so we do not need to make any additional state updates here. return $this; } diff --git a/src/view/phui/PHUIPagerView.php b/src/view/phui/PHUIPagerView.php index 232d4ab747..b78efcda96 100644 --- a/src/view/phui/PHUIPagerView.php +++ b/src/view/phui/PHUIPagerView.php @@ -55,7 +55,7 @@ final class PHUIPagerView extends AphrontView { } public function willShowPagingControls() { - return $this->hasMorePages; + return $this->hasMorePages || $this->getOffset(); } public function getHasMorePages() { From cb49acc2ca71f3b2f4cec579ec002a4efe24a5c1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 12 Apr 2017 15:35:18 -0700 Subject: [PATCH 30/52] Update Phabricator to use intermediate tokens from the query compiler Summary: Depends on D17669. Ref T12137. Ref T12003. Ref T2632. Ref T7860. Converts Phabricator to the new parse + compile workflow with intermediate tokens. Also fixes a bug where searches for `cat"` or similar (unmatched quotes) wouldn't produce a nice exception. Test Plan: - Fulltext searched. - Fulltext searched in Conpherence. - Fulltext searched with bad syntax. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12137, T12003, T7860, T2632 Differential Revision: https://secure.phabricator.com/D17670 --- .../conpherence/query/ConpherenceFulltextQuery.php | 6 +++--- .../PhabricatorMySQLFulltextStorageEngine.php | 7 ++++--- .../cluster/search/PhabricatorSearchService.php | 4 ++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/applications/conpherence/query/ConpherenceFulltextQuery.php b/src/applications/conpherence/query/ConpherenceFulltextQuery.php index 43de7455ae..ba734049f8 100644 --- a/src/applications/conpherence/query/ConpherenceFulltextQuery.php +++ b/src/applications/conpherence/query/ConpherenceFulltextQuery.php @@ -56,9 +56,9 @@ final class ConpherenceFulltextQuery } if (strlen($this->fulltext)) { - $compiled_query = PhabricatorSearchDocument::newQueryCompiler() - ->setQuery($this->fulltext) - ->compileQuery(); + $compiler = PhabricatorSearchDocument::newQueryCompiler(); + $tokens = $compiler->newTokens($this->fulltext); + $compiled_query = $compiler->compileQuery($tokens); $where[] = qsprintf( $conn_r, diff --git a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php index 72c49576f0..626c7435c3 100644 --- a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php @@ -398,12 +398,13 @@ final class PhabricatorMySQLFulltextStorageEngine $stemmer = new PhutilSearchStemmer(); $compiler = PhabricatorSearchDocument::newQueryCompiler() - ->setQuery($raw_query) ->setStemmer($stemmer); + $tokens = $compiler->newTokens($raw_query); + $queries = array(); - $queries[] = $compiler->compileLiteralQuery(); - $queries[] = $compiler->compileStemmedQuery(); + $queries[] = $compiler->compileLiteralQuery($tokens); + $queries[] = $compiler->compileStemmedQuery($tokens); return implode(' ', array_filter($queries)); } diff --git a/src/infrastructure/cluster/search/PhabricatorSearchService.php b/src/infrastructure/cluster/search/PhabricatorSearchService.php index a9ceb0e7e5..8e5b296132 100644 --- a/src/infrastructure/cluster/search/PhabricatorSearchService.php +++ b/src/infrastructure/cluster/search/PhabricatorSearchService.php @@ -253,6 +253,10 @@ class PhabricatorSearchService $res = $engine->executeSearch($query); // return immediately if we get results return $res; + } catch (PhutilSearchQueryCompilerSyntaxException $ex) { + // If there's a query compilation error, return it directly to the + // user: they issued a query with bad syntax. + throw $ex; } catch (Exception $ex) { $exceptions[] = $ex; } From 3245e74f16bb28ae905f76fcc99d722abc8320ab Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 12 Apr 2017 16:28:04 -0700 Subject: [PATCH 31/52] Show users how fulltext search queries are parsed and executed; don't query stopwords or short tokens Summary: Depends on D17670. Fixes T12137. Fixes T12003. Ref T2632. This shows users a readout of which terms were actually searched for. This also drops those terms from the query we submit to the backend, dodging the weird behaviors / search engine bugs in T12137. This might need some design tweaking. Test Plan: {F4899825} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12137, T12003, T2632 Differential Revision: https://secure.phabricator.com/D17672 --- resources/celerity/map.php | 4 +- resources/sql/stopwords_myisam.txt | 543 ++++++++++++++++++ src/__phutil_library_map__.php | 4 + .../PhabricatorApplicationSearchEngine.php | 6 + .../PhabricatorFulltextStorageEngine.php | 4 + .../PhabricatorMySQLFulltextStorageEngine.php | 128 ++++- .../query/PhabricatorFulltextResultSet.php | 26 + .../search/query/PhabricatorFulltextToken.php | 88 +++ ...abricatorSearchApplicationSearchEngine.php | 30 + .../query/PhabricatorSearchDocumentQuery.php | 14 +- .../search/PhabricatorSearchService.php | 17 +- .../css/application/search/search-results.css | 13 + 12 files changed, 855 insertions(+), 22 deletions(-) create mode 100644 resources/sql/stopwords_myisam.txt create mode 100644 src/applications/search/query/PhabricatorFulltextResultSet.php create mode 100644 src/applications/search/query/PhabricatorFulltextToken.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index afa1f20dc8..6518fb0cc8 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -103,7 +103,7 @@ return array( 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', 'rsrc/css/application/search/application-search-view.css' => '66ee5d46', - 'rsrc/css/application/search/search-results.css' => '64ad079a', + 'rsrc/css/application/search/search-results.css' => 'f87d23ad', 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', @@ -794,7 +794,7 @@ return array( 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => '8d40ae75', 'phabricator-remarkup-css' => '17c0fb37', - 'phabricator-search-results-css' => '64ad079a', + 'phabricator-search-results-css' => 'f87d23ad', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => '4383192f', diff --git a/resources/sql/stopwords_myisam.txt b/resources/sql/stopwords_myisam.txt new file mode 100644 index 0000000000..9a0bde3e9b --- /dev/null +++ b/resources/sql/stopwords_myisam.txt @@ -0,0 +1,543 @@ +a's +able +about +above +according +accordingly +across +actually +after +afterwards +again +against +ain't +all +allow +allows +almost +alone +along +already +also +although +always +am +among +amongst +an +and +another +any +anybody +anyhow +anyone +anything +anyway +anyways +anywhere +apart +appear +appreciate +appropriate +are +aren't +around +as +aside +ask +asking +associated +at +available +away +awfully +be +became +because +become +becomes +becoming +been +before +beforehand +behind +being +believe +below +beside +besides +best +better +between +beyond +both +brief +but +by +c'mon +c's +came +can +can't +cannot +cant +cause +causes +certain +certainly +changes +clearly +co +com +come +comes +concerning +consequently +consider +considering +contain +containing +contains +corresponding +could +couldn't +course +currently +definitely +described +despite +did +didn't +different +do +does +doesn't +doing +don't +done +down +downwards +during +each +edu +eg +eight +either +else +elsewhere +enough +entirely +especially +et +etc +even +ever +every +everybody +everyone +everything +everywhere +ex +exactly +example +except +far +few +fifth +first +five +followed +following +follows +for +former +formerly +forth +four +from +further +furthermore +get +gets +getting +given +gives +go +goes +going +gone +got +gotten +greetings +had +hadn't +happens +hardly +has +hasn't +have +haven't +having +he +he's +hello +help +hence +her +here +here's +hereafter +hereby +herein +hereupon +hers +herself +hi +him +himself +his +hither +hopefully +how +howbeit +however +i'd +i'll +i'm +i've +ie +if +ignored +immediate +in +inasmuch +inc +indeed +indicate +indicated +indicates +inner +insofar +instead +into +inward +is +isn't +it +it'd +it'll +it's +its +itself +just +keep +keeps +kept +know +known +knows +last +lately +later +latter +latterly +least +less +lest +let +let's +like +liked +likely +little +look +looking +looks +ltd +mainly +many +may +maybe +me +mean +meanwhile +merely +might +more +moreover +most +mostly +much +must +my +myself +name +namely +nd +near +nearly +necessary +need +needs +neither +never +nevertheless +new +next +nine +no +nobody +non +none +noone +nor +normally +not +nothing +novel +now +nowhere +obviously +of +off +often +oh +ok +okay +old +on +once +one +ones +only +onto +or +other +others +otherwise +ought +our +ours +ourselves +out +outside +over +overall +own +particular +particularly +per +perhaps +placed +please +plus +possible +presumably +probably +provides +que +quite +qv +rather +rd +re +really +reasonably +regarding +regardless +regards +relatively +respectively +right +said +same +saw +say +saying +says +second +secondly +see +seeing +seem +seemed +seeming +seems +seen +self +selves +sensible +sent +serious +seriously +seven +several +shall +she +should +shouldn't +since +six +so +some +somebody +somehow +someone +something +sometime +sometimes +somewhat +somewhere +soon +sorry +specified +specify +specifying +still +sub +such +sup +sure +t's +take +taken +tell +tends +th +than +thank +thanks +thanx +that +that's +thats +the +their +theirs +them +themselves +then +thence +there +there's +thereafter +thereby +therefore +therein +theres +thereupon +these +they +they'd +they'll +they're +they've +think +third +this +thorough +thoroughly +those +though +three +through +throughout +thru +thus +to +together +too +took +toward +towards +tried +tries +truly +try +trying +twice +two +un +under +unfortunately +unless +unlikely +until +unto +up +upon +us +use +used +useful +uses +using +usually +value +various +very +via +viz +vs +want +wants +was +wasn't +way +we +we'd +we'll +we're +we've +welcome +well +went +were +weren't +what +what's +whatever +when +whence +whenever +where +where's +whereafter +whereas +whereby +wherein +whereupon +wherever +whether +which +while +whither +who +who's +whoever +whole +whom +whose +why +will +willing +wish +with +within +without +won't +wonder +would +wouldn't +yes +yet +you +you'd +you'll +you're +you've +your +yours +yourself +yourselves +zero diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 75f0db1381..237a4ee392 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2844,7 +2844,9 @@ phutil_register_library_map(array( 'PhabricatorFulltextEngineExtensionModule' => 'applications/search/index/PhabricatorFulltextEngineExtensionModule.php', 'PhabricatorFulltextIndexEngineExtension' => 'applications/search/engineextension/PhabricatorFulltextIndexEngineExtension.php', 'PhabricatorFulltextInterface' => 'applications/search/interface/PhabricatorFulltextInterface.php', + 'PhabricatorFulltextResultSet' => 'applications/search/query/PhabricatorFulltextResultSet.php', 'PhabricatorFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php', + 'PhabricatorFulltextToken' => 'applications/search/query/PhabricatorFulltextToken.php', 'PhabricatorFundApplication' => 'applications/fund/application/PhabricatorFundApplication.php', 'PhabricatorGDSetupCheck' => 'applications/config/check/PhabricatorGDSetupCheck.php', 'PhabricatorGarbageCollector' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php', @@ -8005,7 +8007,9 @@ phutil_register_library_map(array( 'PhabricatorFulltextEngineExtension' => 'Phobject', 'PhabricatorFulltextEngineExtensionModule' => 'PhabricatorConfigModule', 'PhabricatorFulltextIndexEngineExtension' => 'PhabricatorIndexEngineExtension', + 'PhabricatorFulltextResultSet' => 'Phobject', 'PhabricatorFulltextStorageEngine' => 'Phobject', + 'PhabricatorFulltextToken' => 'Phobject', 'PhabricatorFundApplication' => 'PhabricatorApplication', 'PhabricatorGDSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorGarbageCollector' => 'Phobject', diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index 4329509b02..5b2f7827f9 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -978,9 +978,15 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { $objects = $query->executeWithCursorPager($pager); } + $this->didExecuteQuery($query); + return $objects; } + protected function didExecuteQuery(PhabricatorPolicyAwareQuery $query) { + return; + } + /* -( Rendering )---------------------------------------------------------- */ diff --git a/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php index 5e919258bd..588ccc3e5e 100644 --- a/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php @@ -101,4 +101,8 @@ abstract class PhabricatorFulltextStorageEngine extends Phobject { public function initIndex() {} + public function getFulltextTokens() { + return array(); + } + } diff --git a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php index 626c7435c3..4dfcae7c43 100644 --- a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php @@ -3,6 +3,9 @@ final class PhabricatorMySQLFulltextStorageEngine extends PhabricatorFulltextStorageEngine { + private $fulltextTokens = array(); + private $engineLimits; + public function getEngineIdentifier() { return 'mysql'; } @@ -203,8 +206,64 @@ final class PhabricatorMySQLFulltextStorageEngine $title_field = PhabricatorSearchDocumentFieldType::FIELD_TITLE; $title_boost = 1024; + $stemmer = new PhutilSearchStemmer(); + $raw_query = $query->getParameter('query'); - $compiled_query = $this->compileQuery($raw_query); + $raw_query = trim($raw_query); + if (strlen($raw_query)) { + $compiler = PhabricatorSearchDocument::newQueryCompiler() + ->setStemmer($stemmer); + + $tokens = $compiler->newTokens($raw_query); + + list($min_length, $stopword_list) = $this->getEngineLimits($conn); + + // Process all the parts of the user's query so we can show them which + // parts we searched for and which ones we ignored. + $fulltext_tokens = array(); + foreach ($tokens as $key => $token) { + $fulltext_token = id(new PhabricatorFulltextToken()) + ->setToken($token); + + $fulltext_tokens[$key] = $fulltext_token; + + $value = $token->getValue(); + if (phutil_utf8_strlen($value) < $min_length) { + $fulltext_token->setIsShort(true); + continue; + } + + if (isset($stopword_list[phutil_utf8_strtolower($value)])) { + $fulltext_token->setIsStopword(true); + continue; + } + } + $this->fulltextTokens = $fulltext_tokens; + + // Remove tokens which aren't queryable from the query. This is mostly + // a workaround for the peculiar behaviors described in T12137. + foreach ($this->fulltextTokens as $key => $fulltext_token) { + if (!$fulltext_token->isQueryable()) { + unset($tokens[$key]); + } + } + + if (!$tokens) { + throw new PhutilSearchQueryCompilerSyntaxException( + pht( + 'All of your search terms are too short or too common to '. + 'appear in the search index. Search for longer or more '. + 'distinctive terms.')); + } + + $queries = array(); + $queries[] = $compiler->compileLiteralQuery($tokens); + $queries[] = $compiler->compileStemmedQuery($tokens); + $compiled_query = implode(' ', array_filter($queries)); + } else { + $compiled_query = null; + } + if (strlen($compiled_query)) { $select[] = qsprintf( $conn, @@ -394,21 +453,6 @@ final class PhabricatorMySQLFulltextStorageEngine return $sql; } - private function compileQuery($raw_query) { - $stemmer = new PhutilSearchStemmer(); - - $compiler = PhabricatorSearchDocument::newQueryCompiler() - ->setStemmer($stemmer); - - $tokens = $compiler->newTokens($raw_query); - - $queries = array(); - $queries[] = $compiler->compileLiteralQuery($tokens); - $queries[] = $compiler->compileStemmedQuery($tokens); - - return implode(' ', array_filter($queries)); - } - public function indexExists() { return true; } @@ -417,4 +461,56 @@ final class PhabricatorMySQLFulltextStorageEngine return false; } + public function getFulltextTokens() { + return $this->fulltextTokens; + } + + private function getEngineLimits(AphrontDatabaseConnection $conn) { + if ($this->engineLimits === null) { + $this->engineLimits = $this->newEngineLimits($conn); + } + return $this->engineLimits; + } + + private function newEngineLimits(AphrontDatabaseConnection $conn) { + $result = queryfx_one( + $conn, + 'SELECT + @@innodb_ft_min_token_size innodb_max, + @@ft_min_word_len myisam_max, + @@ft_stopword_file myisam_stopwords'); + + if ($result['innodb_max']) { + $min_len = $result['innodb_max']; + $stopwords = queryfx_all( + $conn, + 'SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD'); + $stopwords = ipull($stopwords, 'value'); + $stopwords = array_fuse($stopwords); + } else { + $min_len = $result['myisam_max']; + + $file = $result['myisam_stopwords']; + if (preg_match('(/resources/sql/stopwords\.txt\z)', $file)) { + // If this is set to something that looks like the Phabricator + // stopword file, read that. + $file = 'stopwords.txt'; + } else { + // Otherwise, just use the default stopwords. This might be wrong + // but we can't read the actual value dynamically and reading + // whatever file the variable is set to could be a big headache + // to get right from a security perspective. + $file = 'stopwords_myisam.txt'; + } + + $root = dirname(phutil_get_library_root('phabricator')); + $data = Filesystem::readFile($root.'/resources/sql/'.$file); + $stopwords = explode("\n", $data); + $stopwords = array_filter($stopwords); + $stopwords = array_fuse($stopwords); + } + + return array($min_len, $stopwords); + } + } diff --git a/src/applications/search/query/PhabricatorFulltextResultSet.php b/src/applications/search/query/PhabricatorFulltextResultSet.php new file mode 100644 index 0000000000..decc863407 --- /dev/null +++ b/src/applications/search/query/PhabricatorFulltextResultSet.php @@ -0,0 +1,26 @@ +phids = $phids; + return $this; + } + + public function getPHIDs() { + return $this->phids; + } + + public function setFulltextTokens($fulltext_tokens) { + $this->fulltextTokens = $fulltext_tokens; + return $this; + } + + public function getFulltextTokens() { + return $this->fulltextTokens; + } + +} diff --git a/src/applications/search/query/PhabricatorFulltextToken.php b/src/applications/search/query/PhabricatorFulltextToken.php new file mode 100644 index 0000000000..d42c15e9e6 --- /dev/null +++ b/src/applications/search/query/PhabricatorFulltextToken.php @@ -0,0 +1,88 @@ +token = $token; + return $this; + } + + public function getToken() { + return $this->token; + } + + public function isQueryable() { + return !$this->getIsShort() && !$this->getIsStopword(); + } + + public function setIsShort($is_short) { + $this->isShort = $is_short; + return $this; + } + + public function getIsShort() { + return $this->isShort; + } + + public function setIsStopword($is_stopword) { + $this->isStopword = $is_stopword; + return $this; + } + + public function getIsStopword() { + return $this->isStopword; + } + + public function newTag() { + $token = $this->getToken(); + + $tip = null; + $icon = null; + + if ($this->getIsShort()) { + $shade = PHUITagView::COLOR_GREY; + $tip = pht('Ignored Short Word'); + } else if ($this->getIsStopword()) { + $shade = PHUITagView::COLOR_GREY; + $tip = pht('Ignored Common Word'); + } else { + $operator = $token->getOperator(); + switch ($operator) { + case PhutilSearchQueryCompiler::OPERATOR_NOT: + $shade = PHUITagView::COLOR_RED; + $icon = 'fa-minus'; + break; + default: + $shade = PHUITagView::COLOR_BLUE; + break; + } + } + + $tag = id(new PHUITagView()) + ->setType(PHUITagView::TYPE_SHADE) + ->setShade($shade) + ->setName($token->getValue()); + + if ($tip !== null) { + Javelin::initBehavior('phabricator-tooltips'); + + $tag + ->addSigil('has-tooltip') + ->setMetadata( + array( + 'tip' => $tip, + )); + } + + if ($icon !== null) { + $tag->setIcon($icon); + } + + return $tag; + } + +} diff --git a/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php b/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php index f9bcb6804a..d944d61964 100644 --- a/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php +++ b/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php @@ -3,6 +3,8 @@ final class PhabricatorSearchApplicationSearchEngine extends PhabricatorApplicationSearchEngine { + private $resultSet; + public function getResultTypeDescription() { return pht('Fulltext Search Results'); } @@ -243,6 +245,9 @@ final class PhabricatorSearchApplicationSearchEngine PhabricatorSavedQuery $query, array $handles) { + $result_set = $this->resultSet; + $fulltext_tokens = $result_set->getFulltextTokens(); + $viewer = $this->requireViewer(); $list = new PHUIObjectItemListView(); $list->setNoDataString(pht('No results found.')); @@ -263,7 +268,28 @@ final class PhabricatorSearchApplicationSearchEngine } } + $fulltext_view = null; + if ($fulltext_tokens) { + require_celerity_resource('phabricator-search-results-css'); + + $fulltext_view = array(); + foreach ($fulltext_tokens as $token) { + $fulltext_view[] = $token->newTag(); + } + $fulltext_view = phutil_tag( + 'div', + array( + 'class' => 'phui-fulltext-tokens', + ), + array( + pht('Searched For:'), + ' ', + $fulltext_view, + )); + } + $result = new PhabricatorApplicationSearchResultView(); + $result->setContent($fulltext_view); $result->setObjectList($list); return $result; @@ -280,4 +306,8 @@ final class PhabricatorSearchApplicationSearchEngine return $owner_phids; } + protected function didExecuteQuery(PhabricatorPolicyAwareQuery $query) { + $this->resultSet = $query->getFulltextResultSet(); + } + } diff --git a/src/applications/search/query/PhabricatorSearchDocumentQuery.php b/src/applications/search/query/PhabricatorSearchDocumentQuery.php index fb6e23c6d4..4aed4722bd 100644 --- a/src/applications/search/query/PhabricatorSearchDocumentQuery.php +++ b/src/applications/search/query/PhabricatorSearchDocumentQuery.php @@ -6,6 +6,7 @@ final class PhabricatorSearchDocumentQuery private $savedQuery; private $objectCapabilities; private $unfilteredOffset; + private $fulltextResultSet; public function withSavedQuery(PhabricatorSavedQuery $query) { $this->savedQuery = $query; @@ -25,8 +26,17 @@ final class PhabricatorSearchDocumentQuery return $this->getRequiredCapabilities(); } + public function getFulltextResultSet() { + if (!$this->fulltextResultSet) { + throw new PhutilInvalidStateException('execute'); + } + + return $this->fulltextResultSet; + } + protected function willExecute() { $this->unfilteredOffset = 0; + $this->fulltextResultSet = null; } protected function loadPage() { @@ -39,8 +49,10 @@ final class PhabricatorSearchDocumentQuery ->setParameter('offset', $this->unfilteredOffset) ->setParameter('limit', $this->getRawResultLimit()); - $phids = PhabricatorSearchService::executeSearch($query); + $result_set = PhabricatorSearchService::newResultSet($query, $this); + $phids = $result_set->getPHIDs(); + $this->fulltextResultSet = $result_set; $this->unfilteredOffset += count($phids); $handles = id(new PhabricatorHandleQuery()) diff --git a/src/infrastructure/cluster/search/PhabricatorSearchService.php b/src/infrastructure/cluster/search/PhabricatorSearchService.php index 8e5b296132..073f9ffab6 100644 --- a/src/infrastructure/cluster/search/PhabricatorSearchService.php +++ b/src/infrastructure/cluster/search/PhabricatorSearchService.php @@ -245,14 +245,25 @@ class PhabricatorSearchService * @throws PhutilAggregateException */ public static function executeSearch(PhabricatorSavedQuery $query) { + $result_set = self::newResultSet($query); + return $result_set->getPHIDs(); + } + + public static function newResultSet(PhabricatorSavedQuery $query) { $exceptions = array(); // try all services until one succeeds foreach (self::getAllServices() as $service) { + if (!$service->isReadable()) { + continue; + } + try { $engine = $service->getEngine(); - $res = $engine->executeSearch($query); - // return immediately if we get results - return $res; + $phids = $engine->executeSearch($query); + + return id(new PhabricatorFulltextResultSet()) + ->setPHIDs($phids) + ->setFulltextTokens($engine->getFulltextTokens()); } catch (PhutilSearchQueryCompilerSyntaxException $ex) { // If there's a query compilation error, return it directly to the // user: they issued a query with bad syntax. diff --git a/webroot/rsrc/css/application/search/search-results.css b/webroot/rsrc/css/application/search/search-results.css index 734d86c029..a1837fa391 100644 --- a/webroot/rsrc/css/application/search/search-results.css +++ b/webroot/rsrc/css/application/search/search-results.css @@ -16,3 +16,16 @@ font-weight: normal; color: #000; } + +.phui-fulltext-tokens { + margin: 16px 8px; + font-weight: bold; +} + +.phui-fulltext-tokens .phui-tag-view { + margin: 0 2px; +} + +.phui-fulltext-tokens .phui-tag-view.phui-tag-shade-grey { + opacity: 0.5; +} From ada9046e31cb683c2536f530415b47432b12ccc6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 12 Apr 2017 19:16:58 -0700 Subject: [PATCH 32/52] Fix a fulltext search issue where finding token length and stopwords could fail Summary: Ref T12137. If a database is missing the InnoDB or MyISAM table engines, the big combined query to get both will fail. Instead, try InnoDB first and then MyISAM. (I have both engines locally so this worked until I deployed it.) Test Plan: Faked an InnoDB error like `secure`, got a MyISAM result. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12137 Differential Revision: https://secure.phabricator.com/D17673 --- .../PhabricatorMySQLFulltextStorageEngine.php | 67 +++++++++++-------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php index 4dfcae7c43..5bb9943e59 100644 --- a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php @@ -473,43 +473,56 @@ final class PhabricatorMySQLFulltextStorageEngine } private function newEngineLimits(AphrontDatabaseConnection $conn) { - $result = queryfx_one( - $conn, - 'SELECT - @@innodb_ft_min_token_size innodb_max, - @@ft_min_word_len myisam_max, - @@ft_stopword_file myisam_stopwords'); + // First, try InnoDB. Some database may not have both table engines, so + // selecting variables from missing table engines can fail and throw. - if ($result['innodb_max']) { + try { + $result = queryfx_one( + $conn, + 'SELECT @@innodb_ft_min_token_size innodb_max'); + } catch (AphrontQueryException $ex) { + $result = null; + } + + if ($result) { $min_len = $result['innodb_max']; $stopwords = queryfx_all( $conn, 'SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD'); $stopwords = ipull($stopwords, 'value'); $stopwords = array_fuse($stopwords); - } else { - $min_len = $result['myisam_max']; - $file = $result['myisam_stopwords']; - if (preg_match('(/resources/sql/stopwords\.txt\z)', $file)) { - // If this is set to something that looks like the Phabricator - // stopword file, read that. - $file = 'stopwords.txt'; - } else { - // Otherwise, just use the default stopwords. This might be wrong - // but we can't read the actual value dynamically and reading - // whatever file the variable is set to could be a big headache - // to get right from a security perspective. - $file = 'stopwords_myisam.txt'; - } - - $root = dirname(phutil_get_library_root('phabricator')); - $data = Filesystem::readFile($root.'/resources/sql/'.$file); - $stopwords = explode("\n", $data); - $stopwords = array_filter($stopwords); - $stopwords = array_fuse($stopwords); + return array($min_len, $stopwords); } + // If InnoDB fails, try MyISAM. + $result = queryfx_one( + $conn, + 'SELECT + @@ft_min_word_len myisam_max, + @@ft_stopword_file myisam_stopwords'); + + $min_len = $result['myisam_max']; + + $file = $result['myisam_stopwords']; + if (preg_match('(/resources/sql/stopwords\.txt\z)', $file)) { + // If this is set to something that looks like the Phabricator + // stopword file, read that. + $file = 'stopwords.txt'; + } else { + // Otherwise, just use the default stopwords. This might be wrong + // but we can't read the actual value dynamically and reading + // whatever file the variable is set to could be a big headache + // to get right from a security perspective. + $file = 'stopwords_myisam.txt'; + } + + $root = dirname(phutil_get_library_root('phabricator')); + $data = Filesystem::readFile($root.'/resources/sql/'.$file); + $stopwords = explode("\n", $data); + $stopwords = array_filter($stopwords); + $stopwords = array_fuse($stopwords); + return array($min_len, $stopwords); } From 03f2a41b16a414dd68a379b9914db993f8e732d4 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 12 Apr 2017 20:44:32 -0700 Subject: [PATCH 33/52] Clean up Conpherence Transactions and notifications Summary: Does a few things. Turns off feed stories (again), removes "action" transactions from notificiations, and only updates message count on actual messages. This feels a bit cleaner and less spammy... I guess... I think @epriestley will really like it and do me a favor or something. Test Plan: Pull up two windows. test a message, see message count on second screen. Edit a topic or title, get no notification. At all. Ever. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D17674 --- resources/celerity/map.php | 6 +- ...ConpherenceNotificationPanelController.php | 2 +- .../conpherence/editor/ConpherenceEditor.php | 83 +++++++------------ .../conpherence/storage/ConpherenceThread.php | 80 +++++------------- .../application/conpherence/notification.css | 11 ++- 5 files changed, 62 insertions(+), 120 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 6518fb0cc8..b0d1e64332 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => 'b5ee2073', + 'conpherence.pkg.css' => 'f8390290', 'conpherence.pkg.js' => '281b1a73', 'core.pkg.css' => '30a64ed6', 'core.pkg.js' => 'fbc1c380', @@ -49,7 +49,7 @@ return array( 'rsrc/css/application/conpherence/header-pane.css' => '4082233d', 'rsrc/css/application/conpherence/menu.css' => '5abfb32d', 'rsrc/css/application/conpherence/message-pane.css' => 'd1fc13e1', - 'rsrc/css/application/conpherence/notification.css' => '965db05b', + 'rsrc/css/application/conpherence/notification.css' => 'cef0a3fc', 'rsrc/css/application/conpherence/participant-pane.css' => '604a8b02', 'rsrc/css/application/conpherence/transaction.css' => '85129c68', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', @@ -556,7 +556,7 @@ return array( 'conpherence-header-pane-css' => '4082233d', 'conpherence-menu-css' => '5abfb32d', 'conpherence-message-pane-css' => 'd1fc13e1', - 'conpherence-notification-css' => '965db05b', + 'conpherence-notification-css' => 'cef0a3fc', 'conpherence-participant-pane-css' => '604a8b02', 'conpherence-thread-manager' => 'c8b5ee6f', 'conpherence-transaction-css' => '85129c68', diff --git a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php index 03f9c926ca..402da2aac3 100644 --- a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php +++ b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php @@ -20,7 +20,7 @@ final class ConpherenceNotificationPanelController ->withPHIDs(array_keys($participant_data)) ->needProfileImage(true) ->needTransactions(true) - ->setTransactionLimit(50) + ->setTransactionLimit(100) ->needParticipantCache(true) ->execute(); } diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 3ffb193c86..2026eb94c4 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -87,9 +87,9 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorTransactions::TYPE_COMMENT; - $types[] = ConpherenceTransaction::TYPE_PARTICIPANTS; + + $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; @@ -101,25 +101,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return pht('%s created this room.', $author); } - protected function shouldPublishFeedStory( - PhabricatorLiskDAO $object, - array $xactions) { - - foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case ConpherenceThreadTitleTransaction::TRANSACTIONTYPE: - case ConpherenceThreadTopicTransaction::TRANSACTIONTYPE: - case ConpherenceThreadPictureTransaction::TRANSACTIONTYPE: - return true; - default: - return false; - } - } - return true; - } - - - protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { @@ -338,40 +319,40 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: $message_count++; + + // update everyone's participation status on a message -only- + $xaction_phid = $xaction->getPHID(); + $behind = ConpherenceParticipationStatus::BEHIND; + $up_to_date = ConpherenceParticipationStatus::UP_TO_DATE; + $participants = $object->getParticipants(); + $user = $this->getActor(); + $time = time(); + foreach ($participants as $phid => $participant) { + if ($phid != $user->getPHID()) { + if ($participant->getParticipationStatus() != $behind) { + $participant->setBehindTransactionPHID($xaction_phid); + $participant->setSeenMessageCount( + $object->getMessageCount() - $message_count); + } + $participant->setParticipationStatus($behind); + $participant->setDateTouched($time); + } else { + $participant->setSeenMessageCount($object->getMessageCount()); + $participant->setBehindTransactionPHID($xaction_phid); + $participant->setParticipationStatus($up_to_date); + $participant->setDateTouched($time); + } + $participant->save(); + } + + PhabricatorUserCache::clearCaches( + PhabricatorUserMessageCountCacheType::KEY_COUNT, + array_keys($participants)); + break; } } - // update everyone's participation status on the last xaction -only- - $xaction = end($xactions); - $xaction_phid = $xaction->getPHID(); - $behind = ConpherenceParticipationStatus::BEHIND; - $up_to_date = ConpherenceParticipationStatus::UP_TO_DATE; - $participants = $object->getParticipants(); - $user = $this->getActor(); - $time = time(); - foreach ($participants as $phid => $participant) { - if ($phid != $user->getPHID()) { - if ($participant->getParticipationStatus() != $behind) { - $participant->setBehindTransactionPHID($xaction_phid); - $participant->setSeenMessageCount( - $object->getMessageCount() - $message_count); - } - $participant->setParticipationStatus($behind); - $participant->setDateTouched($time); - } else { - $participant->setSeenMessageCount($object->getMessageCount()); - $participant->setBehindTransactionPHID($xaction_phid); - $participant->setParticipationStatus($up_to_date); - $participant->setDateTouched($time); - } - $participant->save(); - } - - PhabricatorUserCache::clearCaches( - PhabricatorUserMessageCountCacheType::KEY_COUNT, - array_keys($participants)); - if ($xactions) { $data = array( 'type' => 'message', diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index b8d5a8dce0..17a3de6c50 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -240,71 +240,33 @@ final class ConpherenceThread extends ConpherenceDAO $transactions = array(); } - if ($transactions) { - $subtitle_mode = 'message'; - } else { - $subtitle_mode = 'recent'; - } - - $lucky_phid = head($this->getOtherRecentParticipantPHIDs($viewer)); - if ($lucky_phid) { - $lucky_handle = $handles[$lucky_phid]; - } else { - // This will be just the user talking to themselves. Weirdo. - $lucky_handle = reset($handles); - } - $img_src = $this->getProfileImageURI(); - $message_title = null; - if ($subtitle_mode == 'message') { - $message_transaction = null; - $action_transaction = null; - foreach ($transactions as $transaction) { - if ($message_transaction || $action_transaction) { - break; - } - switch ($transaction->getTransactionType()) { - case PhabricatorTransactions::TYPE_COMMENT: - $message_transaction = $transaction; - break; - case ConpherenceThreadTitleTransaction::TRANSACTIONTYPE: - case ConpherenceThreadTopicTransaction::TRANSACTIONTYPE: - case ConpherenceThreadPictureTransaction::TRANSACTIONTYPE: - case ConpherenceTransaction::TYPE_PARTICIPANTS: - $action_transaction = $transaction; - break; - default: - break; - } - } + $message_transaction = null; + foreach ($transactions as $transaction) { if ($message_transaction) { - $message_handle = $handles[$message_transaction->getAuthorPHID()]; - $message_title = sprintf( - '%s: %s', - $message_handle->getName(), - id(new PhutilUTF8StringTruncator()) - ->setMaximumGlyphs(60) - ->truncateString( - $message_transaction->getComment()->getContent())); + break; } - if ($action_transaction) { - $message_title = id(clone $action_transaction) - ->setRenderingTarget(PhabricatorApplicationTransaction::TARGET_TEXT) - ->getTitle(); + switch ($transaction->getTransactionType()) { + case PhabricatorTransactions::TYPE_COMMENT: + $message_transaction = $transaction; + break; + default: + break; } } - switch ($subtitle_mode) { - case 'recent': - $subtitle = $this->getRecentParticipantsString($viewer); - break; - case 'message': - if ($message_title) { - $subtitle = $message_title; - } else { - $subtitle = $this->getRecentParticipantsString($viewer); - } - break; + if ($message_transaction) { + $message_handle = $handles[$message_transaction->getAuthorPHID()]; + $subtitle = sprintf( + '%s: %s', + $message_handle->getName(), + id(new PhutilUTF8StringTruncator()) + ->setMaximumGlyphs(60) + ->truncateString( + $message_transaction->getComment()->getContent())); + } else { + // Kinda lame, but maybe add last message to cache? + $subtitle = pht('No recent messages'); } $user_participation = $this->getParticipantIfExists($viewer->getPHID()); diff --git a/webroot/rsrc/css/application/conpherence/notification.css b/webroot/rsrc/css/application/conpherence/notification.css index 6347b40ee3..21e73047a6 100644 --- a/webroot/rsrc/css/application/conpherence/notification.css +++ b/webroot/rsrc/css/application/conpherence/notification.css @@ -8,7 +8,7 @@ .phabricator-notification .conpherence-menu-item-view { display: block; - height: 46px; + height: 48px; overflow: hidden; position: relative; text-decoration: none; @@ -23,8 +23,8 @@ left: 8px; display: block; position: absolute; - width: 30px; - height: 30px; + width: 32px; + height: 32px; background-size: 100%; border-radius: 3px; } @@ -33,7 +33,7 @@ .conpherence-menu-item-title { display: block; margin-top: 8px; - margin-left: 46px; + margin-left: 48px; text-align: left; font-weight: bold; font-size: {$normalfontsize}; @@ -49,8 +49,7 @@ display: block; color: {$lightgreytext}; font-size: {$smallestfontsize}; - margin-top: 2px; - margin-left: 46px; + margin-left: 48px; width: 290px; text-overflow: ellipsis; white-space: nowrap; From 3d6049d0da5ce5e94760e8828e79bf5f4df367ec Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 13 Apr 2017 09:04:11 -0700 Subject: [PATCH 34/52] Remove CAN_JOIN policy from Conpherence Summary: Fixes T12178, Fixes T11704 Not sure this feature gets any use and I can't find a similar option in other software, so removing it I think simiplifies a number of things. Removes CAN_JOIN and joinable is basically now CAN_VIEW and !$participating. Also removed some old transaction strings for other policies. Don't seem used. Test Plan: Create a new room, edit room policies, see changes. Log into second account, search for rooms, everything now is visible. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12178, T11704 Differential Revision: https://secure.phabricator.com/D17675 --- .../controller/ConpherenceController.php | 6 +--- .../ConpherenceNewRoomController.php | 10 ------ .../ConpherenceUpdateController.php | 21 +---------- .../controller/ConpherenceViewController.php | 12 ++----- .../conpherence/editor/ConpherenceEditor.php | 23 +++--------- .../conpherence/storage/ConpherenceThread.php | 6 +--- .../storage/ConpherenceTransaction.php | 36 ------------------- 7 files changed, 10 insertions(+), 104 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index e6690a55d1..09708b7148 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -78,10 +78,6 @@ abstract class ConpherenceController extends PhabricatorController { } $participating = $conpherence->getParticipantIfExists($viewer->getPHID()); - $can_join = PhabricatorPolicyFilter::hasCapability( - $viewer, - $conpherence, - PhabricatorPolicyCapability::CAN_JOIN); $header->addActionItem( id(new PHUIIconCircleView()) @@ -129,7 +125,7 @@ abstract class ConpherenceController extends PhabricatorController { ->setColor('green') ->addClass('conpherence-search-toggle')); - if ($can_join && !$participating) { + if (!$participating) { $action = ConpherenceUpdateActions::JOIN_ROOM; $uri = $this->getApplicationURI('update/'.$conpherence->getID().'/'); $button = phutil_tag( diff --git a/src/applications/conpherence/controller/ConpherenceNewRoomController.php b/src/applications/conpherence/controller/ConpherenceNewRoomController.php index 1f27a6807e..d70395dbc9 100644 --- a/src/applications/conpherence/controller/ConpherenceNewRoomController.php +++ b/src/applications/conpherence/controller/ConpherenceNewRoomController.php @@ -34,9 +34,6 @@ final class ConpherenceNewRoomController extends ConpherenceController { $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) ->setNewValue($request->getStr('editPolicy')); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_JOIN_POLICY) - ->setNewValue($request->getStr('joinPolicy')); try { $editor @@ -55,7 +52,6 @@ final class ConpherenceNewRoomController extends ConpherenceController { $conpherence->setViewPolicy($request->getStr('viewPolicy')); $conpherence->setEditPolicy($request->getStr('editPolicy')); - $conpherence->setJoinPolicy($request->getStr('joinPolicy')); } } else { if ($request->getStr('participant')) { @@ -110,12 +106,6 @@ final class ConpherenceNewRoomController extends ConpherenceController { ->setName('editPolicy') ->setPolicyObject($conpherence) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('joinPolicy') - ->setPolicyObject($conpherence) - ->setCapability(PhabricatorPolicyCapability::CAN_JOIN) ->setPolicies($policies)); $dialog->appendChild($form); diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 44e6c3cf4b..b8ebe5a826 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -24,9 +24,6 @@ final class ConpherenceUpdateController case ConpherenceUpdateActions::METADATA: $needed_capabilities[] = PhabricatorPolicyCapability::CAN_EDIT; break; - case ConpherenceUpdateActions::JOIN_ROOM: - $needed_capabilities[] = PhabricatorPolicyCapability::CAN_JOIN; - break; case ConpherenceUpdateActions::NOTIFICATIONS: $need_participants = true; break; @@ -153,9 +150,6 @@ final class ConpherenceUpdateController $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) ->setNewValue($request->getStr('editPolicy')); - $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_JOIN_POLICY) - ->setNewValue($request->getStr('joinPolicy')); if (!$request->getExists('force_ajax')) { $response_mode = 'redirect'; } @@ -256,16 +250,9 @@ final class ConpherenceUpdateController $participant = $conpherence->getParticipantIfExists($user->getPHID()); if (!$participant) { - $can_join = PhabricatorPolicyFilter::hasCapability( - $user, - $conpherence, - PhabricatorPolicyCapability::CAN_JOIN); - if ($can_join) { + if ($user->isLoggedIn()) { $text = pht( 'Notification settings are available after joining the room.'); - } else if ($user->isLoggedIn()) { - $text = pht( - 'Notification settings not applicable to rooms you can not join.'); } else { $text = pht( 'Notification settings are available after logging in and joining '. @@ -459,12 +446,6 @@ final class ConpherenceUpdateController ->setName('editPolicy') ->setPolicyObject($conpherence) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('joinPolicy') - ->setPolicyObject($conpherence) - ->setCapability(PhabricatorPolicyCapability::CAN_JOIN) ->setPolicies($policies)); $view = id(new AphrontDialogView()) diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 1fbb338822..57fe194845 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -119,11 +119,6 @@ final class ConpherenceViewController extends return id(new AphrontAjaxResponse())->setContent($content); } - $can_join = PhabricatorPolicyFilter::hasCapability( - $user, - $conpherence, - PhabricatorPolicyCapability::CAN_JOIN); - $layout = id(new ConpherenceLayoutView()) ->setUser($user) ->setBaseURI($this->getApplicationURI()) @@ -151,12 +146,9 @@ final class ConpherenceViewController extends $conpherence = $this->getConpherence(); $user = $this->getRequest()->getUser(); - $can_join = PhabricatorPolicyFilter::hasCapability( - $user, - $conpherence, - PhabricatorPolicyCapability::CAN_JOIN); + $participating = $conpherence->getParticipantIfExists($user->getPHID()); - if (!$can_join && !$participating && $user->isLoggedIn()) { + if (!$participating && $user->isLoggedIn()) { return null; } $draft = PhabricatorDraft::newFromUserAndKey( diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 2026eb94c4..e4728f3c00 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -92,7 +92,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; return $types; } @@ -383,24 +382,12 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $actor_phid = $this->requireActor()->getPHID(); - $is_join = (($add === array($actor_phid)) && !$rem); - $is_leave = (($rem === array($actor_phid)) && !$add); + // You need CAN_EDIT to change participants other than yourself. + PhabricatorPolicyFilter::requireCapability( + $this->requireActor(), + $object, + PhabricatorPolicyCapability::CAN_EDIT); - if ($is_join) { - // You need CAN_JOIN to join a room. - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_JOIN); - } else if ($is_leave) { - // You don't need any capabilities to leave a conpherence thread. - } else { - // You need CAN_EDIT to change participants other than yourself. - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_EDIT); - } break; case ConpherenceThreadTitleTransaction::TRANSACTIONTYPE: case ConpherenceThreadTopicTransaction::TRANSACTIONTYPE: diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index 17a3de6c50..db6d4a7e61 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -33,7 +33,7 @@ final class ConpherenceThread extends ConpherenceDAO ->attachParticipants(array()) ->setViewPolicy($default_policy) ->setEditPolicy($default_policy) - ->setJoinPolicy($default_policy); + ->setJoinPolicy(''); } protected function getConfiguration() { @@ -298,7 +298,6 @@ final class ConpherenceThread extends ConpherenceDAO return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, - PhabricatorPolicyCapability::CAN_JOIN, ); } @@ -308,8 +307,6 @@ final class ConpherenceThread extends ConpherenceDAO return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); - case PhabricatorPolicyCapability::CAN_JOIN: - return $this->getJoinPolicy(); } return PhabricatorPolicies::POLICY_NOONE; } @@ -322,7 +319,6 @@ final class ConpherenceThread extends ConpherenceDAO switch ($capability) { case PhabricatorPolicyCapability::CAN_EDIT: - case PhabricatorPolicyCapability::CAN_JOIN: return false; } diff --git a/src/applications/conpherence/storage/ConpherenceTransaction.php b/src/applications/conpherence/storage/ConpherenceTransaction.php index b449037235..c692dffb98 100644 --- a/src/applications/conpherence/storage/ConpherenceTransaction.php +++ b/src/applications/conpherence/storage/ConpherenceTransaction.php @@ -53,11 +53,6 @@ final class ConpherenceTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_VIEW_POLICY: - case PhabricatorTransactions::TYPE_EDIT_POLICY: - case PhabricatorTransactions::TYPE_JOIN_POLICY: - return $this->getRoomTitle(); - break; case self::TYPE_PARTICIPANTS: $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -90,37 +85,6 @@ final class ConpherenceTransaction return parent::getTitle(); } - private function getRoomTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_VIEW_POLICY: - return pht( - '%s changed the visibility of this room from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderPolicyName($old, 'old'), - $this->renderPolicyName($new, 'new')); - break; - case PhabricatorTransactions::TYPE_EDIT_POLICY: - return pht( - '%s changed the edit policy of this room from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderPolicyName($old, 'old'), - $this->renderPolicyName($new, 'new')); - break; - case PhabricatorTransactions::TYPE_JOIN_POLICY: - return pht( - '%s changed the join policy of this room from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $this->renderPolicyName($old, 'old'), - $this->renderPolicyName($new, 'new')); - break; - } - } - public function getRequiredHandlePHIDs() { $phids = parent::getRequiredHandlePHIDs(); From 9d56a3d86ec7336c619779b63000a6e9f8eedf25 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 12 Apr 2017 18:19:31 -0700 Subject: [PATCH 35/52] Reimplement Countdown transactions using Modular Transaction framework Test Plan: owls Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17671 --- src/__phutil_library_map__.php | 10 +- .../editor/PhabricatorCountdownEditEngine.php | 9 +- .../editor/PhabricatorCountdownEditor.php | 124 ------------------ .../PhabricatorCountdownTransaction.php | 87 +----------- ...ricatorCountdownDescriptionTransaction.php | 55 ++++++++ .../PhabricatorCountdownEpochTransaction.php | 58 ++++++++ .../PhabricatorCountdownTitleTransaction.php | 54 ++++++++ .../PhabricatorCountdownTransactionType.php | 4 + 8 files changed, 192 insertions(+), 209 deletions(-) create mode 100644 src/applications/countdown/xaction/PhabricatorCountdownDescriptionTransaction.php create mode 100644 src/applications/countdown/xaction/PhabricatorCountdownEpochTransaction.php create mode 100644 src/applications/countdown/xaction/PhabricatorCountdownTitleTransaction.php create mode 100644 src/applications/countdown/xaction/PhabricatorCountdownTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 237a4ee392..4a92977988 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2413,9 +2413,11 @@ phutil_register_library_map(array( 'PhabricatorCountdownDAO' => 'applications/countdown/storage/PhabricatorCountdownDAO.php', 'PhabricatorCountdownDefaultEditCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultEditCapability.php', 'PhabricatorCountdownDefaultViewCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultViewCapability.php', + 'PhabricatorCountdownDescriptionTransaction' => 'applications/countdown/xaction/PhabricatorCountdownDescriptionTransaction.php', 'PhabricatorCountdownEditController' => 'applications/countdown/controller/PhabricatorCountdownEditController.php', 'PhabricatorCountdownEditEngine' => 'applications/countdown/editor/PhabricatorCountdownEditEngine.php', 'PhabricatorCountdownEditor' => 'applications/countdown/editor/PhabricatorCountdownEditor.php', + 'PhabricatorCountdownEpochTransaction' => 'applications/countdown/xaction/PhabricatorCountdownEpochTransaction.php', 'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php', 'PhabricatorCountdownMailReceiver' => 'applications/countdown/mail/PhabricatorCountdownMailReceiver.php', 'PhabricatorCountdownQuery' => 'applications/countdown/query/PhabricatorCountdownQuery.php', @@ -2423,9 +2425,11 @@ phutil_register_library_map(array( 'PhabricatorCountdownReplyHandler' => 'applications/countdown/mail/PhabricatorCountdownReplyHandler.php', 'PhabricatorCountdownSchemaSpec' => 'applications/countdown/storage/PhabricatorCountdownSchemaSpec.php', 'PhabricatorCountdownSearchEngine' => 'applications/countdown/query/PhabricatorCountdownSearchEngine.php', + 'PhabricatorCountdownTitleTransaction' => 'applications/countdown/xaction/PhabricatorCountdownTitleTransaction.php', 'PhabricatorCountdownTransaction' => 'applications/countdown/storage/PhabricatorCountdownTransaction.php', 'PhabricatorCountdownTransactionComment' => 'applications/countdown/storage/PhabricatorCountdownTransactionComment.php', 'PhabricatorCountdownTransactionQuery' => 'applications/countdown/query/PhabricatorCountdownTransactionQuery.php', + 'PhabricatorCountdownTransactionType' => 'applications/countdown/xaction/PhabricatorCountdownTransactionType.php', 'PhabricatorCountdownView' => 'applications/countdown/view/PhabricatorCountdownView.php', 'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php', 'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php', @@ -7516,9 +7520,11 @@ phutil_register_library_map(array( 'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO', 'PhabricatorCountdownDefaultEditCapability' => 'PhabricatorPolicyCapability', 'PhabricatorCountdownDefaultViewCapability' => 'PhabricatorPolicyCapability', + 'PhabricatorCountdownDescriptionTransaction' => 'PhabricatorCountdownTransactionType', 'PhabricatorCountdownEditController' => 'PhabricatorCountdownController', 'PhabricatorCountdownEditEngine' => 'PhabricatorEditEngine', 'PhabricatorCountdownEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorCountdownEpochTransaction' => 'PhabricatorCountdownTransactionType', 'PhabricatorCountdownListController' => 'PhabricatorCountdownController', 'PhabricatorCountdownMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorCountdownQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', @@ -7526,9 +7532,11 @@ phutil_register_library_map(array( 'PhabricatorCountdownReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorCountdownSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorCountdownSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'PhabricatorCountdownTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorCountdownTitleTransaction' => 'PhabricatorCountdownTransactionType', + 'PhabricatorCountdownTransaction' => 'PhabricatorModularTransaction', 'PhabricatorCountdownTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorCountdownTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorCountdownTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorCountdownView' => 'AphrontView', 'PhabricatorCountdownViewController' => 'PhabricatorCountdownController', 'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery', diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php b/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php index c1d5f6753a..844cd6d9df 100644 --- a/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php +++ b/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php @@ -81,7 +81,8 @@ final class PhabricatorCountdownEditEngine ->setKey('name') ->setLabel(pht('Name')) ->setIsRequired(true) - ->setTransactionType(PhabricatorCountdownTransaction::TYPE_TITLE) + ->setTransactionType( + PhabricatorCountdownTitleTransaction::TRANSACTIONTYPE) ->setDescription(pht('The countdown name.')) ->setConduitDescription(pht('Rename the countdown.')) ->setConduitTypeDescription(pht('New countdown name.')) @@ -89,7 +90,8 @@ final class PhabricatorCountdownEditEngine id(new PhabricatorEpochEditField()) ->setKey('epoch') ->setLabel(pht('End Date')) - ->setTransactionType(PhabricatorCountdownTransaction::TYPE_EPOCH) + ->setTransactionType( + PhabricatorCountdownEpochTransaction::TRANSACTIONTYPE) ->setDescription(pht('Date when the countdown ends.')) ->setConduitDescription(pht('Change the end date of the countdown.')) ->setConduitTypeDescription(pht('New countdown end date.')) @@ -97,7 +99,8 @@ final class PhabricatorCountdownEditEngine id(new PhabricatorRemarkupEditField()) ->setKey('description') ->setLabel(pht('Description')) - ->setTransactionType(PhabricatorCountdownTransaction::TYPE_DESCRIPTION) + ->setTransactionType( + PhabricatorCountdownDescriptionTransaction::TRANSACTIONTYPE) ->setDescription(pht('Description of the countdown.')) ->setConduitDescription(pht('Change the countdown description.')) ->setConduitTypeDescription(pht('New description.')) diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditor.php b/src/applications/countdown/editor/PhabricatorCountdownEditor.php index 37f23f6bd6..322b2ee2c3 100644 --- a/src/applications/countdown/editor/PhabricatorCountdownEditor.php +++ b/src/applications/countdown/editor/PhabricatorCountdownEditor.php @@ -14,10 +14,6 @@ final class PhabricatorCountdownEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorCountdownTransaction::TYPE_TITLE; - $types[] = PhabricatorCountdownTransaction::TYPE_EPOCH; - $types[] = PhabricatorCountdownTransaction::TYPE_DESCRIPTION; - $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_SPACE; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; @@ -27,126 +23,6 @@ final class PhabricatorCountdownEditor return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorCountdownTransaction::TYPE_TITLE: - return $object->getTitle(); - case PhabricatorCountdownTransaction::TYPE_DESCRIPTION: - return $object->getDescription(); - case PhabricatorCountdownTransaction::TYPE_EPOCH: - return $object->getEpoch(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorCountdownTransaction::TYPE_TITLE: - return $xaction->getNewValue(); - case PhabricatorCountdownTransaction::TYPE_DESCRIPTION: - return $xaction->getNewValue(); - case PhabricatorCountdownTransaction::TYPE_EPOCH: - return $xaction->getNewValue()->getEpoch(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $type = $xaction->getTransactionType(); - switch ($type) { - case PhabricatorCountdownTransaction::TYPE_TITLE: - $object->setTitle($xaction->getNewValue()); - return; - case PhabricatorCountdownTransaction::TYPE_DESCRIPTION: - $object->setDescription($xaction->getNewValue()); - return; - case PhabricatorCountdownTransaction::TYPE_EPOCH: - $object->setEpoch($xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $type = $xaction->getTransactionType(); - switch ($type) { - case PhabricatorCountdownTransaction::TYPE_TITLE: - return; - case PhabricatorCountdownTransaction::TYPE_DESCRIPTION: - return; - case PhabricatorCountdownTransaction::TYPE_EPOCH: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case PhabricatorCountdownTransaction::TYPE_TITLE: - $missing = $this->validateIsEmptyTextField( - $object->getTitle(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('You must give the countdown a name.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; - case PhabricatorCountdownTransaction::TYPE_EPOCH: - if (!$object->getEpoch() && !$xactions) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('You must give the countdown an end date.'), - null); - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - - foreach ($xactions as $xaction) { - $value = $xaction->getNewValue(); - if (!$value->isValid()) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('You must give the countdown a valid end date.'), - $xaction); - $errors[] = $error; - } - } - break; - } - - return $errors; - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php index 247466ffe1..78e0a54ced 100644 --- a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php +++ b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php @@ -1,11 +1,7 @@ getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_TITLE: - return pht( - '%s renamed this countdown from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - case self::TYPE_DESCRIPTION: - return pht( - '%s edited the description of this countdown.', - $this->renderHandleLink($author_phid)); - case self::TYPE_EPOCH: - return pht( - '%s updated this countdown to end on %s.', - $this->renderHandleLink($author_phid), - phabricator_datetime($new, $this->getViewer())); - } - - return parent::getTitle(); - } - - public function getTitleForFeed() { - $author_phid = $this->getAuthorPHID(); - $object_phid = $this->getObjectPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $type = $this->getTransactionType(); - switch ($type) { - case self::TYPE_TITLE: - return pht( - '%s renamed %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_DESCRIPTION: - return pht( - '%s edited the description of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - case self::TYPE_EPOCH: - return pht( - '%s edited the end date of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - - return parent::getTitleForFeed(); + public function getBaseTransactionClass() { + return 'PhabricatorCountdownTransactionType'; } public function getMailTags() { @@ -88,9 +30,9 @@ final class PhabricatorCountdownTransaction case PhabricatorTransactions::TYPE_COMMENT: $tags[] = self::MAILTAG_COMMENT; break; - case self::TYPE_TITLE: - case self::TYPE_EPOCH: - case self::TYPE_DESCRIPTION: + case PhabricatorCountdownTitleTransaction::TRANSACTIONTYPE: + case PhabricatorCountdownEpochTransaction::TRANSACTIONTYPE: + case PhabricatorCountdownDescriptionTransaction::TRANSACTIONTYPE: $tags[] = self::MAILTAG_DETAILS; break; default: @@ -100,21 +42,4 @@ final class PhabricatorCountdownTransaction return $tags; } - - public function hasChangeDetails() { - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($this->getOldValue() !== null); - } - - return parent::hasChangeDetails(); - } - - public function renderChangeDetails(PhabricatorUser $viewer) { - return $this->renderTextCorpusChangeDetails( - $viewer, - $this->getOldValue(), - $this->getNewValue()); - } - } diff --git a/src/applications/countdown/xaction/PhabricatorCountdownDescriptionTransaction.php b/src/applications/countdown/xaction/PhabricatorCountdownDescriptionTransaction.php new file mode 100644 index 0000000000..a742ac2125 --- /dev/null +++ b/src/applications/countdown/xaction/PhabricatorCountdownDescriptionTransaction.php @@ -0,0 +1,55 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + '%s updated the countdown description.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the countdown description for %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO COUNTDOWN DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } +} diff --git a/src/applications/countdown/xaction/PhabricatorCountdownEpochTransaction.php b/src/applications/countdown/xaction/PhabricatorCountdownEpochTransaction.php new file mode 100644 index 0000000000..905e0771d8 --- /dev/null +++ b/src/applications/countdown/xaction/PhabricatorCountdownEpochTransaction.php @@ -0,0 +1,58 @@ +getEpoch(); + } + + public function generateNewValue($object, $value) { + return $value->newPhutilDateTime() + ->newAbsoluteDateTime() + ->getEpoch(); + } + + public function applyInternalEffects($object, $value) { + $object->setEpoch($value); + } + + public function getTitle() { + return pht( + '%s updated the countdown end from %s to %s.', + $this->renderAuthor(), + $this->renderOldDate(), + $this->renderNewDate()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the countdown end for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldDate(), + $this->renderNewDate()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$object->getEpoch() && !$xactions) { + $errors[] = $this->newRequiredError( + pht('You must give the countdown an end date.')); + return $errors; + } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + if (!$value->isValid()) { + $errors[] = $this->newInvalidError( + pht('You must give the countdown an end date.')); + } + } + + return $errors; + } +} diff --git a/src/applications/countdown/xaction/PhabricatorCountdownTitleTransaction.php b/src/applications/countdown/xaction/PhabricatorCountdownTitleTransaction.php new file mode 100644 index 0000000000..dca96c10c1 --- /dev/null +++ b/src/applications/countdown/xaction/PhabricatorCountdownTitleTransaction.php @@ -0,0 +1,54 @@ +getTitle(); + } + + public function applyInternalEffects($object, $value) { + $object->setTitle($value); + } + + public function getTitle() { + return pht( + '%s updated the title for this countdown from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the title for this countdown from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getTitle(), $xactions)) { + $errors[] = $this->newRequiredError(pht('Countdowns must have a title.')); + } + + $max_length = $object->getColumnMaximumByteLength('title'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'Countdown titles must not be longer than %s character(s).', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/countdown/xaction/PhabricatorCountdownTransactionType.php b/src/applications/countdown/xaction/PhabricatorCountdownTransactionType.php new file mode 100644 index 0000000000..be0a8c6f78 --- /dev/null +++ b/src/applications/countdown/xaction/PhabricatorCountdownTransactionType.php @@ -0,0 +1,4 @@ + Date: Thu, 13 Apr 2017 11:20:51 -0700 Subject: [PATCH 36/52] Fix Durable Column CSS-Overload Summary: This moves the count on the Conpherence Menu Item into a phui-list-item-count, and removes the CSS call to the entire Conphrence stack when durable column is open. Test Plan: Test with and without the chat column, and a menu with a count Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17677 --- resources/celerity/map.php | 16 ++++++++-------- .../PhabricatorConpherenceProfileMenuItem.php | 3 +-- .../application/conpherence/durable-column.css | 8 ++++---- .../rsrc/css/application/conpherence/menu.css | 8 +------- webroot/rsrc/css/phui/phui-list.css | 14 ++++++++++++++ 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b0d1e64332..e9c86db2e9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,9 +7,9 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => 'f8390290', + 'conpherence.pkg.css' => '437d3b5a', 'conpherence.pkg.js' => '281b1a73', - 'core.pkg.css' => '30a64ed6', + 'core.pkg.css' => '9139007e', 'core.pkg.js' => 'fbc1c380', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '90b30783', @@ -45,9 +45,9 @@ return array( 'rsrc/css/application/config/config-template.css' => '8f18fa41', 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', - 'rsrc/css/application/conpherence/durable-column.css' => '292c71f0', + 'rsrc/css/application/conpherence/durable-column.css' => '89ea6bef', 'rsrc/css/application/conpherence/header-pane.css' => '4082233d', - 'rsrc/css/application/conpherence/menu.css' => '5abfb32d', + 'rsrc/css/application/conpherence/menu.css' => '3d8e5c9c', 'rsrc/css/application/conpherence/message-pane.css' => 'd1fc13e1', 'rsrc/css/application/conpherence/notification.css' => 'cef0a3fc', 'rsrc/css/application/conpherence/participant-pane.css' => '604a8b02', @@ -157,7 +157,7 @@ return array( 'rsrc/css/phui/phui-info-view.css' => 'ec92802a', 'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0', 'rsrc/css/phui/phui-lightbox.css' => '0a035e40', - 'rsrc/css/phui/phui-list.css' => 'a3ec3cf1', + 'rsrc/css/phui/phui-list.css' => '12eb8ce6', 'rsrc/css/phui/phui-object-box.css' => '8b289e3d', 'rsrc/css/phui/phui-pager.css' => '77d8a794', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', @@ -552,9 +552,9 @@ return array( 'conduit-api-css' => '7bc725c4', 'config-options-css' => '0ede4c9b', 'config-page-css' => 'c1d5121b', - 'conpherence-durable-column-view' => '292c71f0', + 'conpherence-durable-column-view' => '89ea6bef', 'conpherence-header-pane-css' => '4082233d', - 'conpherence-menu-css' => '5abfb32d', + 'conpherence-menu-css' => '3d8e5c9c', 'conpherence-message-pane-css' => 'd1fc13e1', 'conpherence-notification-css' => 'cef0a3fc', 'conpherence-participant-pane-css' => '604a8b02', @@ -860,7 +860,7 @@ return array( 'phui-inline-comment-view-css' => 'be663c95', 'phui-invisible-character-view-css' => '6993d9f0', 'phui-lightbox-css' => '0a035e40', - 'phui-list-view-css' => 'a3ec3cf1', + 'phui-list-view-css' => '12eb8ce6', 'phui-object-box-css' => '8b289e3d', 'phui-oi-big-ui-css' => '19f9369b', 'phui-oi-color-css' => 'cd2b9b77', diff --git a/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php index 18342cb357..cd41d41148 100644 --- a/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php @@ -36,7 +36,6 @@ final class PhabricatorConpherenceProfileMenuItem } public function willBuildNavigationItems(array $items) { - require_celerity_resource('conpherence-menu-css'); $viewer = $this->getViewer(); $room_phids = array(); foreach ($items as $item) { @@ -115,7 +114,7 @@ final class PhabricatorConpherenceProfileMenuItem $count = phutil_tag( 'span', array( - 'class' => 'conpherence-menu-item-count', + 'class' => 'phui-list-item-count', ), $unread_count); } diff --git a/webroot/rsrc/css/application/conpherence/durable-column.css b/webroot/rsrc/css/application/conpherence/durable-column.css index 2b0ab85e44..fdbdbe848f 100644 --- a/webroot/rsrc/css/application/conpherence/durable-column.css +++ b/webroot/rsrc/css/application/conpherence/durable-column.css @@ -65,7 +65,7 @@ font-size: 10px; } -.conpherence-durable-column-header .phabricator-application-menu +.device-desktop .conpherence-durable-column-header .phabricator-application-menu .phui-list-item-view { margin: 0; width: 28px; @@ -73,7 +73,7 @@ min-width: 28px; } -.conpherence-durable-column-header .phabricator-application-menu +.device-desktop .conpherence-durable-column-header .phabricator-application-menu .phui-list-item-href { background: transparent; border: none; @@ -300,7 +300,7 @@ img { .minimize-column .conpherence-durable-column .conpherence-durable-column-header .phabricator-application-menu - .phui-list-item-icon.phui-font-fa:hover { + .phui-list-item-view:hover .phui-list-item-icon.phui-font-fa { color: {$darkbluetext}; } @@ -311,6 +311,6 @@ img { } .minimize-column .conpherence-durable-column .phabricator-application-menu - .conpherence-settings-icon { + .fa-gear { display: none; } diff --git a/webroot/rsrc/css/application/conpherence/menu.css b/webroot/rsrc/css/application/conpherence/menu.css index 775dfaed43..e0719cdcc8 100644 --- a/webroot/rsrc/css/application/conpherence/menu.css +++ b/webroot/rsrc/css/application/conpherence/menu.css @@ -167,8 +167,7 @@ } .conpherence-menu .conpherence-menu-item-view -.conpherence-menu-item-unread-count, -.conpherence-menu-item-count { +.conpherence-menu-item-unread-count { position: absolute; right: 4px; top: 10px; @@ -180,11 +179,6 @@ font-size: {$smallestfontsize}; } -.phui-list-item-view .conpherence-menu-item-count { - right: 7px; - top: 7px; -} - .conpherence-menu .hide-unread-count .conpherence-menu-item-unread-count, .conpherence-menu .conpherence-selected .conpherence-menu-item-unread-count { display: none; diff --git a/webroot/rsrc/css/phui/phui-list.css b/webroot/rsrc/css/phui/phui-list.css index eadd1987ee..e571e228d8 100644 --- a/webroot/rsrc/css/phui/phui-list.css +++ b/webroot/rsrc/css/phui/phui-list.css @@ -228,3 +228,17 @@ .phui-list-item-action-icon { opacity: 1; } + +/* - Item Counts ----------------------------------------------------------- */ + +.phui-list-item-count { + position: absolute; + right: 7px; + top: 7px; + background: {$blue}; + border-radius: 2px; + color: #fff; + font-weight: bold; + padding: 0 5px 1px; + font-size: {$smallestfontsize}; +} From 4189eb810b4c03302d488c947e569aefb60b3c9d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 13 Apr 2017 12:16:07 -0700 Subject: [PATCH 37/52] Use violet with not-verified user tags Summary: Will see how this goes in practice. Uses violet where color is used for non responsive peeps. Test Plan: Create a user without email verification, test hover card, profile, mentions and lists. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17678 --- resources/celerity/map.php | 10 +++++----- .../people/markup/PhabricatorMentionRemarkupRule.php | 2 +- .../people/phid/PhabricatorPeopleUserPHIDType.php | 2 +- .../people/view/PhabricatorUserCardView.php | 2 +- src/applications/phid/PhabricatorObjectHandle.php | 1 + .../rsrc/css/application/base/standard-page-view.css | 4 ++++ webroot/rsrc/css/phui/phui-tag-view.css | 6 +++--- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e9c86db2e9..33e3549d2b 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '437d3b5a', 'conpherence.pkg.js' => '281b1a73', - 'core.pkg.css' => '9139007e', + 'core.pkg.css' => 'b2ad82f4', 'core.pkg.js' => 'fbc1c380', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '90b30783', @@ -37,7 +37,7 @@ return array( 'rsrc/css/application/base/main-menu-view.css' => '5294060f', 'rsrc/css/application/base/notification-menu.css' => '6a697e43', 'rsrc/css/application/base/phui-theme.css' => '9f261c6b', - 'rsrc/css/application/base/standard-page-view.css' => '285cedf3', + 'rsrc/css/application/base/standard-page-view.css' => '89da5a9c', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => '0ede4c9b', @@ -166,7 +166,7 @@ return array( 'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', - 'rsrc/css/phui/phui-tag-view.css' => '84d65f26', + 'rsrc/css/phui/phui-tag-view.css' => 'cc4fd402', 'rsrc/css/phui/phui-timeline-view.css' => '1d7ef61d', 'rsrc/css/phui/phui-two-column-view.css' => 'ce9fa0b7', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', @@ -798,7 +798,7 @@ return array( 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => '4383192f', - 'phabricator-standard-page-view' => '285cedf3', + 'phabricator-standard-page-view' => '89da5a9c', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', 'phabricator-tooltip' => '8fadb715', @@ -875,7 +875,7 @@ return array( 'phui-segment-bar-view-css' => 'b1d1b892', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => 'd5263e49', - 'phui-tag-view-css' => '84d65f26', + 'phui-tag-view-css' => 'cc4fd402', 'phui-theme-css' => '9f261c6b', 'phui-timeline-view-css' => '1d7ef61d', 'phui-two-column-view-css' => 'ce9fa0b7', diff --git a/src/applications/people/markup/PhabricatorMentionRemarkupRule.php b/src/applications/people/markup/PhabricatorMentionRemarkupRule.php index 778384e946..1f82d78423 100644 --- a/src/applications/people/markup/PhabricatorMentionRemarkupRule.php +++ b/src/applications/people/markup/PhabricatorMentionRemarkupRule.php @@ -151,7 +151,7 @@ final class PhabricatorMentionRemarkupRule extends PhutilRemarkupRule { } if (!$user->isResponsive()) { - $tag->setDotColor(PHUITagView::COLOR_GREY); + $tag->setDotColor(PHUITagView::COLOR_VIOLET); } else { if ($user->getAwayUntil()) { $away = PhabricatorCalendarEventInvitee::AVAILABILITY_AWAY; diff --git a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php index 2d773121a0..b2a456cd51 100644 --- a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php +++ b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php @@ -62,7 +62,7 @@ final class PhabricatorPeopleUserPHIDType extends PhabricatorPHIDType { $availability = null; if (!$user->isResponsive()) { - $availability = PhabricatorObjectHandle::AVAILABILITY_DISABLED; + $availability = PhabricatorObjectHandle::AVAILABILITY_NOEMAIL; } else { $until = $user->getAwayUntil(); if ($until) { diff --git a/src/applications/people/view/PhabricatorUserCardView.php b/src/applications/people/view/PhabricatorUserCardView.php index 5b6d34b90c..4f4f15a33d 100644 --- a/src/applications/people/view/PhabricatorUserCardView.php +++ b/src/applications/people/view/PhabricatorUserCardView.php @@ -68,7 +68,7 @@ final class PhabricatorUserCardView extends AphrontTagView { } else if (!$user->getIsEmailVerified()) { $tag_icon = 'fa-envelope'; $tag_title = pht('Email Not Verified'); - $tag_shade = PHUITagView::COLOR_RED; + $tag_shade = PHUITagView::COLOR_VIOLET; } else if ($user->getIsAdmin()) { $tag_icon = 'fa-star'; $tag_title = pht('Administrator'); diff --git a/src/applications/phid/PhabricatorObjectHandle.php b/src/applications/phid/PhabricatorObjectHandle.php index e6230c4ae6..49255fd9e8 100644 --- a/src/applications/phid/PhabricatorObjectHandle.php +++ b/src/applications/phid/PhabricatorObjectHandle.php @@ -6,6 +6,7 @@ final class PhabricatorObjectHandle const AVAILABILITY_FULL = 'full'; const AVAILABILITY_NONE = 'none'; + const AVAILABILITY_NOEMAIL = 'no-email'; const AVAILABILITY_PARTIAL = 'partial'; const AVAILABILITY_DISABLED = 'disabled'; diff --git a/webroot/rsrc/css/application/base/standard-page-view.css b/webroot/rsrc/css/application/base/standard-page-view.css index f0e5e0cbe3..b4386764ab 100644 --- a/webroot/rsrc/css/application/base/standard-page-view.css +++ b/webroot/rsrc/css/application/base/standard-page-view.css @@ -84,6 +84,10 @@ a.handle-status-closed:hover { color: {$orange}; } +.handle-availability-no-email .perfect-circle { + color: {$violet}; +} + .handle-availability-disabled .perfect-circle { color: {$greytext}; } diff --git a/webroot/rsrc/css/phui/phui-tag-view.css b/webroot/rsrc/css/phui/phui-tag-view.css index f14909bc06..d5b2710d7b 100644 --- a/webroot/rsrc/css/phui/phui-tag-view.css +++ b/webroot/rsrc/css/phui/phui-tag-view.css @@ -46,11 +46,11 @@ a.phui-tag-view:hover { .phui-tag-dot { position: relative; display: inline-block; - width: 6px; - height: 6px; + width: 5px; + height: 5px; margin-right: 4px; top: -1px; - border-radius: 6px; + border-radius: 5px; border: 1px solid transparent; } From d902d2ac6b396d327fe3aea9cf719f0d3bc6918f Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Thu, 13 Apr 2017 12:17:18 -0700 Subject: [PATCH 38/52] Implement countdown.search and countdown.edit Summary: adds new conduit methods for countdown.edit and countdown.search Test Plan: Search: {P2037} Edit: {P2038} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12524 Differential Revision: https://secure.phabricator.com/D17679 --- src/__phutil_library_map__.php | 5 +++ .../conduit/CountdownEditConduitAPIMethod.php | 18 +++++++++ .../CountdownSearchConduitAPIMethod.php | 18 +++++++++ .../storage/PhabricatorCountdown.php | 40 ++++++++++++++++++- 4 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 src/applications/countdown/conduit/CountdownEditConduitAPIMethod.php create mode 100644 src/applications/countdown/conduit/CountdownSearchConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4a92977988..250e741a55 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -338,6 +338,8 @@ phutil_register_library_map(array( 'ConpherenceUpdateController' => 'applications/conpherence/controller/ConpherenceUpdateController.php', 'ConpherenceUpdateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php', 'ConpherenceViewController' => 'applications/conpherence/controller/ConpherenceViewController.php', + 'CountdownEditConduitAPIMethod' => 'applications/countdown/conduit/CountdownEditConduitAPIMethod.php', + 'CountdownSearchConduitAPIMethod' => 'applications/countdown/conduit/CountdownSearchConduitAPIMethod.php', 'DarkConsoleController' => 'applications/console/controller/DarkConsoleController.php', 'DarkConsoleCore' => 'applications/console/core/DarkConsoleCore.php', 'DarkConsoleDataController' => 'applications/console/controller/DarkConsoleDataController.php', @@ -5127,6 +5129,8 @@ phutil_register_library_map(array( 'ConpherenceUpdateController' => 'ConpherenceController', 'ConpherenceUpdateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceViewController' => 'ConpherenceController', + 'CountdownEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', + 'CountdownSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'DarkConsoleController' => 'PhabricatorController', 'DarkConsoleCore' => 'Phobject', 'DarkConsoleDataController' => 'PhabricatorController', @@ -7513,6 +7517,7 @@ phutil_register_library_map(array( 'PhabricatorSpacesInterface', 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorConduitResultInterface', ), 'PhabricatorCountdownApplication' => 'PhabricatorApplication', 'PhabricatorCountdownController' => 'PhabricatorController', diff --git a/src/applications/countdown/conduit/CountdownEditConduitAPIMethod.php b/src/applications/countdown/conduit/CountdownEditConduitAPIMethod.php new file mode 100644 index 0000000000..f4b39d0dd5 --- /dev/null +++ b/src/applications/countdown/conduit/CountdownEditConduitAPIMethod.php @@ -0,0 +1,18 @@ +openTransaction(); $this->delete(); $this->saveTransaction(); } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('title') + ->setType('string') + ->setDescription(pht('The title of the countdown.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('description') + ->setType('remarkup') + ->setDescription(pht('The description of the countdown.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('epoch') + ->setType('epoch') + ->setDescription(pht('The end date of the countdown.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'title' => $this->getTitle(), + 'description' => array( + 'raw' => $this->getDescription(), + ), + 'epoch' => (int)$this->getEpoch(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } From 5c5d3c35a780a9e9a920e5e31e5ec685f473a459 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 13 Apr 2017 12:58:08 -0700 Subject: [PATCH 39/52] Convert date-marker to ModularTransaction in Conpherence Summary: Swaps this transaction over. Test Plan: Load up a few rooms with date markers, still render as expected. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12550 Differential Revision: https://secure.phabricator.com/D17680 --- src/__phutil_library_map__.php | 2 ++ .../conpherence/ConpherenceTransactionRenderer.php | 3 ++- .../conpherence/storage/ConpherenceTransaction.php | 5 ----- .../conpherence/view/ConpherenceTransactionView.php | 2 +- .../xaction/ConpherenceThreadDateMarkerTransaction.php | 8 ++++++++ 5 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 src/applications/conpherence/xaction/ConpherenceThreadDateMarkerTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 250e741a55..f403a4ed7e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -316,6 +316,7 @@ phutil_register_library_map(array( 'ConpherenceTestCase' => 'applications/conpherence/__tests__/ConpherenceTestCase.php', 'ConpherenceThread' => 'applications/conpherence/storage/ConpherenceThread.php', 'ConpherenceThreadDatasource' => 'applications/conpherence/typeahead/ConpherenceThreadDatasource.php', + 'ConpherenceThreadDateMarkerTransaction' => 'applications/conpherence/xaction/ConpherenceThreadDateMarkerTransaction.php', 'ConpherenceThreadIndexEngineExtension' => 'applications/conpherence/engineextension/ConpherenceThreadIndexEngineExtension.php', 'ConpherenceThreadListView' => 'applications/conpherence/view/ConpherenceThreadListView.php', 'ConpherenceThreadMailReceiver' => 'applications/conpherence/mail/ConpherenceThreadMailReceiver.php', @@ -5107,6 +5108,7 @@ phutil_register_library_map(array( 'PhabricatorNgramsInterface', ), 'ConpherenceThreadDatasource' => 'PhabricatorTypeaheadDatasource', + 'ConpherenceThreadDateMarkerTransaction' => 'ConpherenceThreadTransactionType', 'ConpherenceThreadIndexEngineExtension' => 'PhabricatorIndexEngineExtension', 'ConpherenceThreadListView' => 'AphrontView', 'ConpherenceThreadMailReceiver' => 'PhabricatorObjectMailReceiver', diff --git a/src/applications/conpherence/ConpherenceTransactionRenderer.php b/src/applications/conpherence/ConpherenceTransactionRenderer.php index 187247e063..5a6eaffaf2 100644 --- a/src/applications/conpherence/ConpherenceTransactionRenderer.php +++ b/src/applications/conpherence/ConpherenceTransactionRenderer.php @@ -60,7 +60,8 @@ final class ConpherenceTransactionRenderer extends Phobject { // between days. some setup required! $previous_transaction = null; $date_marker_transaction = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_DATE_MARKER) + ->setTransactionType( + ConpherenceThreadDateMarkerTransaction::TRANSACTIONTYPE) ->makeEphemeral(); $date_marker_transaction_view = id(new ConpherenceTransactionView()) ->setUser($user) diff --git a/src/applications/conpherence/storage/ConpherenceTransaction.php b/src/applications/conpherence/storage/ConpherenceTransaction.php index c692dffb98..2fb3fca380 100644 --- a/src/applications/conpherence/storage/ConpherenceTransaction.php +++ b/src/applications/conpherence/storage/ConpherenceTransaction.php @@ -4,7 +4,6 @@ final class ConpherenceTransaction extends PhabricatorModularTransaction { const TYPE_PARTICIPANTS = 'participants'; - const TYPE_DATE_MARKER = 'date-marker'; public function getApplicationName() { return 'conpherence'; @@ -39,8 +38,6 @@ final class ConpherenceTransaction switch ($this->getTransactionType()) { case self::TYPE_PARTICIPANTS: return ($old === null); - case self::TYPE_DATE_MARKER: - return false; } return parent::shouldHide(); @@ -93,8 +90,6 @@ final class ConpherenceTransaction $phids[] = $this->getAuthorPHID(); switch ($this->getTransactionType()) { - case self::TYPE_DATE_MARKER: - break; case self::TYPE_PARTICIPANTS: $phids = array_merge($phids, $this->getOldValue()); $phids = array_merge($phids, $this->getNewValue()); diff --git a/src/applications/conpherence/view/ConpherenceTransactionView.php b/src/applications/conpherence/view/ConpherenceTransactionView.php index 28dcf5c361..d976fb604f 100644 --- a/src/applications/conpherence/view/ConpherenceTransactionView.php +++ b/src/applications/conpherence/view/ConpherenceTransactionView.php @@ -67,7 +67,7 @@ final class ConpherenceTransactionView extends AphrontView { $transaction = $this->getConpherenceTransaction(); switch ($transaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_DATE_MARKER: + case ConpherenceThreadDateMarkerTransaction::TRANSACTIONTYPE: return javelin_tag( 'div', array( diff --git a/src/applications/conpherence/xaction/ConpherenceThreadDateMarkerTransaction.php b/src/applications/conpherence/xaction/ConpherenceThreadDateMarkerTransaction.php new file mode 100644 index 0000000000..3a7819ba4e --- /dev/null +++ b/src/applications/conpherence/xaction/ConpherenceThreadDateMarkerTransaction.php @@ -0,0 +1,8 @@ + Date: Thu, 13 Apr 2017 13:01:18 -0700 Subject: [PATCH 40/52] Remove old Countdown route Summary: removes old phabricator.com/countdown/{id} route and code that uses that URL scheme Test Plan: loaded phabricator.com/countdown, verified that generated links point to phabricator.com/CXXX Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12524 Differential Revision: https://secure.phabricator.com/D17681 --- .../countdown/application/PhabricatorCountdownApplication.php | 2 -- .../countdown/query/PhabricatorCountdownSearchEngine.php | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/applications/countdown/application/PhabricatorCountdownApplication.php b/src/applications/countdown/application/PhabricatorCountdownApplication.php index 2e7b8d1bbd..6cead5a01c 100644 --- a/src/applications/countdown/application/PhabricatorCountdownApplication.php +++ b/src/applications/countdown/application/PhabricatorCountdownApplication.php @@ -42,8 +42,6 @@ final class PhabricatorCountdownApplication extends PhabricatorApplication { '/countdown/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorCountdownListController', - '(?P[1-9]\d*)/' - => 'PhabricatorCountdownViewController', 'comment/(?P[1-9]\d*)/' => 'PhabricatorCountdownCommentController', $this->getEditRoutePattern('edit/') diff --git a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php index 00512cb07c..2f9fe9c0e8 100644 --- a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php +++ b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php @@ -110,7 +110,7 @@ final class PhabricatorCountdownSearchEngine ->setObject($countdown) ->setObjectName($countdown->getMonogram()) ->setHeader($countdown->getTitle()) - ->setHref($this->getApplicationURI("{$id}/")) + ->setHref($countdown->getURI()) ->addByline( pht( 'Created by %s', From 5587abf04c8bafce4d27f1f53aa0b77981fcc533 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 13 Apr 2017 13:30:05 -0700 Subject: [PATCH 41/52] Remove recentParticipants from ConpherenceThread Summary: We no longer display this any more in the UI, so go ahead and remove the callsites and db column. Test Plan: New Room, with and without participants. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17683 --- .../20170413.conpherence.01.recentparty.sql | 2 + .../__tests__/ConpherenceRoomTestCase.php | 17 +---- ...ConpherenceQueryThreadConduitAPIMethod.php | 1 - .../ConpherenceColumnViewController.php | 1 - .../controller/ConpherenceListController.php | 1 - ...ConpherenceNotificationPanelController.php | 1 - .../ConpherenceUpdateController.php | 2 - .../controller/ConpherenceViewController.php | 1 - .../conpherence/editor/ConpherenceEditor.php | 41 +---------- .../query/ConpherenceThreadQuery.php | 9 --- .../query/ConpherenceThreadSearchEngine.php | 11 +-- .../conpherence/storage/ConpherenceThread.php | 72 +------------------ .../PhabricatorConpherenceProfileMenuItem.php | 1 - 13 files changed, 7 insertions(+), 153 deletions(-) create mode 100644 resources/sql/autopatches/20170413.conpherence.01.recentparty.sql diff --git a/resources/sql/autopatches/20170413.conpherence.01.recentparty.sql b/resources/sql/autopatches/20170413.conpherence.01.recentparty.sql new file mode 100644 index 0000000000..996a058c5b --- /dev/null +++ b/resources/sql/autopatches/20170413.conpherence.01.recentparty.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_conpherence.conpherence_thread + DROP COLUMN recentParticipantPHIDs; diff --git a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php index 1fb4dbabaa..85c4b9c1f1 100644 --- a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php +++ b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php @@ -16,9 +16,6 @@ final class ConpherenceRoomTestCase extends ConpherenceTestCase { $this->assertTrue((bool)$conpherence->getID()); $this->assertEqual(1, count($conpherence->getParticipants())); - $this->assertEqual( - $participant_phids, - $conpherence->getRecentParticipantPHIDs()); } public function testNUserRoomCreate() { @@ -38,9 +35,6 @@ final class ConpherenceRoomTestCase extends ConpherenceTestCase { $this->assertTrue((bool)$conpherence->getID()); $this->assertEqual(4, count($conpherence->getParticipants())); - $this->assertEqual( - $participant_phids, - $conpherence->getRecentParticipantPHIDs()); } public function testRoomParticipantAddition() { @@ -58,16 +52,11 @@ final class ConpherenceRoomTestCase extends ConpherenceTestCase { $this->assertTrue((bool)$conpherence->getID()); $this->assertEqual(2, count($conpherence->getParticipants())); - $this->assertEqual( - $participant_phids, - $conpherence->getRecentParticipantPHIDs()); // test add by creator $participant_phids[] = $friend_2->getPHID(); $this->addParticipants($creator, $conpherence, array($friend_2->getPHID())); - $this->assertEqual( - $participant_phids, - $conpherence->getRecentParticipantPHIDs()); + $this->assertEqual(3, count($conpherence->getParticipants())); // test add by other participant, so recent participation should // meaningfully change @@ -81,9 +70,7 @@ final class ConpherenceRoomTestCase extends ConpherenceTestCase { $friend_2, $conpherence, array($friend_3->getPHID())); - $this->assertEqual( - $participant_phids, - $conpherence->getRecentParticipantPHIDs()); + $this->assertEqual(4, count($conpherence->getParticipants())); } public function testRoomParticipantDeletion() { diff --git a/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php index c66a90f585..fd602058a3 100644 --- a/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php +++ b/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php @@ -71,7 +71,6 @@ final class ConpherenceQueryThreadConduitAPIMethod 'conpherencePHID' => $conpherence->getPHID(), 'conpherenceTitle' => $conpherence->getTitle(), 'messageCount' => $conpherence->getMessageCount(), - 'recentParticipantPHIDs' => $conpherence->getRecentParticipantPHIDs(), 'conpherenceURI' => $this->getConpherenceURI($conpherence), ); } diff --git a/src/applications/conpherence/controller/ConpherenceColumnViewController.php b/src/applications/conpherence/controller/ConpherenceColumnViewController.php index de83379aab..aa4f94edfc 100644 --- a/src/applications/conpherence/controller/ConpherenceColumnViewController.php +++ b/src/applications/conpherence/controller/ConpherenceColumnViewController.php @@ -17,7 +17,6 @@ final class ConpherenceColumnViewController extends ->setViewer($user) ->withPHIDs($conpherence_phids) ->needProfileImage(true) - ->needParticipantCache(true) ->execute(); $latest_conpherences = mpull($latest_conpherences, null, 'getPHID'); $latest_conpherences = array_select_keys( diff --git a/src/applications/conpherence/controller/ConpherenceListController.php b/src/applications/conpherence/controller/ConpherenceListController.php index 4e81526004..00968af576 100644 --- a/src/applications/conpherence/controller/ConpherenceListController.php +++ b/src/applications/conpherence/controller/ConpherenceListController.php @@ -159,7 +159,6 @@ final class ConpherenceListController extends ConpherenceController { ->setViewer($user) ->withPHIDs($conpherence_phids) ->needProfileImage(true) - ->needParticipantCache(true) ->execute(); // this will re-sort by participation data diff --git a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php index 402da2aac3..ed728c6ab7 100644 --- a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php +++ b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php @@ -21,7 +21,6 @@ final class ConpherenceNotificationPanelController ->needProfileImage(true) ->needTransactions(true) ->setTransactionLimit(100) - ->needParticipantCache(true) ->execute(); } diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index b8ebe5a826..9cc666109c 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -470,7 +470,6 @@ final class ConpherenceUpdateController $latest_transaction_id) { $need_transactions = false; - $need_participant_cache = true; switch ($action) { case ConpherenceUpdateActions::METADATA: case ConpherenceUpdateActions::LOAD: @@ -491,7 +490,6 @@ final class ConpherenceUpdateController ->setViewer($user) ->setAfterTransactionID($latest_transaction_id) ->needProfileImage(true) - ->needParticipantCache($need_participant_cache) ->needParticipants(true) ->needTransactions($need_transactions) ->withIDs(array($conpherence_id)) diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 57fe194845..6b953b9de4 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -20,7 +20,6 @@ final class ConpherenceViewController extends ->setViewer($user) ->withIDs(array($conpherence_id)) ->needProfileImage(true) - ->needParticipantCache(true) ->needTransactions(true) ->setTransactionLimit($this->getMainQueryLimit()); diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index e4728f3c00..e358700348 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -190,7 +190,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { ->setSeenMessageCount($message_count) ->save(); $object->attachParticipants($participants); - $object->setRecentParticipantPHIDs(array_keys($participants)); } break; } @@ -201,39 +200,12 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { - $make_author_recent_participant = true; switch ($xaction->getTransactionType()) { case ConpherenceTransaction::TYPE_PARTICIPANTS: - if (!$this->getIsNewObject()) { - $old_map = array_fuse($xaction->getOldValue()); - $new_map = array_fuse($xaction->getNewValue()); - // if we added people, add them to the end of "recent" participants - $add = array_keys(array_diff_key($new_map, $old_map)); - // if we remove people, then definintely remove them from "recent" - // participants - $del = array_keys(array_diff_key($old_map, $new_map)); - if ($add || $del) { - $participants = $object->getRecentParticipantPHIDs(); - if ($add) { - $participants = array_merge($participants, $add); - } - if ($del) { - $participants = array_diff($participants, $del); - $actor = $this->requireActor(); - if (in_array($actor->getPHID(), $del)) { - $make_author_recent_participant = false; - } - } - $participants = array_slice(array_unique($participants), 0, 10); - $object->setRecentParticipantPHIDs($participants); - } - } + if (!$this->getIsNewObject()) {} break; } - if ($make_author_recent_participant) { - $this->makeAuthorMostRecentParticipant($object, $xaction); - } } protected function applyBuiltinInternalTransaction( @@ -249,17 +221,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return parent::applyBuiltinInternalTransaction($object, $xaction); } - private function makeAuthorMostRecentParticipant( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $participants = $object->getRecentParticipantPHIDs(); - array_unshift($participants, $xaction->getAuthorPHID()); - $participants = array_slice(array_unique($participants), 0, 10); - - $object->setRecentParticipantPHIDs($participants); - } - protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php index 48dc3323e0..1bb96537dc 100644 --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -10,18 +10,12 @@ final class ConpherenceThreadQuery private $participantPHIDs; private $needParticipants; private $needTransactions; - private $needParticipantCache; private $afterTransactionID; private $beforeTransactionID; private $transactionLimit; private $fulltext; private $needProfileImage; - public function needParticipantCache($participant_cache) { - $this->needParticipantCache = $participant_cache; - return $this; - } - public function needParticipants($need) { $this->needParticipants = $need; return $this; @@ -101,9 +95,6 @@ final class ConpherenceThreadQuery if ($conpherences) { $conpherences = mpull($conpherences, null, 'getPHID'); $this->loadParticipantsAndInitHandles($conpherences); - if ($this->needParticipantCache) { - $this->loadCoreHandles($conpherences, 'getRecentParticipantPHIDs'); - } if ($this->needParticipants) { $this->loadCoreHandles($conpherences, 'getParticipantPHIDs'); } diff --git a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php index 4e0a89d266..cbaf43b0a9 100644 --- a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php +++ b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php @@ -13,7 +13,6 @@ final class ConpherenceThreadSearchEngine public function newQuery() { return id(new ConpherenceThreadQuery()) - ->needParticipantCache(true) ->needProfileImage(true); } @@ -92,14 +91,6 @@ final class ConpherenceThreadSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } - protected function getRequiredHandlePHIDsForResultList( - array $conpherences, - PhabricatorSavedQuery $query) { - - $recent = mpull($conpherences, 'getRecentParticipantPHIDs'); - return array_unique(array_mergev($recent)); - } - protected function renderResultList( array $conpherences, PhabricatorSavedQuery $query, @@ -153,7 +144,7 @@ final class ConpherenceThreadSearchEngine $list->setUser($viewer); foreach ($conpherences as $conpherence_phid => $conpherence) { $created = phabricator_date($conpherence->getDateCreated(), $viewer); - $title = $conpherence->getDisplayTitle($viewer); + $title = $conpherence->getTitle(); $monogram = $conpherence->getMonogram(); $icon_name = $conpherence->getPolicyIconName($policy_objects); diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index db6d4a7e61..f0d0e60bb3 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -12,7 +12,6 @@ final class ConpherenceThread extends ConpherenceDAO protected $topic; protected $profileImagePHID; protected $messageCount; - protected $recentParticipantPHIDs = array(); protected $mailKey; protected $viewPolicy; protected $editPolicy; @@ -39,9 +38,6 @@ final class ConpherenceThread extends ConpherenceDAO protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, - self::CONFIG_SERIALIZATION => array( - 'recentParticipantPHIDs' => self::SERIALIZATION_JSON, - ), self::CONFIG_COLUMN_SCHEMA => array( 'title' => 'text255?', 'topic' => 'text255', @@ -165,72 +161,6 @@ final class ConpherenceThread extends ConpherenceDAO return pht('Private Room'); } - /** - * Get the thread's display title for a user. - * - * If a thread doesn't have a title set, this will return a string describing - * recent participants. - * - * @param PhabricatorUser Viewer. - * @return string Thread title. - */ - public function getDisplayTitle(PhabricatorUser $viewer) { - $title = $this->getTitle(); - if (strlen($title)) { - return $title; - } - - return $this->getRecentParticipantsString($viewer); - } - - - /** - * Get recent participants (other than the viewer) as a string. - * - * For example, this method might return "alincoln, htaft, gwashington...". - * - * @param PhabricatorUser Viewer. - * @return string Description of other participants. - */ - private function getRecentParticipantsString(PhabricatorUser $viewer) { - $handles = $this->getHandles(); - $phids = $this->getOtherRecentParticipantPHIDs($viewer); - - if (count($phids) == 0) { - $phids[] = $viewer->getPHID(); - $more = false; - } else { - $limit = 3; - $more = (count($phids) > $limit); - $phids = array_slice($phids, 0, $limit); - } - - $names = array_select_keys($handles, $phids); - $names = mpull($names, 'getName'); - $names = implode(', ', $names); - - if ($more) { - $names = $names.'...'; - } - - return $names; - } - - - /** - * Get PHIDs for recent participants who are not the viewer. - * - * @param PhabricatorUser Viewer. - * @return list Participants who are not the viewer. - */ - private function getOtherRecentParticipantPHIDs(PhabricatorUser $viewer) { - $phids = $this->getRecentParticipantPHIDs(); - $phids = array_fuse($phids); - unset($phids[$viewer->getPHID()]); - return array_values($phids); - } - - public function getDisplayData(PhabricatorUser $viewer) { $handles = $this->getHandles(); @@ -277,7 +207,7 @@ final class ConpherenceThread extends ConpherenceDAO } $unread_count = $this->getMessageCount() - $user_seen_count; - $title = $this->getDisplayTitle($viewer); + $title = $this->getTitle(); $topic = $this->getTopic(); return array( diff --git a/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php index cd41d41148..6a91188c8f 100644 --- a/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php @@ -45,7 +45,6 @@ final class PhabricatorConpherenceProfileMenuItem $rooms = id(new ConpherenceThreadQuery()) ->setViewer($viewer) ->withPHIDs($room_phids) - ->needParticipantCache(true) ->needProfileImage(true) ->execute(); From bfffd807d6457a17011338c2d432f3097b64132c Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Thu, 13 Apr 2017 13:18:16 -0700 Subject: [PATCH 42/52] Change syntax highlighting for custom phabricator dot configs Test Plan: Created new paste with title '.arcconfig' without choosing a language; observed that the paste gets highlighted as JSON. JSON mode: {F4901762} Javascript mode: {F4901763} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T11667 Differential Revision: https://secure.phabricator.com/D17682 --- .../option/PhabricatorSyntaxHighlightingConfigOptions.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php b/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php index 75d3e8ad6d..c4e62694f8 100644 --- a/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php +++ b/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php @@ -122,9 +122,9 @@ final class PhabricatorSyntaxHighlightingConfigOptions 'syntax.filemap', 'wild', array( - '@\.arcconfig$@' => 'js', - '@\.arclint$@' => 'js', - '@\.divinerconfig$@' => 'js', + '@\.arcconfig$@' => 'json', + '@\.arclint$@' => 'json', + '@\.divinerconfig$@' => 'json', )) ->setSummary( pht('Override what language files (based on filename) highlight as.')) From 980d6cb70b944986f3382ae846f7c3fee36fd1d1 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Thu, 13 Apr 2017 13:48:30 -0700 Subject: [PATCH 43/52] Add validation for config settings of type regex Summary: Also fixes insufficiently-escaped regex examples Test Plan: Made several changes to http://local.phacility.com/config/edit/syntax.filemap/ and observed validation failures on malformed regexes, and success on well-formed regexes. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12532 Differential Revision: https://secure.phabricator.com/D17684 --- src/__phutil_library_map__.php | 2 ++ .../PhabricatorConfigRegexOptionType.php | 18 ++++++++++++++++++ ...bricatorSyntaxHighlightingConfigOptions.php | 10 ++++++---- 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 src/applications/config/custom/PhabricatorConfigRegexOptionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f403a4ed7e..d9fca984f1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2373,6 +2373,7 @@ phutil_register_library_map(array( 'PhabricatorConfigPageView' => 'applications/config/view/PhabricatorConfigPageView.php', 'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php', 'PhabricatorConfigPurgeCacheController' => 'applications/config/controller/PhabricatorConfigPurgeCacheController.php', + 'PhabricatorConfigRegexOptionType' => 'applications/config/custom/PhabricatorConfigRegexOptionType.php', 'PhabricatorConfigRequestExceptionHandlerModule' => 'applications/config/module/PhabricatorConfigRequestExceptionHandlerModule.php', 'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php', 'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php', @@ -7473,6 +7474,7 @@ phutil_register_library_map(array( 'PhabricatorConfigPageView' => 'AphrontTagView', 'PhabricatorConfigProxySource' => 'PhabricatorConfigSource', 'PhabricatorConfigPurgeCacheController' => 'PhabricatorConfigController', + 'PhabricatorConfigRegexOptionType' => 'PhabricatorConfigJSONOptionType', 'PhabricatorConfigRequestExceptionHandlerModule' => 'PhabricatorConfigModule', 'PhabricatorConfigResponse' => 'AphrontStandaloneHTMLResponse', 'PhabricatorConfigSchemaQuery' => 'Phobject', diff --git a/src/applications/config/custom/PhabricatorConfigRegexOptionType.php b/src/applications/config/custom/PhabricatorConfigRegexOptionType.php new file mode 100644 index 0000000000..4f39543160 --- /dev/null +++ b/src/applications/config/custom/PhabricatorConfigRegexOptionType.php @@ -0,0 +1,18 @@ + $spec) { + $ok = preg_match($pattern, ''); + if ($ok === false) { + throw new Exception( + pht( + 'The following regex is malformed and cannot be used: %s', + $pattern)); + } + } + } + +} diff --git a/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php b/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php index c4e62694f8..27596f7f07 100644 --- a/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php +++ b/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php @@ -120,7 +120,7 @@ final class PhabricatorSyntaxHighlightingConfigOptions 'this is where that list is defined.')), $this->newOption( 'syntax.filemap', - 'wild', + 'custom:PhabricatorConfigRegexOptionType', array( '@\.arcconfig$@' => 'json', '@\.arclint$@' => 'json', @@ -138,12 +138,14 @@ final class PhabricatorSyntaxHighlightingConfigOptions 'be tested against the filename. They should map to either an '. 'explicit language as a string value, or a numeric index into '. 'the captured groups as an integer.')) - ->addExample('{"@\\.xyz$@": "php"}', pht('Highlight %s as PHP.', '*.xyz')) ->addExample( - '{"@/httpd\\.conf@": "apacheconf"}', + '{"@\\\.xyz$@": "php"}', + pht('Highlight %s as PHP.', '*.xyz')) + ->addExample( + '{"@/httpd\\\.conf@": "apacheconf"}', pht('Highlight httpd.conf as "apacheconf".')) ->addExample( - '{"@\\.([^.]+)\\.bak$@": 1}', + '{"@\\\.([^.]+)\\\.bak$@": 1}', pht( "Treat all '*.x.bak' file as '.x'. NOTE: We map to capturing group ". "1 by specifying the mapping as '1'")), From 69053a40f94706c5a71f44c542bf8c88e005caea Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 14 Apr 2017 05:06:20 -0700 Subject: [PATCH 44/52] Dirty the SSH key cache when usernames change Summary: Fixes T12554. The SSH key cache contains usernames, but is not currently dirtied on username changes. An alternative solution would be to use user PHIDs instead of usernames in the file, which would make this unnecessary, but that would make debugging a bit harder. For now, I think this small added complexity is worth the easier debugging, but we could look at this again if cache management gets harder in the future. Test Plan: - Added a key as `ducksey`, ran `bin/ssh-auth`, saw key immediately. - Renamed `ducksey` to `ducker`, ran `bin/ssh-auth`, saw username change immediately. - Added another key as `ducker`, ran `bin/ssh-auth`, saw key immediately. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12554 Differential Revision: https://secure.phabricator.com/D17687 --- .../auth/editor/PhabricatorAuthSSHKeyEditor.php | 4 +--- src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php | 6 ++++++ src/applications/people/editor/PhabricatorUserEditor.php | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php b/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php index 4d04707598..0962b7b56a 100644 --- a/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php +++ b/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php @@ -197,9 +197,7 @@ final class PhabricatorAuthSSHKeyEditor // After making any change to an SSH key, drop the authfile cache so it // is regenerated the next time anyone authenticates. - $cache = PhabricatorCaches::getMutableCache(); - $authfile_key = PhabricatorAuthSSHKeyQuery::AUTHFILE_CACHEKEY; - $cache->deleteKey($authfile_key); + PhabricatorAuthSSHKeyQuery::deleteSSHKeyCache(); return $xactions; } diff --git a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php index 6ba047d100..77b666ea44 100644 --- a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php +++ b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php @@ -11,6 +11,12 @@ final class PhabricatorAuthSSHKeyQuery private $keys; private $isActive; + public static function deleteSSHKeyCache() { + $cache = PhabricatorCaches::getMutableCache(); + $authfile_key = self::AUTHFILE_CACHEKEY; + $cache->deleteKey($authfile_key); + } + public function withIDs(array $ids) { $this->ids = $ids; return $this; diff --git a/src/applications/people/editor/PhabricatorUserEditor.php b/src/applications/people/editor/PhabricatorUserEditor.php index 3370fb428b..cb5ed65f8d 100644 --- a/src/applications/people/editor/PhabricatorUserEditor.php +++ b/src/applications/people/editor/PhabricatorUserEditor.php @@ -195,6 +195,10 @@ final class PhabricatorUserEditor extends PhabricatorEditor { $user->saveTransaction(); + // The SSH key cache currently includes usernames, so dirty it. See T12554 + // for discussion. + PhabricatorAuthSSHKeyQuery::deleteSSHKeyCache(); + $user->sendUsernameChangeEmail($actor, $old_username); } From 1e43d57c81252c4921d1999db9423a7232ed5976 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 14 Apr 2017 05:27:29 -0700 Subject: [PATCH 45/52] When closing tasks with "Fixes xxx", try to act more authentically as the acting user Summary: Via HackerOne (). When we close commits in response to "Fixes Txxx", we currently act as the omnipotent user. This allows users to close tasks they can't see by pushing commits with "Fixes Txxx" in the message. However, we can't actually tell who authored or committed a change: we're just using the "Author" and "Committer" values from Git in most cases, and anyone can forge those. So we can't really get this right, in a security sense. (We can tell who //pushed// a change if we host it, but that's often not the right user. If GPG signing was more prevalent, we could use that. In the future, we could use side channels like having `arc land` tell Phabrcator who was pushing changes.) Since I think the impact of this is fairly minor and this isn't //really// a security issue (more of a confusion/abuse/product issue) I think the behavior is okay more-or-less as-is, but we can do better when we do identify an author: drop permissions, and use their privileges to load the tasks which the commit "fixes". This effectively implements this rule: > If we identify the author of a commit as user X, that commit can only affect tasks which user X can see and edit. Note that: - Commits which we can't identify the author for can still affect any task. - Any user can forge any other user's identity (or an invalid identity) and affect any task. So this is just a guard rail to prevent mistakes by good-faith users who type the wrong task IDs, not a real security measure. Also note that to perform this "attack" you must already have commit access to a repository (or permission to create a repository). Test Plan: - Used `bin/repository reparse --message --force-autoclose` to run the relevant code. - Made the code `throw` before it actually applied the edit. - Verified that the edit was rejected if the author was recognized and can not see or could not edit the task. - Verified that the edit is accepted if the author can see+edit the task. - Verified that the edit is accepted if we can't figure out who the author is. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17688 --- ...torRepositoryCommitMessageParserWorker.php | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index fec7c1f8af..767cfe5290 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -163,6 +163,8 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $author_phid, id(new PhabricatorDiffusionApplication())->getPHID()); + $acting_user = $this->loadActingUser($actor, $acting_as_phid); + $conn_w = id(new DifferentialRevision())->establishConnection('w'); // NOTE: The `differential_commit` table has a unique ID on `commitPHID`, @@ -263,7 +265,8 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $acting_as_phid, $repository, $commit, - $message); + $message, + $acting_user); } $data->save(); @@ -287,7 +290,22 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $acting_as, PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit, - $message) { + $message, + PhabricatorUser $acting_user = null) { + + // If we we were able to identify an author for the commit, we try to act + // as that user when loading tasks marked with "Fixes Txxx". This prevents + // mistakes where a user accidentally writes the wrong task IDs and affects + // tasks they can't see (and thus can't undo the status changes for). + + // This is just a guard rail, not a security measure. An attacker can still + // forge another user's identity trivially by forging author or committer + // emails. We also let commits with unrecognized authors act on any task to + // make behavior less confusing for new installs. + + if (!$acting_user) { + $acting_user = $actor; + } $maniphest = 'PhabricatorManiphestApplication'; if (!PhabricatorApplication::isClassInstalled($maniphest)) { @@ -321,9 +339,14 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker } $tasks = id(new ManiphestTaskQuery()) - ->setViewer($actor) + ->setViewer($acting_user) ->withIDs(array_keys($task_statuses)) ->needProjectPHIDs(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) ->execute(); foreach ($tasks as $task_id => $task) { @@ -369,4 +392,26 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker } } + private function loadActingUser(PhabricatorUser $viewer, $user_phid) { + if (!$user_phid) { + return null; + } + + $user_type = PhabricatorPeopleUserPHIDType::TYPECONST; + if (phid_get_type($user_phid) != $user_type) { + return null; + } + + $user = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($user_phid)) + ->executeOne(); + if (!$user) { + return null; + } + + return $user; + } + + } From 7274e4857c23083a3571c057f3717052fabbd1a4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 14 Apr 2017 09:16:22 -0700 Subject: [PATCH 46/52] Fix a bug where Phortune could fatal while building crumbs Summary: Ref T12451. `$this->getAccount()` may not return an account. Test Plan: - Visit `/phortune/X/`, where `X` is the ID of an account you don't have permission to view. - Before patch: fatal. - After patch: normal policy exception page. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12451 Differential Revision: https://secure.phabricator.com/D17689 --- .../account/PhortuneAccountProfileController.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php index 895b76bdc6..83effe0373 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php @@ -32,13 +32,14 @@ abstract class PhortuneAccountProfileController } protected function buildApplicationCrumbs() { - $account = $this->getAccount(); - $id = $account->getID(); - $account_uri = $this->getApplicationURI("/{$id}/"); - $crumbs = parent::buildApplicationCrumbs(); - $crumbs->addTextCrumb($account->getName(), $account_uri); $crumbs->setBorder(true); + + $account = $this->getAccount(); + if ($account) { + $crumbs->addTextCrumb($account->getName(), $account->getURI()); + } + return $crumbs; } From e1a8b5d3e904688d1168cda6f49713286a987c0e Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 14 Apr 2017 09:19:39 -0700 Subject: [PATCH 47/52] Fix a bug where Phortune accounts created via "Create Account" would not have the viewer added as a member Summary: Ref T12451. When you explicitly created a second or third account or whatever, you wouldn't be added as a member. (The editor sees that you're "already a member", so it doesn't add you.) Test Plan: - Go to `/phortune/`. - Click "Switch Accounts". - Click "Create Account". - Create an account. - Before patch: unable to view it since you don't get added as a member. - After patch: account created with you as member. - Also created an accont with multiple members. - Tried to create an account with no members. - Tried to create an account with just someone else as a member. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12451 Differential Revision: https://secure.phabricator.com/D17690 --- src/applications/phortune/storage/PhortuneAccount.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/applications/phortune/storage/PhortuneAccount.php b/src/applications/phortune/storage/PhortuneAccount.php index 0560104972..ff3b0d8a84 100644 --- a/src/applications/phortune/storage/PhortuneAccount.php +++ b/src/applications/phortune/storage/PhortuneAccount.php @@ -16,10 +16,8 @@ final class PhortuneAccount extends PhortuneDAO private $memberPHIDs = self::ATTACHABLE; public static function initializeNewAccount(PhabricatorUser $actor) { - $account = id(new PhortuneAccount()); - $account->memberPHIDs = array($actor->getPHID() => $actor->getPHID()); - - return $account; + return id(new self()) + ->attachMemberPHIDs(array()); } public static function createNewAccount( From 71d933d496cad8e597a104931c85b1e7776aba86 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 14 Apr 2017 09:48:58 -0700 Subject: [PATCH 48/52] Add a failing test case for new Phortune account initialization Summary: Ref T12451. Ref T12484. I think D17657 fixed this, but caused the bug in D17690. The fix for that causes this bug again. Put a unit test on it. This test currently fails; I'll correct the bug in the next change. Test Plan: Ran `arc unit`, saw a failure. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12484, T12451 Differential Revision: https://secure.phabricator.com/D17691 --- src/__phutil_library_map__.php | 2 ++ .../__tests__/PhabricatorPhortuneTestCase.php | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/applications/phortune/__tests__/PhabricatorPhortuneTestCase.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d9fca984f1..5c1f1ced73 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3428,6 +3428,7 @@ phutil_register_library_map(array( 'PhabricatorPhortuneContentSource' => 'applications/phortune/contentsource/PhabricatorPhortuneContentSource.php', 'PhabricatorPhortuneManagementInvoiceWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php', 'PhabricatorPhortuneManagementWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementWorkflow.php', + 'PhabricatorPhortuneTestCase' => 'applications/phortune/__tests__/PhabricatorPhortuneTestCase.php', 'PhabricatorPhragmentApplication' => 'applications/phragment/application/PhabricatorPhragmentApplication.php', 'PhabricatorPhrequentApplication' => 'applications/phrequent/application/PhabricatorPhrequentApplication.php', 'PhabricatorPhrictionApplication' => 'applications/phriction/application/PhabricatorPhrictionApplication.php', @@ -8679,6 +8680,7 @@ phutil_register_library_map(array( 'PhabricatorPhortuneContentSource' => 'PhabricatorContentSource', 'PhabricatorPhortuneManagementInvoiceWorkflow' => 'PhabricatorPhortuneManagementWorkflow', 'PhabricatorPhortuneManagementWorkflow' => 'PhabricatorManagementWorkflow', + 'PhabricatorPhortuneTestCase' => 'PhabricatorTestCase', 'PhabricatorPhragmentApplication' => 'PhabricatorApplication', 'PhabricatorPhrequentApplication' => 'PhabricatorApplication', 'PhabricatorPhrictionApplication' => 'PhabricatorApplication', diff --git a/src/applications/phortune/__tests__/PhabricatorPhortuneTestCase.php b/src/applications/phortune/__tests__/PhabricatorPhortuneTestCase.php new file mode 100644 index 0000000000..b4d6a0e89d --- /dev/null +++ b/src/applications/phortune/__tests__/PhabricatorPhortuneTestCase.php @@ -0,0 +1,26 @@ + true, + ); + } + + public function testNewPhortuneAccount() { + $user = $this->generateNewTestUser(); + $content_source = $this->newContentSource(); + + $accounts = PhortuneAccountQuery::loadAccountsForUser( + $user, + $content_source); + + $this->assertEqual( + 1, + count($accounts), + pht('Creation of default account for users with no accounts.')); + } + +} From 505b1d8379a5810b42135294ac89cfdebf6386ac Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 14 Apr 2017 09:54:01 -0700 Subject: [PATCH 49/52] Fix member edit transaction validation so it works for both implicit and explicit account creation Summary: Ref T12451. Ref T12484. This should deal with all the `+` / `-` / `=` cases correctly, I think. Also makes sure that members are real users, not commits or tokens or whatever. And expands the creation test case to make some other basic sanity checks. Test Plan: - Went through implicit first-time creation flow. - Went through explicit second-time creation flow. - Unit test now passes. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12484, T12451 Differential Revision: https://secure.phabricator.com/D17692 --- .../__tests__/PhabricatorPhortuneTestCase.php | 17 ++++++ .../phortune/editor/PhortuneAccountEditor.php | 53 ++++++++++--------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/applications/phortune/__tests__/PhabricatorPhortuneTestCase.php b/src/applications/phortune/__tests__/PhabricatorPhortuneTestCase.php index b4d6a0e89d..7be37483be 100644 --- a/src/applications/phortune/__tests__/PhabricatorPhortuneTestCase.php +++ b/src/applications/phortune/__tests__/PhabricatorPhortuneTestCase.php @@ -21,6 +21,23 @@ final class PhabricatorPhortuneTestCase 1, count($accounts), pht('Creation of default account for users with no accounts.')); + + // Reload the account. The user should be able to view and edit it, and + // should be a member. + + $account = head($accounts); + $account = id(new PhortuneAccountQuery()) + ->setViewer($user) + ->withPHIDs(array($account->getPHID())) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + $this->assertEqual(true, ($account instanceof PhortuneAccount)); + $this->assertEqual(array($user->getPHID()), $account->getMemberPHIDs()); } } diff --git a/src/applications/phortune/editor/PhortuneAccountEditor.php b/src/applications/phortune/editor/PhortuneAccountEditor.php index 8672c62417..50a71c476f 100644 --- a/src/applications/phortune/editor/PhortuneAccountEditor.php +++ b/src/applications/phortune/editor/PhortuneAccountEditor.php @@ -28,47 +28,48 @@ final class PhortuneAccountEditor $errors = parent::validateTransaction($object, $type, $xactions); + $viewer = $this->requireActor(); + switch ($type) { case PhabricatorTransactions::TYPE_EDGE: foreach ($xactions as $xaction) { switch ($xaction->getMetadataValue('edge:type')) { case PhortuneAccountHasMemberEdgeType::EDGECONST: - $actor_phid = $this->requireActor()->getPHID(); - $new = $xaction->getNewValue(); $old = $object->getMemberPHIDs(); + $new = $this->getPHIDTransactionNewValue($xaction, $old); - // Check if user is trying to not set themselves on creation - if (!$old) { - $set = idx($new, '+', array()); - $actor_set = false; - foreach ($set as $phid) { - if ($actor_phid == $phid) { - $actor_set = true; - } + $old = array_fuse($old); + $new = array_fuse($new); + + foreach ($new as $new_phid) { + if (isset($old[$new_phid])) { + continue; } - if (!$actor_set) { + + $user = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($new_phid)) + ->executeOne(); + if (!$user) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), - pht('You can not remove yourself as an account manager.'), - $xaction); + pht( + 'Account managers must be valid users, "%s" is not.', + $new_phid)); $errors[] = $error; - + continue; } } - // Check if user is trying to remove themselves on edit - $set = idx($new, '-', array()); - foreach ($set as $phid) { - if ($actor_phid == $phid) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('You can not remove yourself as an account manager.'), - $xaction); - $errors[] = $error; - - } + $actor_phid = $this->getActingAsPHID(); + if (!isset($new[$actor_phid])) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('You can not remove yourself as an account manager.'), + $xaction); + $errors[] = $error; } break; } From 7fbb5f2d92108fd6beaef3bc099247943affb693 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 14 Apr 2017 09:59:43 -0700 Subject: [PATCH 50/52] Reduce some code duplication in PhortuneLandingController Summary: Ref T12451. This code is the same as the other code. Test Plan: Went through the default-account case with this code, worked the same as the other code. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12451 Differential Revision: https://secure.phabricator.com/D17693 --- .../controller/PhortuneLandingController.php | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/applications/phortune/controller/PhortuneLandingController.php b/src/applications/phortune/controller/PhortuneLandingController.php index 2a019c5df9..e6906095d2 100644 --- a/src/applications/phortune/controller/PhortuneLandingController.php +++ b/src/applications/phortune/controller/PhortuneLandingController.php @@ -5,17 +5,9 @@ final class PhortuneLandingController extends PhortuneController { public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $accounts = id(new PhortuneAccountQuery()) - ->setViewer($viewer) - ->withMemberPHIDs(array($viewer->getPHID())) - ->execute(); - - if (!$accounts) { - $account = PhortuneAccount::createNewAccount( - $viewer, - PhabricatorContentSource::newFromRequest($request)); - $accounts = array($account); - } + $accounts = PhortuneAccountQuery::loadAccountsForUser( + $viewer, + PhabricatorContentSource::newFromRequest($request)); if (count($accounts) == 1) { $account = head($accounts); From aec19d2acf32355cd7446e025b227b4ebea17452 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 14 Apr 2017 10:14:21 -0700 Subject: [PATCH 51/52] Reduce code duplication in Phortune account controllers Summary: Ref T12451. This is a GREAT comment (A++) but we only need one copy of it. This uses a pattern similar to Projects, which is a little weird but works well enough. Test Plan: - Viewed all four tabs of an account. - Viewed a page with a bad account ID which 404'd properly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12451 Differential Revision: https://secure.phabricator.com/D17694 --- src/__phutil_library_map__.php | 4 +- .../PhortuneAccountBillingController.php | 25 ++------ .../account/PhortuneAccountController.php | 64 +++++++++++++++++++ .../PhortuneAccountManagerController.php | 26 ++------ .../PhortuneAccountProfileController.php | 19 +----- .../PhortuneAccountSubscriptionController.php | 25 ++------ .../account/PhortuneAccountViewController.php | 28 ++------ 7 files changed, 87 insertions(+), 104 deletions(-) create mode 100644 src/applications/phortune/controller/account/PhortuneAccountController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5c1f1ced73..44a3ca7ad3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4338,6 +4338,7 @@ phutil_register_library_map(array( 'PhortuneAccountAddManagerController' => 'applications/phortune/controller/account/PhortuneAccountAddManagerController.php', 'PhortuneAccountBillingController' => 'applications/phortune/controller/account/PhortuneAccountBillingController.php', 'PhortuneAccountChargeListController' => 'applications/phortune/controller/account/PhortuneAccountChargeListController.php', + 'PhortuneAccountController' => 'applications/phortune/controller/account/PhortuneAccountController.php', 'PhortuneAccountEditController' => 'applications/phortune/controller/account/PhortuneAccountEditController.php', 'PhortuneAccountEditEngine' => 'applications/phortune/editor/PhortuneAccountEditEngine.php', 'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php', @@ -9815,6 +9816,7 @@ phutil_register_library_map(array( 'PhortuneAccountAddManagerController' => 'PhortuneController', 'PhortuneAccountBillingController' => 'PhortuneAccountProfileController', 'PhortuneAccountChargeListController' => 'PhortuneController', + 'PhortuneAccountController' => 'PhortuneController', 'PhortuneAccountEditController' => 'PhortuneController', 'PhortuneAccountEditEngine' => 'PhabricatorEditEngine', 'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor', @@ -9823,7 +9825,7 @@ phutil_register_library_map(array( 'PhortuneAccountManagerController' => 'PhortuneAccountProfileController', 'PhortuneAccountNameTransaction' => 'PhortuneAccountTransactionType', 'PhortuneAccountPHIDType' => 'PhabricatorPHIDType', - 'PhortuneAccountProfileController' => 'PhortuneController', + 'PhortuneAccountProfileController' => 'PhortuneAccountController', 'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneAccountSubscriptionController' => 'PhortuneAccountProfileController', 'PhortuneAccountTransaction' => 'PhabricatorModularTransaction', diff --git a/src/applications/phortune/controller/account/PhortuneAccountBillingController.php b/src/applications/phortune/controller/account/PhortuneAccountBillingController.php index 2d662668a4..0660358af7 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountBillingController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountBillingController.php @@ -4,29 +4,12 @@ final class PhortuneAccountBillingController extends PhortuneAccountProfileController { public function handleRequest(AphrontRequest $request) { - $viewer = $this->getViewer(); - - // TODO: Currently, you must be able to edit an account to view the detail - // page, because the account must be broadly visible so merchants can - // process orders but merchants should not be able to see all the details - // of an account. Ideally this page should be visible to merchants, too, - // just with less information. - $can_edit = true; - - $account = id(new PhortuneAccountQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getURIData('id'))) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$account) { - return new Aphront404Response(); + $response = $this->loadAccount(); + if ($response) { + return $response; } - $this->setAccount($account); + $account = $this->getAccount(); $title = $account->getName(); $crumbs = $this->buildApplicationCrumbs(); diff --git a/src/applications/phortune/controller/account/PhortuneAccountController.php b/src/applications/phortune/controller/account/PhortuneAccountController.php new file mode 100644 index 0000000000..2ba3b393a2 --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountController.php @@ -0,0 +1,64 @@ +account; + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $account = $this->getAccount(); + if ($account) { + $crumbs->addTextCrumb($account->getName(), $account->getURI()); + } + + return $crumbs; + } + + protected function loadAccount() { + // TODO: Currently, you must be able to edit an account to view the detail + // page, because the account must be broadly visible so merchants can + // process orders but merchants should not be able to see all the details + // of an account. Ideally the profile pages should be visible to merchants, + // too, just with less information. + return $this->loadAccountForEdit(); + } + + + protected function loadAccountForEdit() { + $viewer = $this->getViewer(); + $request = $this->getRequest(); + + $account_id = $request->getURIData('accountID'); + if (!$account_id) { + $account_id = $request->getURIData('id'); + } + + if (!$account_id) { + return new Aphront404Response(); + } + + $account = id(new PhortuneAccountQuery()) + ->setViewer($viewer) + ->withIDs(array($account_id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$account) { + return new Aphront404Response(); + } + + $this->account = $account; + + return null; + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountManagerController.php b/src/applications/phortune/controller/account/PhortuneAccountManagerController.php index b5f8a38f64..502fbfe52e 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountManagerController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountManagerController.php @@ -4,29 +4,12 @@ final class PhortuneAccountManagerController extends PhortuneAccountProfileController { public function handleRequest(AphrontRequest $request) { - $viewer = $this->getViewer(); - - // TODO: Currently, you must be able to edit an account to view the detail - // page, because the account must be broadly visible so merchants can - // process orders but merchants should not be able to see all the details - // of an account. Ideally this page should be visible to merchants, too, - // just with less information. - $can_edit = true; - - $account = id(new PhortuneAccountQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getURIData('id'))) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$account) { - return new Aphront404Response(); + $response = $this->loadAccount(); + if ($response) { + return $response; } - $this->setAccount($account); + $account = $this->getAccount(); $title = $account->getName(); $crumbs = $this->buildApplicationCrumbs(); @@ -66,6 +49,7 @@ final class PhortuneAccountManagerController ->setText(pht('New Manager')) ->setIcon('fa-plus') ->setWorkflow(true) + ->setDisabled(!$can_edit) ->setHref("/phortune/account/manager/add/{$id}/"); $header = id(new PHUIHeaderView()) diff --git a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php index 83effe0373..2a5448d5cc 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php @@ -1,18 +1,7 @@ account = $account; - return $this; - } - - public function getAccount() { - return $this->account; - } + extends PhortuneAccountController { public function buildApplicationMenu() { return $this->buildSideNavView()->getMenu(); @@ -34,12 +23,6 @@ abstract class PhortuneAccountProfileController protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $crumbs->setBorder(true); - - $account = $this->getAccount(); - if ($account) { - $crumbs->addTextCrumb($account->getName(), $account->getURI()); - } - return $crumbs; } diff --git a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php index 145c5eb5db..418507e9c2 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php @@ -4,29 +4,12 @@ final class PhortuneAccountSubscriptionController extends PhortuneAccountProfileController { public function handleRequest(AphrontRequest $request) { - $viewer = $this->getViewer(); - - // TODO: Currently, you must be able to edit an account to view the detail - // page, because the account must be broadly visible so merchants can - // process orders but merchants should not be able to see all the details - // of an account. Ideally this page should be visible to merchants, too, - // just with less information. - $can_edit = true; - - $account = id(new PhortuneAccountQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getURIData('id'))) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$account) { - return new Aphront404Response(); + $response = $this->loadAccount(); + if ($response) { + return $response; } - $this->setAccount($account); + $account = $this->getAccount(); $title = $account->getName(); $crumbs = $this->buildApplicationCrumbs(); diff --git a/src/applications/phortune/controller/account/PhortuneAccountViewController.php b/src/applications/phortune/controller/account/PhortuneAccountViewController.php index efb94551c3..6537577921 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountViewController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountViewController.php @@ -4,32 +4,16 @@ final class PhortuneAccountViewController extends PhortuneAccountProfileController { public function handleRequest(AphrontRequest $request) { - $viewer = $this->getViewer(); - $id = $request->getURIData('accountID'); - - // TODO: Currently, you must be able to edit an account to view the detail - // page, because the account must be broadly visible so merchants can - // process orders but merchants should not be able to see all the details - // of an account. Ideally this page should be visible to merchants, too, - // just with less information. - $can_edit = true; - - $account = id(new PhortuneAccountQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$account) { - return new Aphront404Response(); + $response = $this->loadAccount(); + if ($response) { + return $response; } - $this->setAccount($account); + $account = $this->getAccount(); $title = $account->getName(); + $viewer = $this->getViewer(); + $invoices = id(new PhortuneCartQuery()) ->setViewer($viewer) ->withAccountPHIDs(array($account->getPHID())) From f0fbf7a7d32e1430880bf4ef5b28c84a0a95f1cc Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 14 Apr 2017 12:14:04 -0700 Subject: [PATCH 52/52] Sort "closed" results (like disabled users) to the bottom of typeaheads, but don't hide them completely Summary: Fixes T12538. Instead of hiding "closed" results unless only closed results match, show closed results but sort them to the bottom. This fixes the actual issue in T12538, and I think this is probably the correct/best behavior for the global search. It also makes all other typeaheads use this behavior. They currently have a "bug" where enabled user `abcd` makes it impossible to select disabled user `abc`. This manifests in some real cases, where enabled function `members(abc)` makes it impossible to disabled user `abc` in some function tokenizers. If ths feels worse, we could go back to filtering in the simpler cases and introduce a rule like "show closed results if only closed results would be shown OR if query is an exact match for the disabled result", but that gets dicier because "exact match" is a fuzzy concept. (There are a lot of other minor bad behaviors that this doesn't try to fix.) Test Plan: Enabled project "instabug" no longer prevents bot user "instabug" from being shown: {F4903843} Disabled user "mmaven" is sorted below enabled user "mmclewis", in defiance of the otherwise alphabetical order. There's no visual cue that this user is disabled because of T6906. {F4903845} Same as above, but this source renders "disabled" in a more obvious way: {F4903848} Function selecting members of active project `members(instabug)` no longer prevents selection of bot user `instabug`: {F4903849} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12538 Differential Revision: https://secure.phabricator.com/D17695 --- resources/celerity/map.php | 72 +++++++------- webroot/rsrc/js/core/Prefab.js | 32 ------- .../rsrc/js/core/behavior-search-typeahead.js | 95 ++++++++----------- webroot/rsrc/js/phuix/PHUIXAutocomplete.js | 1 - 4 files changed, 76 insertions(+), 124 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 33e3549d2b..d9eb3a42e0 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => '437d3b5a', 'conpherence.pkg.js' => '281b1a73', 'core.pkg.css' => 'b2ad82f4', - 'core.pkg.js' => 'fbc1c380', + 'core.pkg.js' => 'bf3b5cdf', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', @@ -472,7 +472,7 @@ return array( 'rsrc/js/core/KeyboardShortcutManager.js' => '4a021c10', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 'rsrc/js/core/Notification.js' => 'ccf1cbf8', - 'rsrc/js/core/Prefab.js' => '8d40ae75', + 'rsrc/js/core/Prefab.js' => 'c5af80a2', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '320810c8', 'rsrc/js/core/Title.js' => '485aaa6c', @@ -510,7 +510,7 @@ return array( 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', - 'rsrc/js/core/behavior-search-typeahead.js' => '06c32383', + 'rsrc/js/core/behavior-search-typeahead.js' => '0f2a0820', 'rsrc/js/core/behavior-select-content.js' => 'bf5374ef', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 'rsrc/js/core/behavior-setup-check-https.js' => '491416b3', @@ -528,7 +528,7 @@ return array( 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b', - 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'd5b2abf3', + 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'd713a2c5', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', @@ -666,7 +666,7 @@ return array( 'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-remarkup-assist' => '0ca788bd', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', - 'javelin-behavior-phabricator-search-typeahead' => '06c32383', + 'javelin-behavior-phabricator-search-typeahead' => '0f2a0820', 'javelin-behavior-phabricator-show-older-transactions' => '94c65b72', 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', @@ -792,7 +792,7 @@ return array( 'phabricator-notification-menu-css' => '6a697e43', 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', - 'phabricator-prefab' => '8d40ae75', + 'phabricator-prefab' => 'c5af80a2', 'phabricator-remarkup-css' => '17c0fb37', 'phabricator-search-results-css' => 'f87d23ad', 'phabricator-shaped-request' => '7cbe244b', @@ -885,7 +885,7 @@ return array( 'phui-workpanel-view-css' => 'a3a63478', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => 'b3465b9b', - 'phuix-autocomplete' => 'd5b2abf3', + 'phuix-autocomplete' => 'd713a2c5', 'phuix-dropdown-menu' => '8018ee50', 'phuix-form-control-view' => '83e03671', 'phuix-icon-view' => 'bff6884b', @@ -943,17 +943,6 @@ return array( 'javelin-stratcom', 'javelin-workflow', ), - '06c32383' => array( - 'javelin-behavior', - 'javelin-typeahead-ondemand-source', - 'javelin-typeahead', - 'javelin-dom', - 'javelin-uri', - 'javelin-util', - 'javelin-stratcom', - 'phabricator-prefab', - 'phuix-icon-view', - ), '0825c27a' => array( 'javelin-behavior', 'javelin-dom', @@ -999,6 +988,17 @@ return array( 'phuix-autocomplete', 'javelin-mask', ), + '0f2a0820' => array( + 'javelin-behavior', + 'javelin-typeahead-ondemand-source', + 'javelin-typeahead', + 'javelin-dom', + 'javelin-uri', + 'javelin-util', + 'javelin-stratcom', + 'phabricator-prefab', + 'phuix-icon-view', + ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -1588,18 +1588,6 @@ return array( 'javelin-stratcom', 'javelin-install', ), - '8d40ae75' => array( - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-typeahead', - 'javelin-tokenizer', - 'javelin-typeahead-preloaded-source', - 'javelin-typeahead-ondemand-source', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - ), '8fadb715' => array( 'javelin-install', 'javelin-util', @@ -1955,6 +1943,18 @@ return array( 'c587b80f' => array( 'javelin-install', ), + 'c5af80a2' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-typeahead', + 'javelin-tokenizer', + 'javelin-typeahead-preloaded-source', + 'javelin-typeahead-ondemand-source', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + ), 'c7ccd872' => array( 'phui-fontkit-css', ), @@ -2055,12 +2055,6 @@ return array( 'javelin-uri', 'phabricator-notification', ), - 'd5b2abf3' => array( - 'javelin-install', - 'javelin-dom', - 'phuix-icon-view', - 'phabricator-prefab', - ), 'd6a7e717' => array( 'multirow-row-manager', 'javelin-install', @@ -2070,6 +2064,12 @@ return array( 'javelin-json', 'phabricator-prefab', ), + 'd713a2c5' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-icon-view', + 'phabricator-prefab', + ), 'd7a74243' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/js/core/Prefab.js b/webroot/rsrc/js/core/Prefab.js index 2c3da10102..c5a8ef5e9d 100644 --- a/webroot/rsrc/js/core/Prefab.js +++ b/webroot/rsrc/js/core/Prefab.js @@ -101,7 +101,6 @@ JX.install('Prefab', { datasource.setSortHandler( JX.bind(datasource, JX.Prefab.sortHandler, config)); - datasource.setFilterHandler(JX.Prefab.filterClosedResults); datasource.setTransformer(JX.Prefab.transformDatasourceResults); var typeahead = new JX.Typeahead( @@ -256,37 +255,6 @@ JX.install('Prefab', { }, - /** - * Filter callback for tokenizers and typeaheads which filters out closed - * or disabled objects unless they are the only options. - */ - filterClosedResults: function(value, list) { - // Look for any open result. - var has_open = false; - var ii; - for (ii = 0; ii < list.length; ii++) { - if (!list[ii].closed) { - has_open = true; - break; - } - } - - if (!has_open) { - // Everything is closed, so just use it as-is. - return list; - } - - // Otherwise, only display the open results. - var results = []; - for (ii = 0; ii < list.length; ii++) { - if (!list[ii].closed) { - results.push(list[ii]); - } - } - - return results; - }, - /** * Transform results from a wire format into a usable format in a standard * way. diff --git a/webroot/rsrc/js/core/behavior-search-typeahead.js b/webroot/rsrc/js/core/behavior-search-typeahead.js index a981f3861e..864d45ff29 100644 --- a/webroot/rsrc/js/core/behavior-search-typeahead.js +++ b/webroot/rsrc/js/core/behavior-search-typeahead.js @@ -52,72 +52,55 @@ JX.behavior('phabricator-search-typeahead', function(config) { datasource.setTransformer(transform); - // Sort handler that orders results by type (e.g., applications, users) - // and then selects for good matches on the "priority" substrings if they - // exist (for instance, username matches are preferred over real name - // matches, and application name matches are preferred over application - // flavor text matches). - var sort_handler = function(value, list, cmp) { - var priority_hits = {}; - var type_priority = { - 'jump' : 1, - 'apps' : 2, - 'proj' : 3, - 'user' : 4, - 'repo' : 5, - 'symb' : 6 - }; - - var tokens = this.tokenize(value); + // First, sort all the results normally. + JX.bind(this, JX.Prefab.sortHandler, {}, value, list, cmp)(); + // Now we're going to apply some special rules to order results by type, + // so applications always appear near the top, then users, etc. var ii; + + var type_order = [ + 'jump', + 'apps', + 'proj', + 'user', + 'repo', + 'symb', + 'misc' + ]; + + var type_map = {}; + for (ii = 0; ii < type_order.length; ii++) { + type_map[type_order[ii]] = true; + } + + var buckets = {}; for (ii = 0; ii < list.length; ii++) { var item = list[ii]; - for (var jj = 0; jj < tokens.length; jj++) { - if (item.name.indexOf(tokens[jj]) === 0) { - priority_hits[item.id] = true; - } + var type = item.priorityType; + if (!type_map.hasOwnProperty(type)) { + type = 'misc'; } - if (!item.priority) { - continue; + if (!buckets.hasOwnProperty(type)) { + buckets[type] = []; } - for (var hh = 0; hh < tokens.length; hh++) { - if (item.priority.substr(0, tokens[hh].length) == tokens[hh]) { - priority_hits[item.id] = true; - } - } + buckets[type].push(item); } - list.sort(function(u, v) { - var u_type = type_priority[u.priorityType] || 999; - var v_type = type_priority[v.priorityType] || 999; - - if (u_type != v_type) { - return u_type - v_type; - } - - if (priority_hits[u.id] != priority_hits[v.id]) { - return priority_hits[v.id] ? 1 : -1; - } - - return cmp(u, v); - }); - // If we have more results than fit, limit each type of result to 3, so // we show 3 applications, then 3 users, etc. For jump items, we show only // one result. - var type_count = 0; - var current_type = null; - for (ii = 0; ii < list.length; ii++) { - if (list[ii].type != current_type) { - current_type = list[ii].type; - type_count = 1; - } else { - type_count++; + + var jj; + var results = []; + for (ii = 0; ii < type_order.length; ii++) { + var current_type = type_order[ii]; + var type_list = buckets[current_type] || []; + for (jj = 0; jj < type_list.length; jj++) { // Skip this item if: // - it's a jump nav item, and we already have at least one jump @@ -125,19 +108,21 @@ JX.behavior('phabricator-search-typeahead', function(config) { // - we have more items than will fit in the typeahead, and this // is the 4..Nth result of its type. - var skip = ((current_type == 'jump') && (type_count > 1)) || + var skip = ((current_type == 'jump') && (jj > 1)) || ((list.length > config.limit) && (type_count > 3)); if (skip) { - list.splice(ii, 1); - ii--; + continue; } + + results.push(type_list[jj]); } } + // Replace the list in place with the results. + list.splice.apply(list, [0, list.length].concat(results)); }; datasource.setSortHandler(JX.bind(datasource, sort_handler)); - datasource.setFilterHandler(JX.Prefab.filterClosedResults); datasource.setMaximumResultCount(config.limit); var typeahead = new JX.Typeahead(JX.$(config.id), JX.$(config.input)); diff --git a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js index a03c2adf70..16ed8b75f3 100644 --- a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js +++ b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js @@ -153,7 +153,6 @@ JX.install('PHUIXAutocomplete', { datasource.setTransformer(JX.bind(this, this._transformresult)); datasource.setSortHandler( JX.bind(datasource, JX.Prefab.sortHandler, {})); - datasource.setFilterHandler(JX.Prefab.filterClosedResults); this._datasources[code] = datasource; }