From ea554af476493bd27017017c230b8e8a76b8dd4d Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Sat, 11 May 2024 13:22:24 +0300 Subject: [PATCH 001/118] Add (Advanced) Custom Fields to Item List Summary: Allow PHP-coded Custom Fields to show things in Lists. Also add Repository to Revision List and Flags to Maniphest lists. Closes T15133. Ref T15512, T15750 Test Plan: Look at Repository List and Task lists that have flags. Reviewers: O1 Blessed Committers, aklapper, valerio.bozzolan Reviewed By: O1 Blessed Committers, aklapper, valerio.bozzolan Subscribers: aklapper, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15750, T15512, T15133 Differential Revision: https://we.phorge.it/D25548 --- src/__phutil_library_map__.php | 6 +++ .../DifferentialRepositoryField.php | 12 +++++ .../DifferentialRevisionSearchEngine.php | 5 ++ .../view/DifferentialRevisionListView.php | 12 +++++ .../PhorgeFlagFlaggedObjectCustomField.php | 46 +++++++++++++++++++ .../PhorgeFlagFlaggedObjectFieldStorage.php | 36 +++++++++++++++ .../field/ManiphestFlagCustomField.php | 18 ++++++++ .../query/ManiphestTaskSearchEngine.php | 24 ++++++++++ .../maniphest/view/ManiphestTaskListView.php | 15 ++++++ .../view/ManiphestTaskResultListView.php | 18 ++++++-- .../PhabricatorApplicationSearchEngine.php | 32 ++++++++++++- .../field/PhabricatorCustomField.php | 13 +++++- .../field/PhabricatorCustomFieldList.php | 13 ++++++ 13 files changed, 244 insertions(+), 6 deletions(-) create mode 100644 src/applications/flag/customfield/PhorgeFlagFlaggedObjectCustomField.php create mode 100644 src/applications/flag/customfield/PhorgeFlagFlaggedObjectFieldStorage.php create mode 100644 src/applications/maniphest/field/ManiphestFlagCustomField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 17b1e837a6..b21f29d1b4 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1802,6 +1802,7 @@ phutil_register_library_map(array( 'ManiphestEditConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestEditConduitAPIMethod.php', 'ManiphestEditEngine' => 'applications/maniphest/editor/ManiphestEditEngine.php', 'ManiphestEmailCommand' => 'applications/maniphest/command/ManiphestEmailCommand.php', + 'ManiphestFlagCustomField' => 'applications/maniphest/field/ManiphestFlagCustomField.php', 'ManiphestGetTaskTransactionsConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php', 'ManiphestHovercardEngineExtension' => 'applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php', 'ManiphestInfoConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php', @@ -5388,6 +5389,8 @@ phutil_register_library_map(array( 'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php', 'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php', 'PhorgeCodeWarningSetupCheck' => 'applications/config/check/PhorgeCodeWarningSetupCheck.php', + 'PhorgeFlagFlaggedObjectCustomField' => 'applications/flag/customfield/PhorgeFlagFlaggedObjectCustomField.php', + 'PhorgeFlagFlaggedObjectFieldStorage' => 'applications/flag/customfield/PhorgeFlagFlaggedObjectFieldStorage.php', 'PhorgeSystemDeprecationWarningListener' => 'applications/system/events/PhorgeSystemDeprecationWarningListener.php', 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', 'PhortuneAccountAddManagerController' => 'applications/phortune/controller/account/PhortuneAccountAddManagerController.php', @@ -8008,6 +8011,7 @@ phutil_register_library_map(array( 'ManiphestEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'ManiphestEditEngine' => 'PhabricatorEditEngine', 'ManiphestEmailCommand' => 'MetaMTAEmailTransactionCommand', + 'ManiphestFlagCustomField' => 'ManiphestCustomField', 'ManiphestGetTaskTransactionsConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'ManiphestInfoConduitAPIMethod' => 'ManiphestConduitAPIMethod', @@ -12212,6 +12216,8 @@ phutil_register_library_map(array( 'PholioTransactionView' => 'PhabricatorApplicationTransactionView', 'PholioUploadedImageView' => 'AphrontView', 'PhorgeCodeWarningSetupCheck' => 'PhabricatorSetupCheck', + 'PhorgeFlagFlaggedObjectCustomField' => 'PhabricatorCustomField', + 'PhorgeFlagFlaggedObjectFieldStorage' => 'Phobject', 'PhorgeSystemDeprecationWarningListener' => 'PhabricatorEventListener', 'PhortuneAccount' => array( 'PhortuneDAO', diff --git a/src/applications/differential/customfield/DifferentialRepositoryField.php b/src/applications/differential/customfield/DifferentialRepositoryField.php index 1d403d160a..9cdc29fcb3 100644 --- a/src/applications/differential/customfield/DifferentialRepositoryField.php +++ b/src/applications/differential/customfield/DifferentialRepositoryField.php @@ -63,4 +63,16 @@ final class DifferentialRepositoryField $repository->getMonogram().' '.$repository->getName()); } + public function shouldAppearInListView() { + return true; + } + + public function renderOnListItem(PHUIObjectItemView $view) { + if ($this->getValue()) { + $handle = $this->getViewer()->renderHandle($this->getValue()); + $view->addByLine(pht('Repository: %s', $handle)); + } + } + + } diff --git a/src/applications/differential/query/DifferentialRevisionSearchEngine.php b/src/applications/differential/query/DifferentialRevisionSearchEngine.php index b69b764bb8..9e18ba8e24 100644 --- a/src/applications/differential/query/DifferentialRevisionSearchEngine.php +++ b/src/applications/differential/query/DifferentialRevisionSearchEngine.php @@ -199,6 +199,10 @@ final class DifferentialRevisionSearchEngine $unlanded = $this->loadUnlandedDependencies($revisions); + $custom_field_lists = $this->loadCustomFields( + $revisions, + PhabricatorCustomField::ROLE_LIST); + $views = array(); if ($bucket) { $bucket->setViewer($viewer); @@ -231,6 +235,7 @@ final class DifferentialRevisionSearchEngine foreach ($views as $view) { $view->setUnlandedDependencies($unlanded); + $view->setCustomFieldLists($custom_field_lists); } if (count($views) == 1) { diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php index ab0abcc386..018f640c6c 100644 --- a/src/applications/differential/view/DifferentialRevisionListView.php +++ b/src/applications/differential/view/DifferentialRevisionListView.php @@ -11,6 +11,7 @@ final class DifferentialRevisionListView extends AphrontView { private $noBox; private $background = null; private $unlandedDependencies = array(); + private $customFieldLists = array(); public function setUnlandedDependencies(array $unlanded_dependencies) { $this->unlandedDependencies = $unlanded_dependencies; @@ -47,6 +48,11 @@ final class DifferentialRevisionListView extends AphrontView { return $this; } + public function setCustomFieldLists(array $lists) { + $this->customFieldLists = $lists; + return $this; + } + public function render() { $viewer = $this->getViewer(); @@ -181,6 +187,12 @@ final class DifferentialRevisionListView extends AphrontView { "{$icon} {$color}", $revision->getStatusDisplayName()); + $field_list = idx($this->customFieldLists, $revision->getPHID()); + if ($field_list) { + $field_list + ->addFieldsToListViewItem($revision, $viewer, $item); + } + $list->addItem($item); } diff --git a/src/applications/flag/customfield/PhorgeFlagFlaggedObjectCustomField.php b/src/applications/flag/customfield/PhorgeFlagFlaggedObjectCustomField.php new file mode 100644 index 0000000000..8d2f358efd --- /dev/null +++ b/src/applications/flag/customfield/PhorgeFlagFlaggedObjectCustomField.php @@ -0,0 +1,46 @@ +flag) { + return; + } + // I'm very open to improvements in the way a Flag is displayed + $icon = PhabricatorFlagColor::getIcon($this->flag->getColor()); + $view->addIcon($icon); + } + + + public function shouldUseStorage() { + return true; + } + + public function setValueFromStorage($value) { + $this->flag = $value; + } + + // The parent function is defined to return a PhabricatorCustomFieldStorage, + // but that assumes a DTO with a particular form; That doesn't apply here. + // Maybe the function needs to be re-defined with a suitable interface. + // For now, PhorgeFlagFlaggedObjectFieldStorage just duck-types into the + // right shape. + public function newStorageObject() { + return id(new PhorgeFlagFlaggedObjectFieldStorage()) + ->setViewer($this->getViewer()); + } + +} diff --git a/src/applications/flag/customfield/PhorgeFlagFlaggedObjectFieldStorage.php b/src/applications/flag/customfield/PhorgeFlagFlaggedObjectFieldStorage.php new file mode 100644 index 0000000000..b8f28a2fac --- /dev/null +++ b/src/applications/flag/customfield/PhorgeFlagFlaggedObjectFieldStorage.php @@ -0,0 +1,36 @@ +viewer = $viewer; + return $this; + } + + public function getStorageSourceKey() { + return 'flags/flag'; + } + + public function loadStorageSourceData(array $fields) { + + $objects = mpull($fields, 'getObject'); + $object_phids = mpull($objects, 'getPHID'); + $flags = (new PhabricatorFlagQuery()) + ->setViewer($this->viewer) + ->withOwnerPHIDs(array($this->viewer->getPHID())) + ->withObjectPHIDs($object_phids) + ->execute(); + $flags = mpull($flags, null, 'getObjectPHID'); + + $result = array(); + foreach ($fields as $key => $field) { + $target_phid = $field->getObject()->getPHID(); + $result[$key] = idx($flags, $target_phid); + } + + return $result; + } + +} diff --git a/src/applications/maniphest/field/ManiphestFlagCustomField.php b/src/applications/maniphest/field/ManiphestFlagCustomField.php new file mode 100644 index 0000000000..febf0aea8d --- /dev/null +++ b/src/applications/maniphest/field/ManiphestFlagCustomField.php @@ -0,0 +1,18 @@ +setProxy(new PhorgeFlagFlaggedObjectCustomField()); + } + + public function canSetProxy() { + return true; + } + + public function newStorageObject() { + return $this->getProxy()->newStorageObject(); + } +} diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php index 4f3e9c0462..541f7f3831 100644 --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -374,11 +374,17 @@ final class ManiphestTaskSearchEngine ManiphestBulkEditCapability::CAPABILITY); } + $custom_field_lists = $this->loadCustomFields( + $tasks, + PhabricatorCustomField::ROLE_LIST); + $list = id(new ManiphestTaskResultListView()) ->setUser($viewer) ->setTasks($tasks) + ->setHandles($handles) ->setSavedQuery($saved) ->setCanBatchEdit($can_bulk_edit) + ->setCustomFieldLists($custom_field_lists) ->setShowBatchControls($this->showBatchControls); $result = new PhabricatorApplicationSearchResultView(); @@ -387,6 +393,24 @@ final class ManiphestTaskSearchEngine return $result; } + protected function getRequiredHandlePHIDsForResultList( + array $objects, + PhabricatorSavedQuery $query) { + + $phids = array(); + foreach ($objects as $task) { + $assigned_phid = $task->getOwnerPHID(); + if ($assigned_phid) { + $phids[] = $assigned_phid; + } + foreach ($task->getProjectPHIDs() as $project_phid) { + $phids[] = $project_phid; + } + } + + return $phids; + } + protected function willUseSavedQuery(PhabricatorSavedQuery $saved) { // The 'withUnassigned' parameter may be present in old saved queries from diff --git a/src/applications/maniphest/view/ManiphestTaskListView.php b/src/applications/maniphest/view/ManiphestTaskListView.php index f9ad9e6046..d5d3d1e9c5 100644 --- a/src/applications/maniphest/view/ManiphestTaskListView.php +++ b/src/applications/maniphest/view/ManiphestTaskListView.php @@ -4,6 +4,7 @@ final class ManiphestTaskListView extends ManiphestView { private $tasks; private $handles; + private $customFieldLists = array(); private $showBatchControls; private $noDataString; @@ -19,6 +20,11 @@ final class ManiphestTaskListView extends ManiphestView { return $this; } + public function setCustomFieldLists(array $lists) { + $this->customFieldLists = $lists; + return $this; + } + public function setShowBatchControls($show_batch_controls) { $this->showBatchControls = $show_batch_controls; return $this; @@ -132,12 +138,21 @@ final class ManiphestTaskListView extends ManiphestView { ->setHref($href)); } + + $field_list = idx($this->customFieldLists, $task->getPHID()); + if ($field_list) { + $field_list + ->addFieldsToListViewItem($task, $this->getViewer(), $item); + } + $list->addItem($item); } return $list; } + // This method should be removed, and all call-sites switch + // to use ManiphestSearchEngine public static function loadTaskHandles( PhabricatorUser $viewer, array $tasks) { diff --git a/src/applications/maniphest/view/ManiphestTaskResultListView.php b/src/applications/maniphest/view/ManiphestTaskResultListView.php index cc2a135292..6ee2025265 100644 --- a/src/applications/maniphest/view/ManiphestTaskResultListView.php +++ b/src/applications/maniphest/view/ManiphestTaskResultListView.php @@ -3,6 +3,8 @@ final class ManiphestTaskResultListView extends ManiphestView { private $tasks; + private $handles; + private $customFieldLists = array(); private $savedQuery; private $canBatchEdit; private $showBatchControls; @@ -17,6 +19,16 @@ final class ManiphestTaskResultListView extends ManiphestView { return $this; } + public function setHandles(array $handles) { + $this->handles = $handles; + return $this; + } + + public function setCustomFieldLists(array $lists) { + $this->customFieldLists = $lists; + return $this; + } + public function setCanBatchEdit($can_batch_edit) { $this->canBatchEdit = $can_batch_edit; return $this; @@ -42,11 +54,10 @@ final class ManiphestTaskResultListView extends ManiphestView { $group_parameter = nonempty($query->getParameter('group'), 'priority'); $order_parameter = nonempty($query->getParameter('order'), 'priority'); - $handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); $groups = $this->groupTasks( $tasks, $group_parameter, - $handles); + $this->handles); $result = array(); @@ -56,7 +67,8 @@ final class ManiphestTaskResultListView extends ManiphestView { $task_list->setShowBatchControls($this->showBatchControls); $task_list->setUser($viewer); $task_list->setTasks($list); - $task_list->setHandles($handles); + $task_list->setHandles($this->handles); + $task_list->setCustomFieldLists($this->customFieldLists); $header = id(new PHUIHeaderView()) ->addSigil('task-group') diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index cad397b0ac..9094ead227 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -13,6 +13,7 @@ * @task read Reading Utilities * @task exec Paging and Executing Queries * @task render Rendering Results + * @task custom Custom Fields */ abstract class PhabricatorApplicationSearchEngine extends Phobject { @@ -1071,7 +1072,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { if ($phids) { $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) - ->witHPHIDs($phids) + ->withPHIDs($phids) ->execute(); } else { $handles = array(); @@ -1626,4 +1627,33 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { return $supported; } + /** + * Load from object and from storage, and updates Custom Fields instances + * that are attached to each object. + * + * @return mapPhabricatorCustomFieldList> of loaded fields. + * @task custom + */ + protected function loadCustomFields(array $objects, $role) { + assert_instances_of($objects, 'PhabricatorCustomFieldInterface'); + + $query = new PhabricatorCustomFieldStorageQuery(); + $lists = array(); + + foreach ($objects as $object) { + $field_list = PhabricatorCustomField::getObjectFields($object, $role); + $field_list->readFieldsFromObject($object); + foreach ($field_list->getFields() as $field) { + // TODO move $viewer into PhabricatorCustomFieldStorageQuery + $field->setViewer($this->viewer); + } + $lists[$object->getPHID()] = $field_list; + $query->addFields($field_list->getFields()); + } + // This updates the field_list objects. + $query->execute(); + + return $lists; + } + } diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php index 4e55348c36..774dbb6630 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomField.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php @@ -384,6 +384,7 @@ abstract class PhabricatorCustomField extends Phobject { * * @param PhabricatorCustomField Field implementation. * @return this + * @task proxy */ final public function setProxy(PhabricatorCustomField $proxy) { if (!$this->canSetProxy()) { @@ -400,12 +401,20 @@ abstract class PhabricatorCustomField extends Phobject { * @{method:canSetProxy}. * * @return PhabricatorCustomField|null Proxy field, if one is set. + * @task proxy */ final public function getProxy() { return $this->proxy; } - + /** + * @task proxy + */ + public function __clone() { + if ($this->proxy) { + $this->proxy = clone $this->proxy; + } + } /* -( Contextual Data )---------------------------------------------------- */ @@ -827,7 +836,7 @@ abstract class PhabricatorCustomField extends Phobject { /** - * Appearing in ApplicationTrasactions allows a field to be edited using + * Appearing in ApplicationTransactions allows a field to be edited using * standard workflows. * * @return bool True to appear in ApplicationTransactions. diff --git a/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php b/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php index 60a5171cdd..369bed2297 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php @@ -200,6 +200,19 @@ final class PhabricatorCustomFieldList extends Phobject { } } + public function addFieldsToListViewItem( + PhabricatorCustomFieldInterface $object, + PhabricatorUser $viewer, + PHUIObjectItemView $view) { + + foreach ($this->fields as $field) { + if ($field->shouldAppearInListView()) { + $field->setViewer($viewer); + $field->renderOnListItem($view); + } + } + } + public function buildFieldTransactionsFromRequest( PhabricatorApplicationTransaction $template, AphrontRequest $request) { From 67b7181f522673b05435083ce28106bdfa320e18 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 9 May 2024 15:50:39 +0200 Subject: [PATCH 002/118] Facts: Always render YYYY-MM-DD dates with eight digits in chart tooltips Summary: Always render dates in tooltips when hovering over data points in Burndown charts etc of the Facts application as `YYYY-MM-DD` instead of `YYYY-M-D` by converting the integer to a string and if its length is only one character, prepend a zero. Closes T15819 Test Plan: * Enable the Facts application, go to the Reports of a Project with task changes over time, hover over data points and read the tooltip. * Check Console of web browser's developer tools for no errors. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15819 Differential Revision: https://we.phorge.it/D25630 --- webroot/rsrc/js/application/fact/Chart.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/webroot/rsrc/js/application/fact/Chart.js b/webroot/rsrc/js/application/fact/Chart.js index 473feedcea..991acd7347 100644 --- a/webroot/rsrc/js/application/fact/Chart.js +++ b/webroot/rsrc/js/application/fact/Chart.js @@ -194,9 +194,15 @@ JX.install('Chart', { var d_y = dd.getFullYear(); // NOTE: Javascript months are zero-based. See PHI1017. - var d_m = dd.getMonth() + 1; + var d_m = (dd.getMonth() + 1).toString(); + if (d_m.length == 1) { + d_m = '0' + d_m; + } - var d_d = dd.getDate(); + var d_d = dd.getDate().toString(); + if (d_d.length == 1) { + d_d = '0' + d_d; + } var y = parseInt(d.y1); From 8a3b70aa7d4c3376dabf5dfd5d3a0df68998711c Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Mon, 13 May 2024 18:02:55 +0200 Subject: [PATCH 003/118] Run ./bin/celerity map after previous commit Summary: Followup to rP67b7181f522673b05435083ce28106bdfa320e18 Test Plan: Run `./bin/celerity map` and see changes due to my previous commit. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25637 --- resources/celerity/map.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d0ae1e8f0e..45f2ac6a6d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -396,7 +396,7 @@ return array( 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a', 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b', - 'rsrc/js/application/fact/Chart.js' => '52e3ff03', + 'rsrc/js/application/fact/Chart.js' => '0076c427', 'rsrc/js/application/fact/ChartCurtainView.js' => '86954222', 'rsrc/js/application/fact/ChartFunctionLabel.js' => '81de1dab', 'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22', @@ -702,7 +702,7 @@ return array( 'javelin-behavior-user-menu' => '60cd9241', 'javelin-behavior-view-placeholder' => 'a9942052', 'javelin-behavior-workflow' => '9623adc1', - 'javelin-chart' => '52e3ff03', + 'javelin-chart' => '0076c427', 'javelin-chart-curtain-view' => '86954222', 'javelin-chart-function-label' => '81de1dab', 'javelin-color' => '78f811c9', @@ -918,6 +918,12 @@ return array( 'unhandled-exception-css' => '9ecfc00d', ), 'requires' => array( + '0076c427' => array( + 'phui-chart-css', + 'd3', + 'javelin-chart-curtain-view', + 'javelin-chart-function-label', + ), '0116d3e8' => array( 'javelin-behavior', 'javelin-dom', @@ -1419,12 +1425,6 @@ return array( 'javelin-dom', 'javelin-fx', ), - '52e3ff03' => array( - 'phui-chart-css', - 'd3', - 'javelin-chart-curtain-view', - 'javelin-chart-function-label', - ), '541f81c3' => array( 'javelin-install', ), From 464a0aedc45b5b445ee1fe499cfd08634be8b804 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Wed, 15 May 2024 14:32:25 +0200 Subject: [PATCH 004/118] Fix PHP 8.1 "strlen(null)" exception in PhabricatorAuthStartController.php Summary: `strlen()` was used in Phabricator to check if a generic value is a non-empty string. This behavior is deprecated since PHP 8.1. Phorge adopts `phutil_nonempty_string()` as a replacement. Note: this may highlight other absurd input values that might be worth correcting instead of just ignoring. If phutil_nonempty_string() throws an exception in your instance, report it to Phorge to evaluate and fix that specific corner case. ``` ERROR 8192: strlen(): Passing null to parameter #1 ($string) of type string is deprecated at [/var/www/html/phorge/phorge/src/applications/auth/controller/PhabricatorAuthStartController.php:34] ``` Closes T15832 Test Plan: Run `arc unit` locally, probably also don't be logged in. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15832 Differential Revision: https://we.phorge.it/D25639 --- .../auth/controller/PhabricatorAuthStartController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/auth/controller/PhabricatorAuthStartController.php b/src/applications/auth/controller/PhabricatorAuthStartController.php index 76c7eb9df5..57e294f6dc 100644 --- a/src/applications/auth/controller/PhabricatorAuthStartController.php +++ b/src/applications/auth/controller/PhabricatorAuthStartController.php @@ -31,7 +31,7 @@ final class PhabricatorAuthStartController $session_token = $request->getCookie(PhabricatorCookies::COOKIE_SESSION); $did_clear = $request->getStr('cleared'); - if (strlen($session_token)) { + if (phutil_nonempty_string($session_token)) { $kind = PhabricatorAuthSessionEngine::getSessionKindFromToken( $session_token); switch ($kind) { From 50d0bbe8ff6883a527ddee3d083e0dd8054a141e Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Mon, 13 May 2024 19:48:38 +0200 Subject: [PATCH 005/118] Fix PHP 8.1 "strlen(null)" exception rendering files/documents Summary: `strlen()` was used in Phabricator to check if a generic value is a non-empty string. This behavior is deprecated since PHP 8.1. Phorge adopts `phutil_nonempty_string()` as a replacement. Note: this may highlight other absurd input values that might be worth correcting instead of just ignoring. If phutil_nonempty_string() throws an exception in your instance, report it to Phorge to evaluate and fix that specific corner case. ``` ERROR 8192: strlen(): Passing null to parameter #1 ($string) of type string is deprecated at [/var/www/html/phorge/phorge/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php:211] ``` Closes T15826 Test Plan: Surf a Diffusion repository and play with {nav View Options}. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15826 Differential Revision: https://we.phorge.it/D25638 --- .../document/render/PhabricatorDocumentRenderingEngine.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php index f43689bbba..c664e56b6d 100644 --- a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php +++ b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php @@ -208,12 +208,12 @@ abstract class PhabricatorDocumentRenderingEngine $this->activeEngine = $engine; $encode_setting = $request->getStr('encode'); - if (strlen($encode_setting)) { + if (phutil_nonempty_string($encode_setting)) { $engine->setEncodingConfiguration($encode_setting); } $highlight_setting = $request->getStr('highlight'); - if (strlen($highlight_setting)) { + if (phutil_nonempty_string($highlight_setting)) { $engine->setHighlightingConfiguration($highlight_setting); } From d5295da072aec86556e17a59e117fc1e54c82ecc Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Sat, 11 May 2024 16:32:08 +0200 Subject: [PATCH 006/118] Remove deprecated buildStandardPage* method calls Summary: Remove deprecated `buildStandardPageView()` and `buildStandardPageResponse()` methods. They have been deprecated since rP300c74c49dc9d9f3ef1d7bbcc1b2400a3ded0c41 in November 2015. cf rPd26cca27d70b69080c10e1df92a10419fffbfc7e, rP1b00ef08a039d48adb6bbbcd6956f900b6303b5b. Test Plan: Compare http://phorge.localhost/xhpast/ before and after; paste and parse some PHP code into http://phorge.localhost/xhpast/; compare resulting http://phorge.localhost/xhpast/view/1/ before and after. Spot no differences and see no errors in DarkConsole. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25634 --- .../base/controller/PhabricatorController.php | 21 ------------------- .../PhabricatorXHPASTViewController.php | 14 ------------- .../PhabricatorXHPASTViewFrameController.php | 13 ++++++------ 3 files changed, 7 insertions(+), 41 deletions(-) diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index d1b1fcfc8e..3f04e5a62a 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -627,25 +627,4 @@ abstract class PhabricatorController extends AphrontController { return $this->delegateToController($controller); } - -/* -( Deprecated )--------------------------------------------------------- */ - - - /** - * DEPRECATED. Use @{method:newPage}. - */ - public function buildStandardPageView() { - return $this->newPage(); - } - - - /** - * DEPRECATED. Use @{method:newPage}. - */ - public function buildStandardPageResponse($view, array $data) { - $page = $this->buildStandardPageView(); - $page->appendChild($view); - return $page->produceAphrontResponse(); - } - } diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewController.php index 2ef34fb36f..3b36112a24 100644 --- a/src/applications/phpast/controller/PhabricatorXHPASTViewController.php +++ b/src/applications/phpast/controller/PhabricatorXHPASTViewController.php @@ -1,18 +1,4 @@ buildStandardPageView(); - - $page->setApplicationName('XHPASTView'); - $page->setBaseURI('/xhpast/'); - $page->setTitle(idx($data, 'title')); - $page->setGlyph("\xE2\x96\xA0"); - $page->appendChild($view); - - $response = new AphrontWebpageResponse(); - return $response->setContent($page->render()); - } - } diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewFrameController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewFrameController.php index 89f5b8fd7d..73c10cab44 100644 --- a/src/applications/phpast/controller/PhabricatorXHPASTViewFrameController.php +++ b/src/applications/phpast/controller/PhabricatorXHPASTViewFrameController.php @@ -10,17 +10,18 @@ final class PhabricatorXHPASTViewFrameController public function handleRequest(AphrontRequest $request) { $id = $request->getURIData('id'); - return $this->buildStandardPageResponse( - phutil_tag( + return $this->newPage() + ->setApplicationName('XHPASTView') + ->setBaseURI('/xhpast/') + ->setTitle(pht('XHPAST View')) + ->setGlyph("\xE2\x96\xA0") + ->appendChild(phutil_tag( 'iframe', array( 'src' => "/xhpast/frameset/{$id}/", 'frameborder' => '0', 'style' => 'width: 100%; height: 800px;', '', - )), - array( - 'title' => pht('XHPAST View'), - )); + ))); } } From e6925c83c187bc7c24913116c0c7e84a8f80d588 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 26 Apr 2024 12:32:46 +0200 Subject: [PATCH 007/118] Do not hardcode default Priority names in Project Reports tooltip Summary: Pull the names of Priority field values instead of hardcoding them. Closes T15799 Test Plan: * As an admin, go to `/config/edit/maniphest.priorities/` and change the value of `"name"` of a Priority value with a value < `50` (e.g.: Low, Wishlist) * Open `/maniphest/report/project/` * Hover over the `Oldest (Pri)` column and check the tooltip text before and after applying this patch Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15799 Differential Revision: https://we.phorge.it/D25602 --- .../controller/ManiphestReportController.php | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index a7efe89194..012d6d136b 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -487,6 +487,12 @@ final class ManiphestReportController extends ManiphestController { ); } + private function getAveragePriority() { + // TODO: This is sort of a hard-code for the default "normal" status. + // When reports are more powerful, this should be made more general. + return 50; + } + public function renderOpenTasks() { $request = $this->getRequest(); $viewer = $request->getUser(); @@ -626,9 +632,7 @@ final class ManiphestReportController extends ManiphestController { $normal_or_better = array(); foreach ($taskv as $id => $task) { - // TODO: This is sort of a hard-code for the default "normal" status. - // When reports are more powerful, this should be made more general. - if ($task->getPriority() < 50) { + if ($task->getPriority() < $this->getAveragePriority()) { continue; } $normal_or_better[$id] = $task; @@ -700,13 +704,22 @@ final class ManiphestReportController extends ManiphestController { ), pht('Oldest (All)')); $cclass[] = 'n'; + $low_priorities = array(); + $priorities_map = ManiphestTaskPriority::getTaskPriorityMap(); + $normal_priority = $this->getAveragePriority(); + foreach ($priorities_map as $pri => $full_label) { + if ($pri < $normal_priority) { + $low_priorities[] = $full_label; + } + } + $pri_string = implode(', ', $low_priorities); $cname[] = javelin_tag( 'span', array( 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => pht( - 'Oldest open task, excluding those with Low or Wishlist priority.'), + 'Oldest open task, excluding those with priority %s', $pri_string), 'size' => 200, ), ), From a6499aee0ef4e9bbf2ee25830b9000b1c43ee16b Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 17 May 2024 16:18:01 +0200 Subject: [PATCH 008/118] PHPMailer: Remove a check for PHP 5.0 Summary: Phorge requires PHP 5.5, thus remove a version check for PHP 5.0. Compare the same code in https://github.com/PHPMailer/PHPMailer/blob/master/src/POP3.php#L282-L283 Test Plan: Carefully read the code. Reviewers: O1 Blessed Committers, speck Reviewed By: O1 Blessed Committers, speck Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25661 --- externals/phpmailer/class.pop3.php | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/externals/phpmailer/class.pop3.php b/externals/phpmailer/class.pop3.php index f9fd3b2edb..c48191bd72 100644 --- a/externals/phpmailer/class.pop3.php +++ b/externals/phpmailer/class.pop3.php @@ -239,16 +239,7 @@ class POP3 { } // Increase the stream time-out - - // Check for PHP 4.3.0 or later - if (version_compare(phpversion(), '5.0.0', 'ge')) { - stream_set_timeout($this->pop_conn, $tval, 0); - } else { - // Does not work on Windows - if (substr(PHP_OS, 0, 3) !== 'WIN') { - socket_set_timeout($this->pop_conn, $tval, 0); - } - } + stream_set_timeout($this->pop_conn, $tval, 0); // Get the POP3 server response $pop3_response = $this->getResponse(); @@ -404,4 +395,4 @@ class POP3 { // End of class } -?> \ No newline at end of file +?> From f4d9d6920bcdcafedd6a05f34c28642589b3c285 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Sat, 18 May 2024 10:05:18 +0200 Subject: [PATCH 009/118] The feed "created this task" should be the first one Summary: Create a Maniphest task, Resolved, High priority. Before this change, this was the feed in the object page: - a.lincoln closed this task as Resolved. - a.lincoln triaged this task as High priority. - a.lincoln __created this task__. After this change, this is the feed: - a.lincoln __created this task__. - a.lincoln closed this task as Resolved. - a.lincoln triaged this task as High priority. This also improves the situation in the /feed/ page. So you see the creation now. Ref T15816 Test Plan: Create an object in any mentioned application below, setting all fields. Then, check the history of the object itself. Check also the /feed/ page. Check that the creation action is now listed first. This change improves the situation in: - Maniphest - Phriction This change has no impact on these that seem already OK: - Calendar - Dashboard - Dashboard Panel - Diffusion - Macro - Paste - Ponder - Project - Pholio This change has no impact on these that seem still in the wrong order: - Differential The above situation in Differential is noted, but it's unrelated, since it has a different legacy structure and it deserves more triaging. Reviewers: O1 Blessed Committers, aklapper Reviewed By: O1 Blessed Committers, aklapper Subscribers: aklapper, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15816 Differential Revision: https://we.phorge.it/D25626 --- .../xaction/PhabricatorCoreCreateTransaction.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/applications/transactions/xaction/PhabricatorCoreCreateTransaction.php b/src/applications/transactions/xaction/PhabricatorCoreCreateTransaction.php index 0c8c337193..15becceab9 100644 --- a/src/applications/transactions/xaction/PhabricatorCoreCreateTransaction.php +++ b/src/applications/transactions/xaction/PhabricatorCoreCreateTransaction.php @@ -27,4 +27,13 @@ final class PhabricatorCoreCreateTransaction return $editor->getCreateObjectTitleForFeed($author, $object); } + public function getActionStrength() { + // The creation feed is supposed to be "more important" than other things. + // So a Task is first created and then closed, and not vice-versa. + // The default null was causing weirdnesses in Maniphest and Phriction. + // See ManiphestTaskTitleTransaction#getActionStrength() + // See PhrictionDocumentTitleTransaction#getActionStrength() + return 140; + } + } From b8a03bc145b124d559e613868323eccf5c9ebbac Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Sat, 18 May 2024 10:06:53 +0200 Subject: [PATCH 010/118] Workboard Trigger Rule: allow to Add/Remove Subscribers Summary: This is a natural expansion of the Workboard Trigger system to support actions about Subscribers. Context: even before this change - Workboard users were already able to move Tasks to Columns with nice automatisms. For instance, you can already: - Play a nice Sound - Change Task Priority - Change Task Status - Add or Remove a Task Owner - Add or Remove a Task Project Tag With this change, you can also: - Add or Remove Task Subscribers If you need inspiration, this feature is useful for adding more eyes on a given work area; lighten the notifications of certain people after certain workflows have already been done, to increase happiness and mitigate Burnout. If your goal is to oppress yourself or your coworkers - of course you can also use this feature to increase the Burnout of yourself or your coworkers, adding random people as Subscriber. Note: every trigger action can be confusing in general, but it is the fault of Triggers in general and not related to this specific feature - that is totally loveable, just like you. Closes T15162 Test Plan: - Workboard > Column > Create Trigger - Add Action > Add Subscriber > (your best friend who hates NodeJS) - Add Action > Remove Subscriber > (your antagonist who hates PHP) - Move some Tasks there and here, and note that Add/Remove works Reviewers: O1 Blessed Committers, Cigaryno, aklapper Reviewed By: O1 Blessed Committers, Cigaryno, aklapper Subscribers: aklapper, speck, tobiaswiese, Matthew, Cigaryno Tags: #workboard Maniphest Tasks: T15162 Differential Revision: https://we.phorge.it/D25080 --- src/__phutil_library_map__.php | 4 + ...icatorProjectTriggerAddSubscribersRule.php | 127 ++++++++++++++++++ ...torProjectTriggerRemoveSubscribersRule.php | 127 ++++++++++++++++++ 3 files changed, 258 insertions(+) create mode 100644 src/applications/project/trigger/PhabricatorProjectTriggerAddSubscribersRule.php create mode 100644 src/applications/project/trigger/PhabricatorProjectTriggerRemoveSubscribersRule.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b21f29d1b4..02e4c7f2b3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4491,6 +4491,7 @@ phutil_register_library_map(array( 'PhabricatorProjectTransactionType' => 'applications/project/xaction/PhabricatorProjectTransactionType.php', 'PhabricatorProjectTrigger' => 'applications/project/storage/PhabricatorProjectTrigger.php', 'PhabricatorProjectTriggerAddProjectsRule' => 'applications/project/trigger/PhabricatorProjectTriggerAddProjectsRule.php', + 'PhabricatorProjectTriggerAddSubscribersRule' => 'applications/project/trigger/PhabricatorProjectTriggerAddSubscribersRule.php', 'PhabricatorProjectTriggerController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerController.php', 'PhabricatorProjectTriggerCorruptionException' => 'applications/project/exception/PhabricatorProjectTriggerCorruptionException.php', 'PhabricatorProjectTriggerEditController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php', @@ -4505,6 +4506,7 @@ phutil_register_library_map(array( 'PhabricatorProjectTriggerPlaySoundRule' => 'applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php', 'PhabricatorProjectTriggerQuery' => 'applications/project/query/PhabricatorProjectTriggerQuery.php', 'PhabricatorProjectTriggerRemoveProjectsRule' => 'applications/project/trigger/PhabricatorProjectTriggerRemoveProjectsRule.php', + 'PhabricatorProjectTriggerRemoveSubscribersRule' => 'applications/project/trigger/PhabricatorProjectTriggerRemoveSubscribersRule.php', 'PhabricatorProjectTriggerRule' => 'applications/project/trigger/PhabricatorProjectTriggerRule.php', 'PhabricatorProjectTriggerRuleRecord' => 'applications/project/trigger/PhabricatorProjectTriggerRuleRecord.php', 'PhabricatorProjectTriggerRulesetTransaction' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php', @@ -11129,6 +11131,7 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', ), 'PhabricatorProjectTriggerAddProjectsRule' => 'PhabricatorProjectTriggerRule', + 'PhabricatorProjectTriggerAddSubscribersRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerController' => 'PhabricatorProjectController', 'PhabricatorProjectTriggerCorruptionException' => 'Exception', 'PhabricatorProjectTriggerEditController' => 'PhabricatorProjectTriggerController', @@ -11143,6 +11146,7 @@ phutil_register_library_map(array( 'PhabricatorProjectTriggerPlaySoundRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorProjectTriggerRemoveProjectsRule' => 'PhabricatorProjectTriggerRule', + 'PhabricatorProjectTriggerRemoveSubscribersRule' => 'PhabricatorProjectTriggerRule', 'PhabricatorProjectTriggerRule' => 'Phobject', 'PhabricatorProjectTriggerRuleRecord' => 'Phobject', 'PhabricatorProjectTriggerRulesetTransaction' => 'PhabricatorProjectTriggerTransactionType', diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerAddSubscribersRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerAddSubscribersRule.php new file mode 100644 index 0000000000..28b0c614fe --- /dev/null +++ b/src/applications/project/trigger/PhabricatorProjectTriggerAddSubscribersRule.php @@ -0,0 +1,127 @@ +getDatasource()->getWireTokens($this->getValue()); + } + + protected function assertValidRuleRecordFormat($value) { + if (!is_array($value)) { + throw new Exception( + pht( + 'Add subscribers rule value should be a list, but is not '. + '(value is "%s").', + phutil_describe_type($value))); + } + } + + protected function assertValidRuleRecordValue($value) { + if (!$value) { + throw new Exception( + pht( + 'You must select at least one user or project tag to add.')); + } + } + + protected function newDropTransactions($object, $value) { + $subscriber_edge_type = PhabricatorObjectHasSubscriberEdgeType::EDGECONST; + + $xaction = $object->getApplicationTransactionTemplate() + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $subscriber_edge_type) + ->setNewValue( + array( + '+' => array_fuse($value), + )); + + return array($xaction); + } + + protected function newDropEffects($value) { + return array( + $this->newEffect() + ->setIcon('fa-briefcase') + ->setContent($this->getRuleViewDescription($value)), + ); + } + + protected function getDefaultValue() { + return null; + } + + protected function getPHUIXControlType() { + return 'tokenizer'; + } + + private function getDatasource() { + $datasource = new PhabricatorProjectOrUserDatasource(); + + if ($this->getViewer()) { + $datasource->setViewer($this->getViewer()); + } + + return $datasource; + } + + protected function getPHUIXControlSpecification() { + $template = id(new AphrontTokenizerTemplateView()) + ->setViewer($this->getViewer()); + + $template_markup = $template->render(); + $datasource = $this->getDatasource(); + + return array( + 'markup' => (string)hsprintf('%s', $template_markup), + 'config' => array( + 'src' => $datasource->getDatasourceURI(), + 'browseURI' => $datasource->getBrowseURI(), + 'placeholder' => $datasource->getPlaceholderText(), + 'limit' => $datasource->getLimit(), + ), + 'value' => null, + ); + } + + public function getRuleViewLabel() { + return pht('Add subscribers'); + } + + public function getRuleViewDescription($value) { + return pht( + 'Add subscribers: %s.', + phutil_tag( + 'strong', + array(), + $this->getViewer() + ->renderHandleList($value) + ->setAsInline(true) + ->render())); + } + + public function getRuleViewIcon($value) { + return id(new PHUIIconView()) + ->setIcon('fa-users', 'green'); + } + + +} diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerRemoveSubscribersRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerRemoveSubscribersRule.php new file mode 100644 index 0000000000..2cd3fadfee --- /dev/null +++ b/src/applications/project/trigger/PhabricatorProjectTriggerRemoveSubscribersRule.php @@ -0,0 +1,127 @@ +getDatasource()->getWireTokens($this->getValue()); + } + + protected function assertValidRuleRecordFormat($value) { + if (!is_array($value)) { + throw new Exception( + pht( + 'Remove subscribers rule value should be a list, but is not '. + '(value is "%s").', + phutil_describe_type($value))); + } + } + + protected function assertValidRuleRecordValue($value) { + if (!$value) { + throw new Exception( + pht( + 'You must select at least one user or project tag to remove.')); + } + } + + protected function newDropTransactions($object, $value) { + $subscriber_edge_type = PhabricatorObjectHasSubscriberEdgeType::EDGECONST; + + $xaction = $object->getApplicationTransactionTemplate() + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $subscriber_edge_type) + ->setNewValue( + array( + '-' => array_fuse($value), + )); + + return array($xaction); + } + + protected function newDropEffects($value) { + return array( + $this->newEffect() + ->setIcon('fa-briefcase') + ->setContent($this->getRuleViewDescription($value)), + ); + } + + protected function getDefaultValue() { + return null; + } + + protected function getPHUIXControlType() { + return 'tokenizer'; + } + + private function getDatasource() { + $datasource = new PhabricatorProjectOrUserDatasource(); + + if ($this->getViewer()) { + $datasource->setViewer($this->getViewer()); + } + + return $datasource; + } + + protected function getPHUIXControlSpecification() { + $template = id(new AphrontTokenizerTemplateView()) + ->setViewer($this->getViewer()); + + $template_markup = $template->render(); + $datasource = $this->getDatasource(); + + return array( + 'markup' => (string)hsprintf('%s', $template_markup), + 'config' => array( + 'src' => $datasource->getDatasourceURI(), + 'browseURI' => $datasource->getBrowseURI(), + 'placeholder' => $datasource->getPlaceholderText(), + 'limit' => $datasource->getLimit(), + ), + 'value' => null, + ); + } + + public function getRuleViewLabel() { + return pht('Remove subscribers'); + } + + public function getRuleViewDescription($value) { + return pht( + 'Remove subscribers: %s.', + phutil_tag( + 'strong', + array(), + $this->getViewer() + ->renderHandleList($value) + ->setAsInline(true) + ->render())); + } + + public function getRuleViewIcon($value) { + return id(new PHUIIconView()) + ->setIcon('fa-users', 'red'); + } + +} From 8c8211787894a9d6ab48adadb3457fed093cecb8 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 17 May 2024 11:20:07 +0200 Subject: [PATCH 011/118] Fix PonderQuestionCreateMailReceiver accessing non-existing PonderQuestionTransaction constants Summary: The transaction types `TYPE_TITLE` and `TYPE_CONTENT` were removed from `PonderQuestionTransaction` in rPdff028c4907dd1959859733ea0d947f244559e7f. Thus update `PonderQuestionCreateMailReceiver` to use the corresponding modular transactions `PonderQuestionTitleTransaction` and `PonderQuestionContentTransaction` instead. Test Plan: Carefully read the code, grep the code base, use the test case in D25656#18162. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25656 --- .../ponder/mail/PonderQuestionCreateMailReceiver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/ponder/mail/PonderQuestionCreateMailReceiver.php b/src/applications/ponder/mail/PonderQuestionCreateMailReceiver.php index 32669855ea..496cedb8a1 100644 --- a/src/applications/ponder/mail/PonderQuestionCreateMailReceiver.php +++ b/src/applications/ponder/mail/PonderQuestionCreateMailReceiver.php @@ -20,11 +20,11 @@ final class PonderQuestionCreateMailReceiver $xactions = array(); $xactions[] = id(new PonderQuestionTransaction()) - ->setTransactionType(PonderQuestionTransaction::TYPE_TITLE) + ->setTransactionType(PonderQuestionTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); $xactions[] = id(new PonderQuestionTransaction()) - ->setTransactionType(PonderQuestionTransaction::TYPE_CONTENT) + ->setTransactionType(PonderQuestionContentTransaction::TRANSACTIONTYPE) ->setNewValue($mail->getCleanTextBody()); $question = PonderQuestion::initializeNewQuestion($author); From 23a3f59be227e2e6b7658535c567adf3986fb702 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Sat, 18 May 2024 21:44:28 +0200 Subject: [PATCH 012/118] Fix typo creating QRbitstream() Summary: Make the one `new QRbitrtream()` look like the other nine `new QRbitstream()`. Interestingly, this bug was reported upstream since 2018, and still not fixed: https://github.com/t0k4rt/phpqrcode/issues/47 Test Plan: Read the code aloud and realize that this word sound weird. Verify that the class `QRbitrtream` does not exist, and `QRbitstream` exists. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25651 --- externals/phpqrcode/phpqrcode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/externals/phpqrcode/phpqrcode.php b/externals/phpqrcode/phpqrcode.php index 54cde3ee9e..d441593f63 100644 --- a/externals/phpqrcode/phpqrcode.php +++ b/externals/phpqrcode/phpqrcode.php @@ -1159,7 +1159,7 @@ { try { - $bs = new QRbitrtream(); + $bs = new QRbitstream(); $bs->appendNum(4, 0x8); $bs->appendNum(QRspec::lengthIndicator(QR_MODE_KANJI, $version), (int)($this->size / 2)); From c1a8b83bd7ea3951f4daef2e678d5f77a62ad6f5 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 16 May 2024 16:41:44 +0200 Subject: [PATCH 013/118] Correct PHPDoc or add default return for numerous methods Summary: Correct PHPDoc when none of the method calls try to handle a return value. Add missing fallback `return` statements (which may never be reached if previous condition checks were always true). This reduces the amount of errors reported by static code analysis tools. Test Plan: Carefully read the code and check all places in which a function gets called whether a return value is expected. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25650 --- src/aphront/configuration/AphrontApplicationConfiguration.php | 1 + src/applications/people/storage/PhabricatorUserEmail.php | 1 - src/applications/phame/storage/PhamePost.php | 1 + src/infrastructure/daemon/workers/PhabricatorWorker.php | 1 - src/infrastructure/edges/editor/PhabricatorEdgeEditor.php | 1 - support/startup/PhabricatorStartup.php | 1 - 6 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php index 3bdd2c00bc..3198bb9fd4 100644 --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -523,6 +523,7 @@ final class AphrontApplicationConfiguration return array($result->getController(), $result->getURIData()); } } + return null; } private function buildSiteForRequest(AphrontRequest $request) { diff --git a/src/applications/people/storage/PhabricatorUserEmail.php b/src/applications/people/storage/PhabricatorUserEmail.php index d9866f2c43..47c2911601 100644 --- a/src/applications/people/storage/PhabricatorUserEmail.php +++ b/src/applications/people/storage/PhabricatorUserEmail.php @@ -231,7 +231,6 @@ final class PhabricatorUserEmail * * @param PhabricatorUser The user sending the notification. * @param PhabricatorUserEmail New primary email address. - * @return this * @task email */ public function sendOldPrimaryEmail( diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index 5291bcd465..9b4a6ba01a 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -268,6 +268,7 @@ final class PhamePost extends PhameDAO case self::MARKUP_FIELD_BODY: return $this->getBody(); } + return null; } public function didMarkupText( diff --git a/src/infrastructure/daemon/workers/PhabricatorWorker.php b/src/infrastructure/daemon/workers/PhabricatorWorker.php index c62fde447f..2b1e650485 100644 --- a/src/infrastructure/daemon/workers/PhabricatorWorker.php +++ b/src/infrastructure/daemon/workers/PhabricatorWorker.php @@ -262,7 +262,6 @@ abstract class PhabricatorWorker extends Phobject { * you are using queues to improve locking behavior). * * @param map Optional default options. - * @return this */ final public function flushTaskQueue($defaults = array()) { foreach ($this->getQueuedTasks() as $task) { diff --git a/src/infrastructure/edges/editor/PhabricatorEdgeEditor.php b/src/infrastructure/edges/editor/PhabricatorEdgeEditor.php index 588b5267b4..d019c7d0fe 100644 --- a/src/infrastructure/edges/editor/PhabricatorEdgeEditor.php +++ b/src/infrastructure/edges/editor/PhabricatorEdgeEditor.php @@ -90,7 +90,6 @@ final class PhabricatorEdgeEditor extends Phobject { * (e.g., adds followed by removals) if their outcomes are not dependent, * since transactions will not be held open as long. * - * @return this * @task edit */ public function save() { diff --git a/support/startup/PhabricatorStartup.php b/support/startup/PhabricatorStartup.php index 10c6295587..2ae5993dfb 100644 --- a/support/startup/PhabricatorStartup.php +++ b/support/startup/PhabricatorStartup.php @@ -317,7 +317,6 @@ final class PhabricatorStartup { * @param Throwable The exception itself. * @param bool True if it's okay to show the exception's stack trace * to the user. The trace will always be logged. - * @return exit This method **does not return**. * * @task apocalypse */ From 601ac4503276ffa0b749cda358d07c1f30c64c59 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Sun, 19 May 2024 01:12:34 +0200 Subject: [PATCH 014/118] PHPMailer: Remove check for non-existing get_magic_quotes() Summary: No idea if PHP ever offered such a method. In any case, this very check was removed in https://github.com/PHPMailer/PHPMailer/commit/6f919df65e80023fce4eab58832b6035b4a41935#diff-2d294eae28fb0d70fc32da8369c4fe33d301c99ca3eeb4bd41da18ad99344d68 in 2012. Test Plan: Not sure - how to test a removed check for a method which does not exist anyway? :) Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25663 --- externals/phpmailer/class.phpmailer-lite.php | 5 ----- externals/phpmailer/class.phpmailer.php | 5 ----- 2 files changed, 10 deletions(-) diff --git a/externals/phpmailer/class.phpmailer-lite.php b/externals/phpmailer/class.phpmailer-lite.php index 335625ebad..06cf5cc9c3 100644 --- a/externals/phpmailer/class.phpmailer-lite.php +++ b/externals/phpmailer/class.phpmailer-lite.php @@ -1311,11 +1311,6 @@ class PHPMailerLite { if (!is_readable($path)) { throw new phpmailerException($this->Lang('file_open') . $path, self::STOP_CONTINUE); } - if (function_exists('get_magic_quotes')) { - function get_magic_quotes() { - return false; - } - } if (PHP_VERSION < 6) { $magic_quotes = get_magic_quotes_runtime(); set_magic_quotes_runtime(0); diff --git a/externals/phpmailer/class.phpmailer.php b/externals/phpmailer/class.phpmailer.php index 69f9c45ba5..0a413a407b 100644 --- a/externals/phpmailer/class.phpmailer.php +++ b/externals/phpmailer/class.phpmailer.php @@ -1443,11 +1443,6 @@ class PHPMailer { if (!is_readable($path)) { throw new phpmailerException($this->Lang('file_open') . $path, self::STOP_CONTINUE); } - if (function_exists('get_magic_quotes')) { - function get_magic_quotes() { - return false; - } - } if (PHP_VERSION < 6) { $magic_quotes = get_magic_quotes_runtime(); set_magic_quotes_runtime(0); From a579a974ebfe8e91f98a34d2e5269a91c5307cb7 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 17 May 2024 15:56:06 +0200 Subject: [PATCH 015/118] PHPMailer: Remove use of each() function removed in PHP 8.0 Summary: `each()` has been deprecated since PHP 7.2 and got removed in PHP 8.0 per https://www.php.net/manual/en/function.each.php Replace it with a `foreach` construction. Closes T15833 Test Plan: Carefully read the code; locally testing that construction per D25659#18155; compare to similar rP9623e667458821f696eb92e9dac5a2c9720af7af Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15833 Differential Revision: https://we.phorge.it/D25659 --- externals/phpmailer/class.phpmailer-lite.php | 4 ++-- externals/phpmailer/class.phpmailer.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/externals/phpmailer/class.phpmailer-lite.php b/externals/phpmailer/class.phpmailer-lite.php index 06cf5cc9c3..61b11d99b1 100644 --- a/externals/phpmailer/class.phpmailer-lite.php +++ b/externals/phpmailer/class.phpmailer-lite.php @@ -1487,7 +1487,7 @@ class PHPMailerLite { $eol = "\r\n"; $escape = '='; $output = ''; - while( list(, $line) = each($lines) ) { + foreach ($lines as $line) { $linlen = strlen($line); $newline = ''; for($i = 0; $i < $linlen; $i++) { @@ -1518,7 +1518,7 @@ class PHPMailerLite { $newline .= $c; } // end of for $output .= $newline.$eol; - } // end of while + } // end of foreach return $output; } diff --git a/externals/phpmailer/class.phpmailer.php b/externals/phpmailer/class.phpmailer.php index 0a413a407b..001876af65 100644 --- a/externals/phpmailer/class.phpmailer.php +++ b/externals/phpmailer/class.phpmailer.php @@ -1619,7 +1619,7 @@ class PHPMailer { $eol = "\r\n"; $escape = '='; $output = ''; - while( list(, $line) = each($lines) ) { + foreach ($lines as $line) { $linlen = strlen($line); $newline = ''; for($i = 0; $i < $linlen; $i++) { @@ -1650,7 +1650,7 @@ class PHPMailer { $newline .= $c; } // end of for $output .= $newline.$eol; - } // end of while + } // end of foreach return $output; } From 67c0efdffb79d63ffcc15e07a0798f415a4e0845 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Mon, 20 May 2024 10:42:58 +0200 Subject: [PATCH 016/118] PHPMailer: Remove duplicate array keys Summary: `'doc'` is listed twice in the `$mimes` array. Remove one key. The second key was also removed in https://github.com/PHPMailer/PHPMailer/commit/6f919df65e80023fce4eab58832b6035b4a41935. Test Plan: Read/grep the code. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25666 --- externals/phpmailer/class.phpmailer-lite.php | 1 - externals/phpmailer/class.phpmailer.php | 1 - 2 files changed, 2 deletions(-) diff --git a/externals/phpmailer/class.phpmailer-lite.php b/externals/phpmailer/class.phpmailer-lite.php index 61b11d99b1..b6d34a54aa 100644 --- a/externals/phpmailer/class.phpmailer-lite.php +++ b/externals/phpmailer/class.phpmailer-lite.php @@ -1980,7 +1980,6 @@ class PHPMailerLite { 'mov' => 'video/quicktime', 'avi' => 'video/x-msvideo', 'movie' => 'video/x-sgi-movie', - 'doc' => 'application/msword', 'word' => 'application/msword', 'xl' => 'application/excel', 'eml' => 'message/rfc822' diff --git a/externals/phpmailer/class.phpmailer.php b/externals/phpmailer/class.phpmailer.php index 001876af65..b266a74d8c 100644 --- a/externals/phpmailer/class.phpmailer.php +++ b/externals/phpmailer/class.phpmailer.php @@ -2118,7 +2118,6 @@ class PHPMailer { 'mov' => 'video/quicktime', 'avi' => 'video/x-msvideo', 'movie' => 'video/x-sgi-movie', - 'doc' => 'application/msword', 'word' => 'application/msword', 'xl' => 'application/excel', 'eml' => 'message/rfc822' From 3a6e835e364aac6a6f5a23690e54e8a3cc6c22f5 Mon Sep 17 00:00:00 2001 From: Pppery Date: Sat, 25 May 2024 11:40:24 -0400 Subject: [PATCH 017/118] Fix spelling of etc. in messages per Translatewiki request Summary: Closes T15839 Test Plan: Read the code and see that it is spelled correctly Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15839 Differential Revision: https://we.phorge.it/D25670 --- src/applications/config/option/PhabricatorAWSConfigOptions.php | 2 +- .../bulk/management/PhabricatorBulkManagementExportWorkflow.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/config/option/PhabricatorAWSConfigOptions.php b/src/applications/config/option/PhabricatorAWSConfigOptions.php index a7a3a1e2ea..d610e1641c 100644 --- a/src/applications/config/option/PhabricatorAWSConfigOptions.php +++ b/src/applications/config/option/PhabricatorAWSConfigOptions.php @@ -8,7 +8,7 @@ final class PhabricatorAWSConfigOptions } public function getDescription() { - return pht('Configure integration with AWS (EC2, SES, S3, etc).'); + return pht('Configure integration with AWS (EC2, SES, S3, etc.).'); } public function getIcon() { diff --git a/src/applications/transactions/bulk/management/PhabricatorBulkManagementExportWorkflow.php b/src/applications/transactions/bulk/management/PhabricatorBulkManagementExportWorkflow.php index d80534ce73..87de9ee37b 100644 --- a/src/applications/transactions/bulk/management/PhabricatorBulkManagementExportWorkflow.php +++ b/src/applications/transactions/bulk/management/PhabricatorBulkManagementExportWorkflow.php @@ -8,7 +8,7 @@ final class PhabricatorBulkManagementExportWorkflow ->setName('export') ->setExamples('**export** [options]') ->setSynopsis( - pht('Export data to a flat file (JSON, CSV, Excel, etc).')) + pht('Export data to a flat file (JSON, CSV, Excel, etc.).')) ->setArguments( array( array( From 40b1e6b17accf502a595377dcd8908cdf6fa429c Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 28 May 2024 08:35:10 +0200 Subject: [PATCH 018/118] PHPMailer: Remove unused variable Summary: `$tmp` is declared but never used. Test Plan: Read/grep the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25665 --- externals/phpmailer/class.phpmailer-lite.php | 1 - externals/phpmailer/class.phpmailer.php | 1 - 2 files changed, 2 deletions(-) diff --git a/externals/phpmailer/class.phpmailer-lite.php b/externals/phpmailer/class.phpmailer-lite.php index b6d34a54aa..033f68ec0b 100644 --- a/externals/phpmailer/class.phpmailer-lite.php +++ b/externals/phpmailer/class.phpmailer-lite.php @@ -2048,7 +2048,6 @@ class PHPMailerLite { * @param string $key_pass Password for private key */ public function DKIM_QP($txt) { - $tmp=""; $line=""; for ($i=0;$i Date: Tue, 21 May 2024 18:46:31 +0200 Subject: [PATCH 019/118] Improve error passing task IDs as URL parameter in invalid format Summary: Maniphest expects the values of the `ids` URL parameter to be integers. Example: http://phorge.localhost/maniphest/?ids=1,2,3,4 to show a list of tasks with those IDs. When passing monograms instead (`T` prefix, like `T123` instead of `123`), a cryptic message `Expected a numeric scalar or null for %Ld conversion` is shown. Thus check if `$this->taskIDs` consists of integers only; if not throw a `PhutilSearchQueryCompilerSyntaxException` with an explanatory error message. Closes T15838 Test Plan: Go to http://phorge.localhost/maniphest/?ids=T1,T2,T3,T4 before and after applying the patch. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15838 Differential Revision: https://we.phorge.it/D25669 --- src/applications/maniphest/query/ManiphestTaskQuery.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index c206bd6599..7947981ec7 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -358,6 +358,10 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { $where[] = $this->buildOwnerWhereClause($conn); if ($this->taskIDs !== null) { + if (!ctype_digit(implode('', $this->taskIDs))) { + throw new PhutilSearchQueryCompilerSyntaxException( + pht('Task IDs must be integer numbers.')); + } $where[] = qsprintf( $conn, 'task.id in (%Ld)', From d9e543ba970bf2c5fdeb400140ea3d31711cd575 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 30 May 2024 15:38:57 +0200 Subject: [PATCH 020/118] PHPMailer: Set defaults for required parameters followed by optional parameters in Authorise() method signature Summary: Required parameters $password and $username follow the optional parameter $tval. This is deprecated since PHP 8.0 per https://www.php.net/manual/en/migration80.deprecated.php Define defaults for those required parameters to fix the issue - https://github.com/PHPMailer/PHPMailer/blob/master/src/POP3.php#L199 seems to also have survived so far without complaints by PHP 8 users (and PHPStan also does not complain anymore after this change). Closes T15834 Test Plan: Carefully read the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15834 Differential Revision: https://we.phorge.it/D25660 --- externals/phpmailer/class.pop3.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/externals/phpmailer/class.pop3.php b/externals/phpmailer/class.pop3.php index c48191bd72..b4a261cfcc 100644 --- a/externals/phpmailer/class.pop3.php +++ b/externals/phpmailer/class.pop3.php @@ -142,7 +142,7 @@ class POP3 { * @param string $username * @param string $password */ - public function Authorise ($host, $port = false, $tval = false, $username, $password, $debug_level = 0) { + public function Authorise ($host, $port = false, $tval = false, $username = '', $password = '', $debug_level = 0) { $this->host = $host; // If no port value is passed, retrieve it From 1500a5709067b308158606daa7d5217e1ed495d4 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Mon, 3 Jun 2024 11:25:34 +0200 Subject: [PATCH 021/118] Remove unused method shouldCopyWhenCreatingSimilarTask() Summary: Nothing in the codebase calls or reads this function. If it was ever used in some implementation of a custom field, then it was likely in context of the `Similar Task` functionality removed in 52f7446eeaa36ee1716fd1ed6e90b26b964e11ac. Test Plan: Grep the codebase. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25677 --- .../maniphest/field/ManiphestCustomField.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/applications/maniphest/field/ManiphestCustomField.php b/src/applications/maniphest/field/ManiphestCustomField.php index bbd97babf4..26c18cbb8c 100644 --- a/src/applications/maniphest/field/ManiphestCustomField.php +++ b/src/applications/maniphest/field/ManiphestCustomField.php @@ -15,17 +15,4 @@ abstract class ManiphestCustomField return new ManiphestCustomFieldNumericIndex(); } - /** - * When the user creates a task, the UI prompts them to "Create another - * similar task". This copies some fields (e.g., Owner and CCs) but not other - * fields (e.g., description). If this custom field should also be copied, - * return true from this method. - * - * @return bool True to copy the default value from the template task when - * creating a new similar task. - */ - public function shouldCopyWhenCreatingSimilarTask() { - return false; - } - } From 40720ab83d0148fab28eba8218ae5fdc4b3e01f9 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Mon, 3 Jun 2024 13:34:49 +0200 Subject: [PATCH 022/118] Expose initially set values of Maniphest custom date field and point values when removing them Summary: After removing or resetting a custom date field value or points value initially set at task creation there is no way to find and restore the previous value. As Phorge adds a transaction entry anyway, include the previous value in the transaction title. This makes it easier to revert an accidental removal of such data and is also more aligned with the behavior of other custom field types. Closes T15846 Test Plan: * Define a custom date field in Maniphest via http://phorge.localhost/config/edit/maniphest.custom-field-definitions/ and also enable http://phorge.localhost/config/edit/maniphest.points/ * Create a task with those fields set to non-custom values via a create task form which allows editing/changing these values * Edit the task and remove/reset the values of these custom fields * Look at the task and its transaction entries Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15846 Differential Revision: https://we.phorge.it/D25678 --- .../maniphest/xaction/ManiphestTaskPointsTransaction.php | 5 +++++ .../standard/PhabricatorStandardCustomFieldDate.php | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php index 3b24dff590..fae125dccd 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php @@ -33,6 +33,11 @@ final class ManiphestTaskPointsTransaction '%s set the point value for this task to %s.', $this->renderAuthor(), $this->renderNewValue()); + } else if ($new === null && $old !== null) { + return pht( + '%s removed the point value %s for this task.', + $this->renderAuthor(), + $this->renderOldValue()); } else if ($new === null) { return pht( '%s removed the point value for this task.', diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php index 5988970ca6..c437df82fb 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php @@ -177,6 +177,12 @@ final class PhabricatorStandardCustomFieldDate $xaction->renderHandleLink($author_phid), $this->getFieldName(), $new_date); + } else if (!$new && $old) { + return pht( + '%s removed %s which was set to %s.', + $xaction->renderHandleLink($author_phid), + $this->getFieldName(), + $old_date); } else if (!$new) { return pht( '%s removed %s.', From 89dcbe125c6c9d6f92b1c01dd1dda86d465b027f Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Tue, 4 Jun 2024 01:44:41 +0200 Subject: [PATCH 023/118] Panels list: fix missing welcome page Summary: The method getNewUserBody() was never designed for a list controller. The method was just orphan. Now, instead, it's non-orphan, and it works as expected. This bug was highlighted by Andre Klapper, since their linter was screaming about inconsistences in the current nonsense usage of getNewUserBody(): D25649 The code was just moved from the list controller, to the search engine. - PhabricatorDashboardPanelListController (from) - PhabricatorDashboardPanelSearchEngine (to) Bonus point: Adjusted the icon and the title to talk about "Panels" and not "Dashboards". Added also some inline documentation with an hint about `?nux=1`. So now we can easily remember how to test this kind of things. Closes T15844 Test Plan: In both these pages, you finally see a welcome message, not just in Maniphest: - http://phorge.localhost/dashboard/panel/?nux=1 - http://phorge.localhost/maniphest/?nux=1 In both these pages, if you have no elements, you finally see a welcome message, and not just in Maniphest: - http://phorge.localhost/dashboard/panel/ - http://phorge.localhost/maniphest/ Check that the welcome message helps you in creating a new Panel, and that talks about Panels, and that have the lovely icon of a Panel. Reviewers: aklapper, O1 Blessed Committers Reviewed By: aklapper, O1 Blessed Committers Subscribers: tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15844 Differential Revision: https://we.phorge.it/D25674 --- ...habricatorDashboardPanelListController.php | 19 ------------------- .../PhabricatorDashboardPanelSearchEngine.php | 18 ++++++++++++++++++ .../PhabricatorApplicationSearchEngine.php | 12 ++++++++++++ 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelListController.php b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelListController.php index 1eab148261..c9cefca707 100644 --- a/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelListController.php +++ b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelListController.php @@ -48,23 +48,4 @@ final class PhabricatorDashboardPanelListController return $crumbs; } - protected function getNewUserBody() { - $create_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Create a Panel')) - ->setHref('/dashboard/panel/edit/') - ->setColor(PHUIButtonView::GREEN); - - $icon = $this->getApplication()->getIcon(); - $app_name = $this->getApplication()->getName(); - $view = id(new PHUIBigInfoView()) - ->setIcon($icon) - ->setTitle(pht('Welcome to %s', $app_name)) - ->setDescription( - pht('Build individual panels to display on your homepage dashboard.')) - ->addAction($create_button); - - return $view; - } - } diff --git a/src/applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php b/src/applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php index be59345aaa..c4b30652b9 100644 --- a/src/applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php +++ b/src/applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php @@ -147,4 +147,22 @@ final class PhabricatorDashboardPanelSearchEngine return $result; } + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Panel')) + ->setHref('/dashboard/panel/edit/') + ->setColor(PHUIButtonView::GREEN); + + $app_name = pht('Panels'); + $view = id(new PHUIBigInfoView()) + ->setIcon('fa-line-chart') + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht('Build individual panels to display on your homepage dashboard.')) + ->addAction($create_button); + + return $view; + } + } diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index 9094ead227..489dd08065 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -1454,6 +1454,12 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { return $attachments; } + /** + * Render a content body (if available) to onboard new users. + * This body is usually visible when you have no elements in a list, + * or when you force the rendering on a list with the `?nux=1` URL. + * @return wild|PhutilSafeHTML|null + */ final public function renderNewUserView() { $body = $this->getNewUserBody(); @@ -1464,6 +1470,12 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { return $body; } + /** + * Get a content body to onboard new users. + * Traditionally this content is shown from an empty list, to explain + * what a certain entity does, and how to create a new one. + * @return wild|PhutilSafeHTML|null + */ protected function getNewUserHeader() { return null; } From 5a40739451c1b3073128c7f472e5c2c7edaca534 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Tue, 4 Jun 2024 15:57:20 +0200 Subject: [PATCH 024/118] Show login page if a search token requires a valid viewer Summary: A saved query can have tokens that require a valid current viewer. For example, this token: viewer() Before this change, visiting such saved queries would cause this: This datasource ("PhabricatorPeopleUserFunctionDatasource") can not evaluate the function "viewer(...)". After this change, instead of that, you are just redirected to the login page, so, after you do the login, you are redirected back to that saved query and it works. This fix was boosted during the Wikimedia Hackaton (wmhack) in Tallinn. Thanks Tallinn! https://phabricator.wikimedia.org/T356384 Fixes T15704 Test Plan: Go to Maniphest > Advanced Search > Assigned to > "Viewer". It still works. Visit the same page in a new anonymous tab: now it redirects to the login page. You login, and that page works again. Do the same specific test for all these cases: - Maniphest - Assigned To: viewer - Tags: current Viewer's Projects - Authors: viewer - Subscribers: ... - Closed by - Badges - Subscribers - Differential - Responsible Users - Authors - Reviewers - Subscribers - Tags - Dashboards - Authored By - Tags - Dashboard Panels - Authored By - Dashboard Portals - Tags - Calendar: - Hosts - Invited - Subscribers - Tags - Countdown - Authors - Diffusion - Tags - Subscribers - Tags - Diffusion commit - Responsible Users - Authors - Subscribers - Tags - Diffusion identities - Matching Users - Feed - Include Users - Include Projects (interestingly it does not support "current Viewer's Projects") - Files - Authors - Herald - Authors - Subscribers - Legalpad - Subscribers - Nuance (none of their entity support search by token) - Passphrase - Subscribers - Paste - Authors - Subscribers - Tags - Phame - Subscribers - Tags - Pholio - Authors - Subscribers - Tags - Phrequent - Users (interestingly it does not support "viewer") - Ponder - Authors - Answered By - Projects - Members - Watchers - Transactions - /feed/transactions/ - Authors - General search at /search/query/ - Authors - Owners - Subscribers - Tags All the above fields were tested in a clean search, one at a time, both logged-in and logged-out, with the function "viewer" or anything similar like "current Viewer's Projects": For all cases, the login page appeared successfully where needed, instead of a crash. Reviewers: O1 Blessed Committers, aklapper Reviewed By: O1 Blessed Committers, aklapper Subscribers: aklapper, avivey, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15704 Differential Revision: https://we.phorge.it/D25621 --- src/__phutil_library_map__.php | 2 ++ ...alendarInviteeViewerFunctionDatasource.php | 8 ++++-- ...ialResponsibleViewerFunctionDatasource.php | 8 ++++-- .../typeahead/PhabricatorViewerDatasource.php | 8 ++++-- ...bricatorProjectLogicalViewerDatasource.php | 8 ++++-- ...PhabricatorApplicationSearchController.php | 9 +++++++ ...habricatorTypeaheadCompositeDatasource.php | 9 +++++++ .../PhabricatorTypeaheadDatasource.php | 25 +++++++++++++++++++ ...ricatorTypeaheadLoginRequiredException.php | 6 +++++ 9 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 src/applications/typeahead/exception/PhabricatorTypeaheadLoginRequiredException.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 02e4c7f2b3..9287085d03 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5078,6 +5078,7 @@ phutil_register_library_map(array( 'PhabricatorTypeaheadDatasourceTestCase' => 'applications/typeahead/datasource/__tests__/PhabricatorTypeaheadDatasourceTestCase.php', 'PhabricatorTypeaheadFunctionHelpController' => 'applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php', 'PhabricatorTypeaheadInvalidTokenException' => 'applications/typeahead/exception/PhabricatorTypeaheadInvalidTokenException.php', + 'PhabricatorTypeaheadLoginRequiredException' => 'applications/typeahead/exception/PhabricatorTypeaheadLoginRequiredException.php', 'PhabricatorTypeaheadModularDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php', 'PhabricatorTypeaheadMonogramDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php', 'PhabricatorTypeaheadProxyDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadProxyDatasource.php', @@ -11819,6 +11820,7 @@ phutil_register_library_map(array( 'PhabricatorTypeaheadDatasourceTestCase' => 'PhabricatorTestCase', 'PhabricatorTypeaheadFunctionHelpController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadInvalidTokenException' => 'Exception', + 'PhabricatorTypeaheadLoginRequiredException' => 'Exception', 'PhabricatorTypeaheadModularDatasourceController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadMonogramDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorTypeaheadProxyDatasource' => 'PhabricatorTypeaheadCompositeDatasource', diff --git a/src/applications/calendar/typeahead/PhabricatorCalendarInviteeViewerFunctionDatasource.php b/src/applications/calendar/typeahead/PhabricatorCalendarInviteeViewerFunctionDatasource.php index d653c9bdea..ef8cb76dc1 100644 --- a/src/applications/calendar/typeahead/PhabricatorCalendarInviteeViewerFunctionDatasource.php +++ b/src/applications/calendar/typeahead/PhabricatorCalendarInviteeViewerFunctionDatasource.php @@ -28,8 +28,12 @@ final class PhabricatorCalendarInviteeViewerFunctionDatasource ); } + protected function isFunctionWithLoginRequired($function) { + return true; + } + public function loadResults() { - if ($this->getViewer()->getPHID()) { + if ($this->getViewer()->isLoggedIn()) { $results = array($this->renderViewerFunctionToken()); } else { $results = array(); @@ -39,7 +43,7 @@ final class PhabricatorCalendarInviteeViewerFunctionDatasource } protected function canEvaluateFunction($function) { - if (!$this->getViewer()->getPHID()) { + if (!$this->getViewer()->isLoggedIn()) { return false; } diff --git a/src/applications/differential/typeahead/DifferentialResponsibleViewerFunctionDatasource.php b/src/applications/differential/typeahead/DifferentialResponsibleViewerFunctionDatasource.php index 05ae39ec2e..4d735b0c94 100644 --- a/src/applications/differential/typeahead/DifferentialResponsibleViewerFunctionDatasource.php +++ b/src/applications/differential/typeahead/DifferentialResponsibleViewerFunctionDatasource.php @@ -28,8 +28,12 @@ final class DifferentialResponsibleViewerFunctionDatasource ); } + protected function isFunctionWithLoginRequired($function) { + return true; + } + public function loadResults() { - if ($this->getViewer()->getPHID()) { + if ($this->getViewer()->isLoggedIn()) { $results = array($this->renderViewerFunctionToken()); } else { $results = array(); @@ -39,7 +43,7 @@ final class DifferentialResponsibleViewerFunctionDatasource } protected function canEvaluateFunction($function) { - if (!$this->getViewer()->getPHID()) { + if (!$this->getViewer()->isLoggedIn()) { return false; } diff --git a/src/applications/people/typeahead/PhabricatorViewerDatasource.php b/src/applications/people/typeahead/PhabricatorViewerDatasource.php index 6f6d1181fe..cb367ccd4d 100644 --- a/src/applications/people/typeahead/PhabricatorViewerDatasource.php +++ b/src/applications/people/typeahead/PhabricatorViewerDatasource.php @@ -34,8 +34,12 @@ final class PhabricatorViewerDatasource ); } + protected function isFunctionWithLoginRequired($function) { + return true; + } + public function loadResults() { - if ($this->getViewer()->getPHID()) { + if ($this->getViewer()->isLoggedIn()) { $results = array($this->renderViewerFunctionToken()); } else { $results = array(); @@ -45,7 +49,7 @@ final class PhabricatorViewerDatasource } protected function canEvaluateFunction($function) { - if (!$this->getViewer()->getPHID()) { + if (!$this->getViewer()->isLoggedIn()) { return false; } diff --git a/src/applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php b/src/applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php index 9d1a91376a..acd4ea8aa7 100644 --- a/src/applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php @@ -35,8 +35,12 @@ final class PhabricatorProjectLogicalViewerDatasource ); } + protected function isFunctionWithLoginRequired($function) { + return true; + } + public function loadResults() { - if ($this->getViewer()->getPHID()) { + if ($this->getViewer()->isLoggedIn()) { $results = array($this->renderViewerProjectsFunctionToken()); } else { $results = array(); @@ -46,7 +50,7 @@ final class PhabricatorProjectLogicalViewerDatasource } protected function canEvaluateFunction($function) { - if (!$this->getViewer()->getPHID()) { + if (!$this->getViewer()->isLoggedIn()) { return false; } diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index d8f53538e4..5178a1a1d9 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -346,6 +346,15 @@ final class PhabricatorApplicationSearchController $body[] = $pager_box; } } + } catch (PhabricatorTypeaheadLoginRequiredException $ex) { + + // A specific token requires login. Show login page. + $auth_class = PhabricatorAuthApplication::class; + $auth_application = PhabricatorApplication::getByClass($auth_class); + $login_controller = new PhabricatorAuthStartController(); + $this->setCurrentApplication($auth_application); + return $this->delegateToController($login_controller); + } catch (PhabricatorTypeaheadInvalidTokenException $ex) { $exec_errors[] = pht( 'This query specifies an invalid parameter. Review the '. diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php index 8a396c1ed3..338c4db564 100644 --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php @@ -304,6 +304,15 @@ abstract class PhabricatorTypeaheadCompositeDatasource return parent::evaluateFunction($function, $argv); } + protected function isFunctionWithLoginRequired($function) { + foreach ($this->getUsableDatasources() as $source) { + if ($source->isFunctionWithLoginRequired($function)) { + return true; + } + } + return parent::isFunctionWithLoginRequired($function); + } + public function renderFunctionTokens($function, array $argv_list) { foreach ($this->getUsableDatasources() as $source) { if ($source->canEvaluateFunction($function)) { diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php index a35a8e8f0f..308a8bfe1a 100644 --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php @@ -365,6 +365,19 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject { } + /** + * Check if this datasource requires a logged-in viewer. + * @task functions + * @param string $function Function name. + * @return bool + */ + protected function isFunctionWithLoginRequired($function) { + // This is just a default. + // Make sure to override this method to require login. + return false; + } + + /** * @task functions */ @@ -498,6 +511,18 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject { if (!$this->canEvaluateFunction($function)) { if (!$allow_partial) { + + if ($this->isFunctionWithLoginRequired($function)) { + if (!$this->getViewer() || !$this->getViewer()->isLoggedIn()) { + throw new PhabricatorTypeaheadLoginRequiredException( + pht( + 'This datasource ("%s") requires to be logged-in to use the '. + 'function "%s(...)".', + get_class($this), + $function)); + } + } + throw new PhabricatorTypeaheadInvalidTokenException( pht( 'This datasource ("%s") can not evaluate the function "%s(...)".', diff --git a/src/applications/typeahead/exception/PhabricatorTypeaheadLoginRequiredException.php b/src/applications/typeahead/exception/PhabricatorTypeaheadLoginRequiredException.php new file mode 100644 index 0000000000..3b29df5df8 --- /dev/null +++ b/src/applications/typeahead/exception/PhabricatorTypeaheadLoginRequiredException.php @@ -0,0 +1,6 @@ + Date: Tue, 4 Jun 2024 16:14:43 -0400 Subject: [PATCH 025/118] Remove broken link to PhutilHighIntensityIntervalDaemon docs Summary: Closes T15845 Test Plan: Read the code - this changes nothing other than comments Reviewers: O1 Blessed Committers, aklapper Reviewed By: O1 Blessed Committers, aklapper Subscribers: aklapper, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15845 Differential Revision: https://we.phorge.it/D25680 --- src/infrastructure/daemon/PhutilDaemon.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/infrastructure/daemon/PhutilDaemon.php b/src/infrastructure/daemon/PhutilDaemon.php index 701b3d7a27..ccc1a02fec 100644 --- a/src/infrastructure/daemon/PhutilDaemon.php +++ b/src/infrastructure/daemon/PhutilDaemon.php @@ -31,9 +31,6 @@ * into an autoscale pool, this will cause the pool to automatically scale up * when busy and down when idle. * - * See @{class:PhutilHighIntensityIntervalDaemon} for an example of a simple - * autoscaling daemon. - * * Launching a daemon which does not make these callbacks into an autoscale * pool will have no effect. * From a8af0c6e511943e1e476eed9ea94d7fba8b5e3f6 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 4 Jun 2024 11:33:11 +0200 Subject: [PATCH 026/118] Remove unused variable from HeraldEngine Summary: `$activeRule` is not used anywhere. Test Plan: Grep the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25679 --- src/applications/herald/engine/HeraldEngine.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/applications/herald/engine/HeraldEngine.php b/src/applications/herald/engine/HeraldEngine.php index 91a2050779..43c3197e1a 100644 --- a/src/applications/herald/engine/HeraldEngine.php +++ b/src/applications/herald/engine/HeraldEngine.php @@ -3,7 +3,6 @@ final class HeraldEngine extends Phobject { protected $rules = array(); - protected $activeRule; protected $transcript; private $fieldCache = array(); From 98884f758a29ae5544f36940c86b20cf9455f202 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 16 May 2024 13:02:58 +0200 Subject: [PATCH 027/118] Fix call to non-existing newIcons() in PhabricatorIconSet Summary: `PhabricatorIconSet::getIcons()` calls `$this->newIcons()` which is undefined. As all six child classes which extend `PhabricatorIconSet` implement `newIcons()`, declare an abstract function in their parent class. Test Plan: Carefully read the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25647 --- src/applications/files/iconset/PhabricatorIconSet.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/applications/files/iconset/PhabricatorIconSet.php b/src/applications/files/iconset/PhabricatorIconSet.php index baf5422375..c31c3e11fc 100644 --- a/src/applications/files/iconset/PhabricatorIconSet.php +++ b/src/applications/files/iconset/PhabricatorIconSet.php @@ -3,6 +3,8 @@ abstract class PhabricatorIconSet extends Phobject { + abstract protected function newIcons(); + final public function getIconSetKey() { return $this->getPhobjectClassConstant('ICONSETKEY'); } From 5f565e2ab665bf68d8e491a035785fc3d6f3b9e3 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 17 May 2024 10:57:19 +0200 Subject: [PATCH 028/118] Correct PHPDoc return value for loadObject() in PhabricatorObjectMailReceiver Summary: The PHPDoc for the abstract function `loadObject()` defines `@return void`, however the function's return value is used twice in `PhabricatorObjectMailReceiver` and all 17 `loadObject()` implementations in child classes return an object provided by some type of `PhabricatorCursorPagedPolicyAwareQuery`. Thus correct the PHPDoc to make static code analyzers happier. Test Plan: Carefully read and compare the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25654 --- .../metamta/receiver/PhabricatorObjectMailReceiver.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php b/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php index 0342a94a60..65c6089f3b 100644 --- a/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php +++ b/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php @@ -22,7 +22,8 @@ abstract class PhabricatorObjectMailReceiver extends PhabricatorMailReceiver { * @param string A string matched by @{method:getObjectPattern} * fragment. * @param PhabricatorUser The viewing user. - * @return void + * @return object|null The object to receive mail, or null if no such + * object exists. */ abstract protected function loadObject($pattern, PhabricatorUser $viewer); From cb934602c2e8c7da4b1c15793fe2511fe47c108d Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Wed, 5 Jun 2024 13:43:49 +0200 Subject: [PATCH 029/118] Fix PHP 8.1 "strlen(null)" exception creating a Portal Summary: `strlen()` was used in Phabricator to check if a generic value is a non-empty string. This behavior is deprecated since PHP 8.1. Phorge adopts `phutil_nonempty_string()` as a replacement. Note: this may highlight other absurd input values that might be worth correcting instead of just ignoring. If phutil_nonempty_string() throws an exception in your instance, report it to Phorge to evaluate and fix that specific corner case. ``` ERROR 8192: strlen(): Passing null to parameter #1 ($string) of type string is deprecated at [/var/www/html/phorge/phorge/src/applications/dashboard/menuitem/PhabricatorDashboardPortalMenuItem.php:34] ``` Closes T15847 Test Plan: Go to `/portal/edit/form/default/`, set a name and click `Create Portal`. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15847 Differential Revision: https://we.phorge.it/D25681 --- .../dashboard/menuitem/PhabricatorDashboardPortalMenuItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/dashboard/menuitem/PhabricatorDashboardPortalMenuItem.php b/src/applications/dashboard/menuitem/PhabricatorDashboardPortalMenuItem.php index 6d47a4c219..9c5aa8ddc2 100644 --- a/src/applications/dashboard/menuitem/PhabricatorDashboardPortalMenuItem.php +++ b/src/applications/dashboard/menuitem/PhabricatorDashboardPortalMenuItem.php @@ -31,7 +31,7 @@ final class PhabricatorDashboardPortalMenuItem PhabricatorProfileMenuItemConfiguration $config) { $name = $config->getMenuItemProperty('name'); - if (strlen($name)) { + if (phutil_nonempty_string($name)) { return $name; } From d8cd959622da59318adf13f6ea59c660053838c0 Mon Sep 17 00:00:00 2001 From: Pppery Date: Wed, 5 Jun 2024 22:25:53 -0400 Subject: [PATCH 030/118] Fix a leftover misspelled "Phabricator" reference Summary: Closes T15850 Test Plan: Read the code and see that there isn't a typo anymore Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15850 Differential Revision: https://we.phorge.it/D25682 --- .../auth/controller/PhabricatorAuthUnlinkController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php index a4843a7ccd..af6732a796 100644 --- a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php +++ b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php @@ -102,7 +102,7 @@ final class PhabricatorAuthUnlinkController ->addHiddenInput('confirmations', implode(',', $confirmations)) ->appendParagraph( pht( - 'This is the only external login account linked to your Phabicator '. + 'This is the only external login account linked to your '. 'account. If you remove it, you may no longer be able to log in.')) ->appendParagraph( pht( From cb5a72b5e6d151d650efdd1a552f5068c83fd1b6 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 17 May 2024 10:27:55 +0200 Subject: [PATCH 031/118] Fix call to non-existing getDisplayName() in DiffusionRepositoryClusterEngine.php Summary: `DiffusionRepositoryClusterEngine` calls `$this->getDisplayName()` which is not defined in this class or its parent class. Looking at the variable use in the companying error message, getting the name of the `$repository` was intended instead. Test Plan: Carefully read the code and check available methods in its parent classes. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25653 --- .../diffusion/protocol/DiffusionRepositoryClusterEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php index dfc86e5f56..0b2f40c2d6 100644 --- a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php +++ b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php @@ -586,7 +586,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { 'for %s seconds(s). This repository will be frozen.', $this->clusterWriteOwner, $device->getName(), - $this->getDisplayName(), + $repository->getDisplayName(), new PhutilNumber($duration))); } From 3349c3be17c7ae540a228c87db41cc4f119af3f6 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 17 May 2024 11:08:32 +0200 Subject: [PATCH 032/118] Fix call to non-existing newWorkCommands() in NuanceItemType Summary: rPe1b8532e2478395bd1f36baecb998adeef5bea3d introduced an implementation of `newWorkCommands()` in the child class `NuanceFormItemType` which is called in parent `NuanceItemType::buildWorkCommands()` without being defined in that parent class. Thus define `newWorkCommands()` as an abstract class in parent class `NuanceItemType` and add an implementation returning null in the other child class `NuanceGitHubEventItemType` to make static code analyzers happy. Test Plan: Carefully read the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25655 --- src/applications/nuance/item/NuanceGitHubEventItemType.php | 4 ++++ src/applications/nuance/item/NuanceItemType.php | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/applications/nuance/item/NuanceGitHubEventItemType.php b/src/applications/nuance/item/NuanceGitHubEventItemType.php index 1f6249f222..9e8f902a16 100644 --- a/src/applications/nuance/item/NuanceGitHubEventItemType.php +++ b/src/applications/nuance/item/NuanceGitHubEventItemType.php @@ -20,6 +20,10 @@ final class NuanceGitHubEventItemType return $this->newRawEvent($item)->getEventFullTitle(); } + protected function newWorkCommands(NuanceItem $item) { + return array(); + } + public function canUpdateItems() { return true; } diff --git a/src/applications/nuance/item/NuanceItemType.php b/src/applications/nuance/item/NuanceItemType.php index 3a65ad0195..245e11187d 100644 --- a/src/applications/nuance/item/NuanceItemType.php +++ b/src/applications/nuance/item/NuanceItemType.php @@ -6,6 +6,8 @@ abstract class NuanceItemType private $viewer; private $controller; + abstract protected function newWorkCommands(NuanceItem $item); + public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; From 17b568b94f112b750dcc361ec076709f2c8f49c0 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 18 Apr 2024 19:50:03 +0200 Subject: [PATCH 033/118] Fix PHP 8.1 "strlen(null)" exceptions editing existing Dashboard query panel with no query defined Summary: `strlen()` was used in Phabricator to check if a generic value is a non-empty string. This behavior is deprecated since PHP 8.1. Phorge adopts `phutil_nonempty_string()` as a replacement. Note: this may highlight other absurd input values that might be worth correcting instead of just ignoring. If phutil_nonempty_string() throws an exception in your instance, report it to Phorge to evaluate and fix that specific corner case. ``` ERROR 8192: strlen(): Passing null to parameter #1 ($string) of type string is deprecated at [/var/www/html/phorge/phorge/src/applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php:41] ``` ``` ERROR 8192: strlen(): Passing null to parameter #1 ($string) of type string is deprecated at [/var/www/html/phorge/phorge/src/applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php:59] ``` Closes T15791 Test Plan: Successfully create a Dashboard query panel searching for "Diffusion Raw Commits" (which has no Query defined per T15790) and try to edit it afterwards under PHP 8.1 or later. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15791 Differential Revision: https://we.phorge.it/D25596 --- .../PhabricatorDashboardQueryPanelQueryEditField.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php b/src/applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php index 58192c0eee..3be70dc952 100644 --- a/src/applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php +++ b/src/applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php @@ -38,7 +38,7 @@ final class PhabricatorDashboardQueryPanelQueryEditField } } - if (strlen($value) && !$seen) { + if (phutil_nonempty_string($value) && !$seen) { $name = pht('Custom Query ("%s")', $value); } else { $name = pht('(None)'); @@ -56,7 +56,7 @@ final class PhabricatorDashboardQueryPanelQueryEditField 'queryID' => $control_id, 'options' => $queries, 'value' => array( - 'key' => strlen($value) ? $value : null, + 'key' => phutil_nonempty_string($value) ? $value : null, 'name' => $name, ), )); From d2bca2f32da0fdc5300707fbdfa42467b70d3f94 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 16 May 2024 12:24:44 +0200 Subject: [PATCH 034/118] Fix call to non-existing getTransactionCommentForSave() in PhabricatorInlineComment Summary: rPb48a22bf50a32895566e9aa66c1d7aff87a492b8 moved the call `$this->getTransactionCommentForSave()->save()` from within `PhabricatorAuditInlineComment` to the new (parent) class `PhabricatorInlineComment.php`, however the later does not define this function. As the two child classes which extend `PhabricatorInlineComment` both implement `getTransactionCommentForSave()`, declare an abstract function in their parent class. Test Plan: Carefully read the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25644 --- .../diff/interface/PhabricatorInlineComment.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/infrastructure/diff/interface/PhabricatorInlineComment.php b/src/infrastructure/diff/interface/PhabricatorInlineComment.php index 613dfb08aa..4e7b86d30a 100644 --- a/src/infrastructure/diff/interface/PhabricatorInlineComment.php +++ b/src/infrastructure/diff/interface/PhabricatorInlineComment.php @@ -101,6 +101,11 @@ abstract class PhabricatorInlineComment abstract public function supportsHiding(); abstract public function isHidden(); + /** + * @return Phobject + */ + abstract public function getTransactionCommentForSave(); + public function isDraft() { return !$this->getTransactionPHID(); } From a83ba95445e18e9d98d4dd120d38ca713df25fba Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 7 Jun 2024 18:43:04 +0200 Subject: [PATCH 035/118] Only display "Calendar" entry in user profile page menu on mobile when Calendar application is installed Summary: https://we.phorge.it/source/phorge/browse/master/src/view/page/PhabricatorStandardPageView.php$908 tries to "find some navigational menu items to create a mobile navigation menu from" but does not succeed too much it seems: On a user profile page, that very code calls `buildApplicationMenu()` in `PhabricatorPeopleController` which calls `buildSideNavView()` in the same class. This code unconditionally displays a "Calendar" menu item, no matter if the Calendar application is installed or not. Thus check first to avoid offering a link ending up in a 404. For the time being this change renders the menu empty (apart from a link to the page we are already on) but that's more acceptable than all those tears shed by devastated users after clicking the Calendar menu item while being excited and full of anticipation, just to end up on a 404 page instead. Related: T15224 Test Plan: * As an admin, install/uninstall Calendar prototype application via http://phorge.localhost/applications/view/PhabricatorCalendarApplication/ * Go to a user profile page like http://phorge.localhost/username/ on a screen with 920px or less width and click the hamburger menu item in the upper right corner. Check/click the "Calendar" entry. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25683 --- .../people/controller/PhabricatorPeopleController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/applications/people/controller/PhabricatorPeopleController.php b/src/applications/people/controller/PhabricatorPeopleController.php index c2c262f9f4..b4e7e4a8df 100644 --- a/src/applications/people/controller/PhabricatorPeopleController.php +++ b/src/applications/people/controller/PhabricatorPeopleController.php @@ -16,7 +16,9 @@ abstract class PhabricatorPeopleController extends PhabricatorController { if ($name) { $nav->setBaseURI(new PhutilURI('/p/')); $nav->addFilter("{$name}/", $name); - $nav->addFilter("{$name}/calendar/", pht('Calendar')); + if (id(new PhabricatorCalendarApplication())->isInstalled()) { + $nav->addFilter("{$name}/calendar/", pht('Calendar')); + } } } From 10cb2521630aa333b76ff35634b14a793db85f79 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Mon, 10 Jun 2024 19:12:40 +0200 Subject: [PATCH 036/118] Fix PHP 8.1 exceptions importing ICS file without attendee names Summary: `strlen()` was used in Phabricator to check if a generic value is a non-empty string. This behavior is deprecated since PHP 8.1. Phorge adopts `phutil_nonempty_string()` as a replacement. Passing `null` to `preg_match()` is deprecated behavior since PHP 8.1. Thus only call `preg_match()` when the value is set. Note: this may highlight other absurd input values that might be worth correcting instead of just ignoring. If phutil_nonempty_string() throws an exception in your instance, report it to Phorge to evaluate and fix that specific corner case. ``` ERROR 8192: preg_match(): Passing null to parameter #2 ($subject) of type string is deprecated at [/var/www/html/phorge/phorge/src/applications/calendar/import/PhabricatorCalendarImportEngine.php:238] ``` ``` ERROR 8192: strlen(): Passing null to parameter #1 ($string) of type string is deprecated at [/var/www/html/phorge/phorge/src/applications/calendar/import/PhabricatorCalendarImportEngine.php:245] ``` Closes T15852 Test Plan: Import an ICS file which lists attendees without a name but only with an email address into the calendar via http://phorge.localhost/calendar/import/ > "Import Events" > "Import .ics File" Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15852 Differential Revision: https://we.phorge.it/D25686 --- .../calendar/import/PhabricatorCalendarImportEngine.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/calendar/import/PhabricatorCalendarImportEngine.php b/src/applications/calendar/import/PhabricatorCalendarImportEngine.php index 626757c61d..33809f1516 100644 --- a/src/applications/calendar/import/PhabricatorCalendarImportEngine.php +++ b/src/applications/calendar/import/PhabricatorCalendarImportEngine.php @@ -235,14 +235,14 @@ abstract class PhabricatorCalendarImportEngine // We avoid disclosing email addresses to be consistent with the rest // of the product. $name = $attendee->getName(); - if (preg_match('/@/', $name)) { + if (phutil_nonempty_string($name) && preg_match('/@/', $name)) { $name = new PhutilEmailAddress($name); $name = $name->getDisplayName(); } // If we don't have a name or the name still looks like it's an // email address, give them a dummy placeholder name. - if (!strlen($name) || preg_match('/@/', $name)) { + if (!phutil_nonempty_string($name) || preg_match('/@/', $name)) { $name = pht('Private User %d', $private_index); $private_index++; } From c1d86da5b2098fed53ca6517617fcacd55aadf81 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 16 May 2024 11:45:12 +0200 Subject: [PATCH 037/118] Initialize $cache_key variable in CelerityResourceController.php Summary: `$cache_key` is unconditionally called in `$cache->setKey($cache_key, $data)` but is only defined if the previous condition `if ($is_cacheable && $is_locally_cacheable && !$dev_mode)` was true. Thus initialize the variable to avoid a theoretical exception. (No additional null check is needed as `$cache` gets defined in the same condition.) Test Plan: Carefully read the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25642 --- .../celerity/controller/CelerityResourceController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/applications/celerity/controller/CelerityResourceController.php b/src/applications/celerity/controller/CelerityResourceController.php index 547c881df8..1908c4583a 100644 --- a/src/applications/celerity/controller/CelerityResourceController.php +++ b/src/applications/celerity/controller/CelerityResourceController.php @@ -59,6 +59,7 @@ abstract class CelerityResourceController extends PhabricatorController { } $cache = null; + $cache_key = null; $data = null; if ($is_cacheable && $is_locally_cacheable && !$dev_mode) { $cache = PhabricatorCaches::getImmutableCache(); @@ -98,7 +99,7 @@ abstract class CelerityResourceController extends PhabricatorController { $data = $xformer->transformResource($path, $data); } - if ($cache) { + if ($cache && $cache_key !== null) { $cache->setKey($cache_key, $data); } } From 214988fafd752ea68d0633656261fa9ec5cf2017 Mon Sep 17 00:00:00 2001 From: Merula Turdus Date: Sun, 14 Apr 2024 07:36:06 +0200 Subject: [PATCH 038/118] T15774: Celerity: javascript minification issue with template literals (backticked strings) Test Plan: This archive contains a small demo: {F2142567} When installed, an application named D25571 appears under "More Applications". When you start it, you will see 2 messageboxes: one should be a single lined text, the other a multilined text. Steps: 1) unpack archive in some directory 2) add ext-D25571/src/ path to load-libraries in local.json 3) bin/arc liberate 4) bin/cache purge --all 5) bin/celerity map 6) restart httpd 7) start D25571 application from More Applications Also: arc unit ./src/applications/celerity/__tests__/CelerityResourceTransformerTestCase.php Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: avivey, aklapper, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15774 Differential Revision: https://we.phorge.it/D25571 --- externals/JsShrink/jsShrink.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/externals/JsShrink/jsShrink.php b/externals/JsShrink/jsShrink.php index 34a5a5b8e8..a6e2fb6f82 100644 --- a/externals/JsShrink/jsShrink.php +++ b/externals/JsShrink/jsShrink.php @@ -11,7 +11,8 @@ function jsShrink($input) { return preg_replace_callback('( (?: - (^|[-+\([{}=,:;!%^&*|?~]|/(?![/*])|return|throw) # context before regexp + (`(?:\\\\.|[^`\\\\])*`) # template literal + |(^|[-+\([{}=,:;!%^&*|?~]|/(?![/*])|return|throw) # context before regexp (?:\s|//[^\n]*+\n|/\*(?:[^*]|\*(?!/))*+\*/)* # optional space (/(?![/*])(?: \\\\[^\n] @@ -31,9 +32,11 @@ function jsShrink($input) { function jsShrinkCallback($match) { static $last = ''; - $match += array_fill(1, 5, null); // avoid E_NOTICE - list(, $context, $regexp, $result, $word, $operator) = $match; - if ($word != '') { + $match += array_fill(1, 7, null); // avoid E_NOTICE + list(, $template, $context, $regexp, $result, $word, $operator) = $match; + if ($template) { + $result = $template; + } elseif ($word != '') { $result = ($last == 'word' ? "\n" : ($last == 'return' ? " " : "")) . $result; $last = ($word == 'return' || $word == 'throw' || $word == 'break' || $word == 'async' ? 'return' : 'word'); } elseif ($operator) { From 8f361810737c015dd809eb7127e75e0e469a5e36 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 11 Jun 2024 01:17:20 +0200 Subject: [PATCH 039/118] Fix PHP 8.1 "strlen(null)" exception in Diviner when PHPDoc has no @task block Summary: `strlen()` was used in Phabricator to check if a generic value is a non-empty string. This behavior is deprecated since PHP 8.1. Phorge adopts `phutil_nonempty_string()` as a replacement. Note: this may highlight other absurd input values that might be worth correcting instead of just ignoring. If phutil_nonempty_string() throws an exception in your instance, report it to Phorge to evaluate and fix that specific corner case. ``` ERROR 8192: strlen(): Passing null to parameter #1 ($string) of type string is deprecated at [/var/www/html/phorge/phorge/src/applications/diviner/controller/DivinerAtomController.php:440] ``` Closes T15853 Test Plan: Run `./bin/diviner generate` and visit a Diviner page about a class which has no PHPDoc `@task` block, for example access http://phorge.localhost/diviner/find/?name=PhutilSafeHTML&type=class&jump=1 Reviewers: O1 Blessed Committers, speck Reviewed By: O1 Blessed Committers, speck Subscribers: speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15853 Differential Revision: https://we.phorge.it/D25689 --- src/applications/diviner/controller/DivinerAtomController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diviner/controller/DivinerAtomController.php b/src/applications/diviner/controller/DivinerAtomController.php index 60eba689e4..cf29389da9 100644 --- a/src/applications/diviner/controller/DivinerAtomController.php +++ b/src/applications/diviner/controller/DivinerAtomController.php @@ -437,7 +437,7 @@ final class DivinerAtomController extends DivinerController { $tasks = $symbol->getAtom()->getDocblockMetaValue('task'); if (!is_array($tasks)) { - if (strlen($tasks)) { + if (phutil_nonempty_string($tasks)) { $tasks = array($tasks); } else { $tasks = array(); From 1bb7422662ac4c5c998adcbc73205d58ff3d2902 Mon Sep 17 00:00:00 2001 From: Pppery Date: Tue, 11 Jun 2024 23:58:05 -0400 Subject: [PATCH 040/118] Reword joke password reset email message Summary: Closes T15840 Test Plan: Send an I forgot my password email with serious business mode off and see the updated email Reviewers: O1 Blessed Committers, valerio.bozzolan, aklapper Reviewed By: O1 Blessed Committers, valerio.bozzolan, aklapper Subscribers: sirocyl, aklapper, speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15840 Differential Revision: https://we.phorge.it/D25671 --- .../PhabricatorPeopleEmailLoginMailEngine.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/applications/people/mail/PhabricatorPeopleEmailLoginMailEngine.php b/src/applications/people/mail/PhabricatorPeopleEmailLoginMailEngine.php index 4904e269a6..f34e86585a 100644 --- a/src/applications/people/mail/PhabricatorPeopleEmailLoginMailEngine.php +++ b/src/applications/people/mail/PhabricatorPeopleEmailLoginMailEngine.php @@ -84,14 +84,17 @@ final class PhabricatorPeopleEmailLoginMailEngine "Condolences on forgetting your password. You can use this ". "link to reset it:\n\n". " %s\n\n". - "After you set a new password, consider writing it down on a ". - "sticky note and attaching it to your monitor so you don't ". - "forget again! Choosing a very short, easy-to-remember password ". - "like \"cat\" or \"1234\" might also help.\n\n". - "Best Wishes,\nPhabricator\n", - $login_uri); + "After setting a new password, consider writing it down ". + "on a sticky note and attaching it to your monitor so others ". + "can impersonate you at any time. Choosing a short, ". + "easy-to-remember password like \"cat\" or \"1234\"". + " might also help to get your machine hacked, your". + " bank account emptied, or your company ruined.". + "\n\nBest Wishes,\n%s", + $login_uri, + PlatformSymbols::getPlatformServerName()); - } + } } else { $body[] = pht( "You can use this login link to regain access to your account:". From 000bccf8e4f717e6b1dc581ba1081e3f2467d841 Mon Sep 17 00:00:00 2001 From: Merula Turdus Date: Sat, 15 Jun 2024 14:40:05 +0200 Subject: [PATCH 041/118] Fix PHP 8.1 "strlen(null)" exception in PhabricatorMailSMTPAdapter when sending email Summary: `strlen()` was used in Phabricator to check if a generic value is a non-empty string. This behavior is deprecated since PHP 8.1. Phorge adopts `phutil_nonempty_string()` as a replacement. ``` ERROR 8192: strlen(): Passing null to parameter #1 ($string) of type string is deprecated at [/var/www/htdocs/phorge/phorge/src/applications/metamta/adapter/PhabricatorMailSMTPAdapter.php:65] ``` Closes T15857 Test Plan: Send Welcome email via 'Manage' user screen and Run `./bin/phd log` afterwards to validate email action Reviewers: O1 Blessed Committers, speck Reviewed By: O1 Blessed Committers, speck Subscribers: speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15857 Differential Revision: https://we.phorge.it/D25692 --- src/applications/metamta/adapter/PhabricatorMailSMTPAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/metamta/adapter/PhabricatorMailSMTPAdapter.php b/src/applications/metamta/adapter/PhabricatorMailSMTPAdapter.php index abbda40146..f46968ad1a 100644 --- a/src/applications/metamta/adapter/PhabricatorMailSMTPAdapter.php +++ b/src/applications/metamta/adapter/PhabricatorMailSMTPAdapter.php @@ -62,7 +62,7 @@ final class PhabricatorMailSMTPAdapter $smtp->Host = $this->getOption('host'); $smtp->Port = $this->getOption('port'); $user = $this->getOption('user'); - if (strlen($user)) { + if (phutil_nonempty_string($user)) { $smtp->SMTPAuth = true; $smtp->Username = $user; $smtp->Password = $this->getOption('password'); From 5d479556c329f2e14797c4fbf189c5036a24a7a4 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 18 Jun 2024 20:00:14 +0200 Subject: [PATCH 042/118] Display same items in mobile menu on User Profile page as on desktop Summary: Display same items in mobile menu on User Profile page as on desktop. (The code did not make too much sense anyway here?) Also remove special but dysfunctional "Calendar" item in mobile view: The resulting URI does not work even if the calendar application is installed. If it ever worked, it should be moved to the default desktop sidebar anyway and not be a mobile-only menu item. Closes T15224 Test Plan: Open http://phorge.localhost/people/ and http://phorge.localhost/p/username and http://phorge.localhost/people/manage/1/, change screen width to less and more than 920px, check at menu items in left bar (>920px) and right upper corner (<920px). Reviewers: O1 Blessed Committers, speck Reviewed By: O1 Blessed Committers, speck Subscribers: speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15224 Differential Revision: https://we.phorge.it/D25687 --- .../PhabricatorPeopleController.php | 38 ++++++++----------- .../engine/PhabricatorProfileMenuEngine.php | 3 ++ 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/applications/people/controller/PhabricatorPeopleController.php b/src/applications/people/controller/PhabricatorPeopleController.php index b4e7e4a8df..d51dc4ceec 100644 --- a/src/applications/people/controller/PhabricatorPeopleController.php +++ b/src/applications/people/controller/PhabricatorPeopleController.php @@ -6,39 +6,33 @@ abstract class PhabricatorPeopleController extends PhabricatorController { return true; } + /** + * return AphrontSideNavFilterView + */ public function buildSideNavView($for_app = false) { + // we are on /people/* $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - $name = null; - if ($for_app) { - $name = $this->getRequest()->getURIData('username'); - if ($name) { - $nav->setBaseURI(new PhutilURI('/p/')); - $nav->addFilter("{$name}/", $name); - if (id(new PhabricatorCalendarApplication())->isInstalled()) { - $nav->addFilter("{$name}/calendar/", pht('Calendar')); - } - } - } + $viewer = $this->getRequest()->getUser(); + id(new PhabricatorPeopleSearchEngine()) + ->setViewer($viewer) + ->addNavigationItems($nav->getMenu()); - if (!$name) { - $viewer = $this->getRequest()->getUser(); - id(new PhabricatorPeopleSearchEngine()) - ->setViewer($viewer) - ->addNavigationItems($nav->getMenu()); - - if ($viewer->getIsAdmin()) { - $nav->addLabel(pht('User Administration')); - $nav->addFilter('logs', pht('Activity Logs')); - $nav->addFilter('invite', pht('Email Invitations')); - } + if ($viewer->getIsAdmin()) { + $nav->addLabel(pht('User Administration')); + $nav->addFilter('logs', pht('Activity Logs')); + $nav->addFilter('invite', pht('Email Invitations')); } return $nav; } public function buildApplicationMenu() { + if ($this->getRequest()->getURIData('username')) { + // we are on /p/name/ so return the default user profile sidebar + return parent::buildApplicationMenu(); + } return $this->buildSideNavView(true)->getMenu(); } diff --git a/src/applications/search/engine/PhabricatorProfileMenuEngine.php b/src/applications/search/engine/PhabricatorProfileMenuEngine.php index 8799ae8f0b..bfa3d11ee5 100644 --- a/src/applications/search/engine/PhabricatorProfileMenuEngine.php +++ b/src/applications/search/engine/PhabricatorProfileMenuEngine.php @@ -87,6 +87,9 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { abstract public function getItemURI($path); abstract protected function isMenuEngineConfigurable(); + /** + * @return array of PhabricatorProfileMenuItemConfiguration objects + */ abstract protected function getBuiltinProfileItems($object); protected function getBuiltinCustomProfileItems( From 587530a9bf7b7b9f0027aa65f00589759d2b07f3 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Fri, 21 Jun 2024 12:42:57 +0300 Subject: [PATCH 043/118] Add Standard Custom Fields to Item List Summary: Allow "Standard" (config-based") custom fields to be displayed in search-results. Depends on D25548. Ref T15750. Test Plan: Set `maniphest.custom-field-definitions` to the value of P32, and start playing with custom values on tasks. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: 20after4, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15750 Differential Revision: https://we.phorge.it/D25549 --- .../user/configuration/custom_fields.diviner | 6 ++ .../PhabricatorStandardCustomField.php | 75 +++++++++++++++++++ ...abricatorStandardCustomFieldBlueprints.php | 2 +- .../PhabricatorStandardCustomFieldBool.php | 4 +- ...abricatorStandardCustomFieldCredential.php | 4 +- .../PhabricatorStandardCustomFieldDate.php | 2 +- .../PhabricatorStandardCustomFieldHeader.php | 2 +- .../PhabricatorStandardCustomFieldLink.php | 2 +- .../PhabricatorStandardCustomFieldPHIDs.php | 7 +- ...PhabricatorStandardCustomFieldRemarkup.php | 2 +- .../PhabricatorStandardCustomFieldSelect.php | 2 +- 11 files changed, 94 insertions(+), 14 deletions(-) diff --git a/src/docs/user/configuration/custom_fields.diviner b/src/docs/user/configuration/custom_fields.diviner index 5879931317..fb036d11d9 100644 --- a/src/docs/user/configuration/custom_fields.diviner +++ b/src/docs/user/configuration/custom_fields.diviner @@ -119,6 +119,12 @@ When defining custom fields using a configuration option like above the control when rendered on the edit view. - **placeholder**: A placeholder text that appears on text boxes. Only supported in text, int and remarkup fields (optional). + - **list**: If set to `icon`, `attribute` or `byline`, the value of the field + will be shown in list-view. + - **list.icon**: If `list` is set to `icon`, use this icon. These are the + same icons that can be used in the `{icon}` syntax for Remarkup. + - **list.label**: When rendering value in a list, use this label (instead of + `name`). - **copy**: If true, this field's value will be copied when an object is created using another object as a template. - **limit**: For control types which use a tokenizer control to let the user diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php index 87cde00801..8ed6e0f42a 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php @@ -301,6 +301,10 @@ abstract class PhabricatorStandardCustomField } public function renderPropertyViewValue(array $handles) { + return $this->renderValue(); + } + + protected function renderValue() { // If your field needs to render anything more complicated then a string, // then you should override this method. $value_str = phutil_string_cast($this->getFieldValue()); @@ -311,6 +315,77 @@ abstract class PhabricatorStandardCustomField return null; } + public function shouldAppearInListView() { + return $this->getFieldConfigValue('list', false); + } + + public function getStyleForListItemView() { + return $this->getFieldConfigValue('list'); + } + + public function renderListItemValue() { + return $this->renderValue(); + } + + private function isValue($something) { + if (is_object($something)) { + return true; + } + return phutil_nonempty_scalar($something); + } + + public function getValueForListItem() { + $style = $this->getStyleForListItemView(); + $value = $this->renderListItemValue(); + if (!$this->isValue($value) || !$style) { + return null; + } + switch ($style) { + case 'icon': + // maybe expose 'list.icon.alt' for hover stuff? + // also icon's "label", and other features supported by + // PHUIObjectItemView::addIcon(). + return 'fa-'.$this->getFieldConfigValue('list.icon'); + case 'attribute': + case 'byline': + $label = $this->getFieldConfigValue( + 'list.label', + $this->getFieldName()); + if (phutil_nonempty_string($label)) { + return pht('%s: %s', $label, $value); + } + return $value; + default: + throw new Exception( + pht( + "Unknown field list-item view style '%s'; valid styles are ". + "'%s', '%s'and '%s'.", + $style, + 'icon', + 'attribute', + 'byline')); + } + } + + public function renderOnListItem(PHUIObjectItemView $view) { + $value = $this->getValueForListItem(); + if (!$this->isValue($value)) { + return; + } + + switch ($this->getStyleForListItemView()) { + case 'icon': + $view->addIcon($value); + break; + case 'attribute': + $view->addAttribute($value); + break; + case 'byline': + $view->addByline($value); + break; + } + } + public function shouldAppearInApplicationSearch() { return $this->getFieldConfigValue('search', false); } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBlueprints.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBlueprints.php index ad2bb62d81..95c3c1a3e2 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBlueprints.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBlueprints.php @@ -24,7 +24,7 @@ final class PhabricatorStandardCustomFieldBlueprints $new); } - public function renderPropertyViewValue(array $handles) { + protected function renderValue() { $value = $this->getFieldValue(); if (!$value) { return phutil_tag('em', array(), pht('No authorized blueprints.')); diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php index f1d1371a7d..a6bdbeb955 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php @@ -36,7 +36,7 @@ final class PhabricatorStandardCustomFieldBool } public function setValueFromStorage($value) { - if (strlen($value)) { + if (phutil_nonempty_scalar($value)) { $value = (bool)$value; } else { $value = null; @@ -90,7 +90,7 @@ final class PhabricatorStandardCustomFieldBool (bool)$this->getFieldValue()); } - public function renderPropertyViewValue(array $handles) { + protected function renderValue() { $value = $this->getFieldValue(); if ($value) { return $this->getString('view.yes', pht('Yes')); diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php index c2f958f228..feb3732791 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php @@ -53,10 +53,10 @@ final class PhabricatorStandardCustomFieldCredential return array(); } - public function renderPropertyViewValue(array $handles) { + protected function renderValue() { $value = $this->getFieldValue(); if ($value) { - return $handles[$value]->renderLink(); + return $this->getViewer()->renderHandle($value); } return null; } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php index c437df82fb..389b440e46 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php @@ -52,7 +52,7 @@ final class PhabricatorStandardCustomFieldDate $this->setFieldValue($value); } - public function renderPropertyViewValue(array $handles) { + protected function renderValue() { $value = $this->getFieldValue(); if (!$value) { return null; diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldHeader.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldHeader.php index beb92f9aed..2bd8fea40f 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldHeader.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldHeader.php @@ -26,7 +26,7 @@ final class PhabricatorStandardCustomFieldHeader return 'header'; } - public function renderPropertyViewValue(array $handles) { + protected function renderValue() { return $this->getFieldName(); } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldLink.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldLink.php index a5a1af5013..833b110968 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldLink.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldLink.php @@ -18,7 +18,7 @@ final class PhabricatorStandardCustomFieldLink return $indexes; } - public function renderPropertyViewValue(array $handles) { + protected function renderValue() { $value = $this->getFieldValue(); if (!phutil_nonempty_string($value)) { diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php index e34c3913f0..153facd89f 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php @@ -72,15 +72,14 @@ abstract class PhabricatorStandardCustomFieldPHIDs return array(); } - public function renderPropertyViewValue(array $handles) { + protected function renderValue() { $value = $this->getFieldValue(); if (!$value) { return null; } - $handles = mpull($handles, 'renderHovercardLink'); - $handles = phutil_implode_html(', ', $handles); - return $handles; + return $this->getViewer()->renderHandleList($value) + ->setAsInline(true); } public function getRequiredHandlePHIDsForEdit() { diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php index 54f54c7503..cda1595aeb 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php @@ -27,7 +27,7 @@ final class PhabricatorStandardCustomFieldRemarkup ); } - public function renderPropertyViewValue(array $handles) { + protected function renderValue() { $value = $this->getFieldValue(); if (!phutil_nonempty_string($value)) { diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php index 958d65d7e7..505eb19c5e 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php @@ -72,7 +72,7 @@ final class PhabricatorStandardCustomFieldSelect ->setOptions($this->getOptions()); } - public function renderPropertyViewValue(array $handles) { + protected function renderValue() { if (!phutil_nonempty_string($this->getFieldValue())) { return null; } From 1f5c54c35a1a9e05382bd2a91bd8650224d55d9b Mon Sep 17 00:00:00 2001 From: Merula Turdus Date: Sat, 15 Jun 2024 17:01:41 +0200 Subject: [PATCH 044/118] Conduit API: allow phriction.edit to modify viewPolicy and editPolicy Summary: Closes T15858 Test Plan: See T15858 Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15858 Differential Revision: https://we.phorge.it/D25693 --- src/__phutil_library_map__.php | 2 ++ .../PhrictionDocumentEditConduitAPIMethod.php | 19 ++++++++++++++++ .../phid/PhrictionDocumentPHIDType.php | 22 +++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 src/applications/phriction/conduit/PhrictionDocumentEditConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9287085d03..aa0964c85b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5615,6 +5615,7 @@ phutil_register_library_map(array( 'PhrictionDocumentDatasource' => 'applications/phriction/typeahead/PhrictionDocumentDatasource.php', 'PhrictionDocumentDeleteTransaction' => 'applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php', 'PhrictionDocumentDraftTransaction' => 'applications/phriction/xaction/PhrictionDocumentDraftTransaction.php', + 'PhrictionDocumentEditConduitAPIMethod' => 'applications/phriction/conduit/PhrictionDocumentEditConduitAPIMethod.php', 'PhrictionDocumentEditEngine' => 'applications/phriction/editor/PhrictionDocumentEditEngine.php', 'PhrictionDocumentEditTransaction' => 'applications/phriction/xaction/PhrictionDocumentEditTransaction.php', 'PhrictionDocumentFerretEngine' => 'applications/phriction/search/PhrictionDocumentFerretEngine.php', @@ -12510,6 +12511,7 @@ phutil_register_library_map(array( 'PhrictionDocumentDatasource' => 'PhabricatorTypeaheadDatasource', 'PhrictionDocumentDeleteTransaction' => 'PhrictionDocumentVersionTransaction', 'PhrictionDocumentDraftTransaction' => 'PhrictionDocumentEditTransaction', + 'PhrictionDocumentEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhrictionDocumentEditEngine' => 'PhabricatorEditEngine', 'PhrictionDocumentEditTransaction' => 'PhrictionDocumentVersionTransaction', 'PhrictionDocumentFerretEngine' => 'PhabricatorFerretEngine', diff --git a/src/applications/phriction/conduit/PhrictionDocumentEditConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionDocumentEditConduitAPIMethod.php new file mode 100644 index 0000000000..247875cce4 --- /dev/null +++ b/src/applications/phriction/conduit/PhrictionDocumentEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +setViewer($query->getViewer()) + ->withSlugs($names) + ->execute(); + + $results = array(); + foreach ($objects as $id => $object) { + foreach ($names as $name) { + $results[$name] = $object; + } + } + + return $results; + } + } From ddcdd6eaf24be694588bd792e2c2c47b493e3388 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 11 Jun 2024 11:39:46 +0200 Subject: [PATCH 045/118] Fix "Undefined offset: 1" exception in Diviner when @task value in PHPDoc is a single word Summary: The PHPDoc in https://we.phorge.it/source/phorge/browse/master/src/infrastructure/storage/lisk/LiskMigrationIterator.php$14 defines `* @task storage`. That means the value of `$task` in the DivinerAtomController class is the single string `storage` and there is no whitespace to `explode` on and there is no second key in the array to set as `$title`. Thus only call `explode` when `$task` contains a whitespace. ``` EXCEPTION: (RuntimeException) Undefined array key 1 at [/src/error/PhutilErrorHandler.php:273] #0 <#2> PhutilErrorHandler::handleError(integer, string, string, integer) called at [/src/applications/diviner/controller/DivinerAtomController.php:450] ``` Closes T15854 Test Plan: Go to https://we.phorge.it/book/contrib/article/database/#primary-keys and click the `LiskMigrationIterator` link pointing to https://we.phorge.it/book/dev/class/LiskMigrationIterator/ Reviewers: O1 Blessed Committers, speck Reviewed By: O1 Blessed Committers, speck Subscribers: speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15854 Differential Revision: https://we.phorge.it/D25690 --- .../diviner/controller/DivinerAtomController.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/applications/diviner/controller/DivinerAtomController.php b/src/applications/diviner/controller/DivinerAtomController.php index cf29389da9..100fe0f354 100644 --- a/src/applications/diviner/controller/DivinerAtomController.php +++ b/src/applications/diviner/controller/DivinerAtomController.php @@ -446,7 +446,11 @@ final class DivinerAtomController extends DivinerController { if ($tasks) { foreach ($tasks as $task) { - list($name, $title) = explode(' ', $task, 2); + if (strpos($task, ' ') !== false) { + list($name, $title) = explode(' ', $task, 2); + } else { + list($name, $title) = array($task, ''); + } $name = trim($name); $title = trim($title); From 1fa8c79b1ef86a9edda575b582b18fa24d60ea1f Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 9 May 2024 17:21:43 +0200 Subject: [PATCH 046/118] Update d3 from version 5.9.2 to 6.7.0 Summary: Update the d3 library to its last 6.x version available on https://github.com/d3/d3/releases This also requires updating the tooltip event handling of dots in `Chart.js` to avoid an `Uncaught TypeError: d3.event is undefined` per https://observablehq.com/@d3/d3v6-migration-guide#event-management linked from https://github.com/d3/d3/releases/tag/v6.0.0 Closes T15820 Test Plan: * Enable the Facts application, go to the Reports of a Project with task changes over time, look at charts, hover over data points, read the tooltip - e.g. on http://phorge.localhost/project/reports/1/ or http://phorge.localhost/maniphest/report/burn/ * Check HTML source of above URIs for the ` + ``` For the minified version: ```html - + ``` You can also use the standalone D3 microlibraries. For example, [d3-selection](https://github.com/d3/d3-selection): ```html - + ``` D3 is written using [ES2015 modules](http://www.2ality.com/2014/09/es6-modules-final.html). Create a [custom bundle using Rollup](https://bl.ocks.org/mbostock/bb09af4c39c79cffcde4), Webpack, or your preferred bundler. To import D3 into an ES2015 application, either import specific symbols from specific D3 modules: @@ -47,11 +47,11 @@ import * as d3 from "d3"; In Node: ```js -var d3 = require("d3"); +const d3 = require("d3"); ``` You can also require individual modules and combine them into a `d3` object using [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign): ```js -var d3 = Object.assign({}, require("d3-format"), require("d3-geo"), require("d3-geo-projection")); +const d3 = Object.assign({}, require("d3-format"), require("d3-geo"), require("d3-geo-projection")); ``` diff --git a/webroot/rsrc/externals/d3/d3.min.js b/webroot/rsrc/externals/d3/d3.min.js index dfbfa98c1a..034ff8d5c7 100644 --- a/webroot/rsrc/externals/d3/d3.min.js +++ b/webroot/rsrc/externals/d3/d3.min.js @@ -2,5 +2,5 @@ * @provides d3 * @do-not-minify */ -// https://d3js.org v5.9.2 Copyright 2019 Mike Bostock -!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n(t.d3=t.d3||{})}(this,function(t){"use strict";function n(t,n){return tn?1:t>=n?0:NaN}function e(t){var e;return 1===t.length&&(e=t,t=function(t,r){return n(e(t),r)}),{left:function(n,e,r,i){for(null==r&&(r=0),null==i&&(i=n.length);r>>1;t(n[o],e)<0?r=o+1:i=o}return r},right:function(n,e,r,i){for(null==r&&(r=0),null==i&&(i=n.length);r>>1;t(n[o],e)>0?i=o:r=o+1}return r}}}var r=e(n),i=r.right,o=r.left;function a(t,n){return[t,n]}function u(t){return null===t?NaN:+t}function c(t,n){var e,r,i=t.length,o=0,a=-1,c=0,f=0;if(null==n)for(;++a1)return f/(o-1)}function f(t,n){var e=c(t,n);return e?Math.sqrt(e):e}function s(t,n){var e,r,i,o=t.length,a=-1;if(null==n){for(;++a=e)for(r=i=e;++ae&&(r=e),i=e)for(r=i=e;++ae&&(r=e),i0)return[t];if((r=n0)for(t=Math.ceil(t/a),n=Math.floor(n/a),o=new Array(i=Math.ceil(n-t+1));++u=0?(o>=y?10:o>=_?5:o>=b?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(o>=y?10:o>=_?5:o>=b?2:1)}function w(t,n,e){var r=Math.abs(n-t)/Math.max(0,e),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),o=r/i;return o>=y?i*=10:o>=_?i*=5:o>=b&&(i*=2),n=1)return+e(t[r-1],r-1,t);var r,i=(r-1)*n,o=Math.floor(i),a=+e(t[o],o,t);return a+(+e(t[o+1],o+1,t)-a)*(i-o)}}function A(t,n){var e,r,i=t.length,o=-1;if(null==n){for(;++o=e)for(r=e;++or&&(r=e)}else for(;++o=e)for(r=e;++or&&(r=e);return r}function T(t){for(var n,e,r,i=t.length,o=-1,a=0;++o=0;)for(n=(r=t[i]).length;--n>=0;)e[--a]=r[n];return e}function S(t,n){var e,r,i=t.length,o=-1;if(null==n){for(;++o=e)for(r=e;++oe&&(r=e)}else for(;++o=e)for(r=e;++oe&&(r=e);return r}function k(t){if(!(i=t.length))return[];for(var n=-1,e=S(t,E),r=new Array(e);++n=0&&(n=t.slice(e+1),t=t.slice(0,e)),t&&!r.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:n}})),a=-1,u=o.length;if(!(arguments.length<2)){if(null!=n&&"function"!=typeof n)throw new Error("invalid callback: "+n);for(;++a0)for(var e,r,i=new Array(e),o=0;o=0&&"xmlns"!==(n=t.slice(0,e))&&(t=t.slice(e+1)),V.hasOwnProperty(n)?{space:V[n],local:t}:t}function W(t){var n=$(t);return(n.local?function(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}:function(t){return function(){var n=this.ownerDocument,e=this.namespaceURI;return e===G&&n.documentElement.namespaceURI===G?n.createElement(t):n.createElementNS(e,t)}})(n)}function Z(){}function Q(t){return null==t?Z:function(){return this.querySelector(t)}}function J(){return[]}function K(t){return null==t?J:function(){return this.querySelectorAll(t)}}function tt(t){return function(){return this.matches(t)}}function nt(t){return new Array(t.length)}function et(t,n){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=n}et.prototype={constructor:et,appendChild:function(t){return this._parent.insertBefore(t,this._next)},insertBefore:function(t,n){return this._parent.insertBefore(t,n)},querySelector:function(t){return this._parent.querySelector(t)},querySelectorAll:function(t){return this._parent.querySelectorAll(t)}};var rt="$";function it(t,n,e,r,i,o){for(var a,u=0,c=n.length,f=o.length;un?1:t>=n?0:NaN}function ut(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function ct(t,n){return t.style.getPropertyValue(n)||ut(t).getComputedStyle(t,null).getPropertyValue(n)}function ft(t){return t.trim().split(/^|\s+/)}function st(t){return t.classList||new lt(t)}function lt(t){this._node=t,this._names=ft(t.getAttribute("class")||"")}function ht(t,n){for(var e=st(t),r=-1,i=n.length;++r=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var wt={};(t.event=null,"undefined"!=typeof document)&&("onmouseenter"in document.documentElement||(wt={mouseenter:"mouseover",mouseleave:"mouseout"}));function Mt(t,n,e){return t=Nt(t,n,e),function(n){var e=n.relatedTarget;e&&(e===this||8&e.compareDocumentPosition(this))||t.call(this,n)}}function Nt(n,e,r){return function(i){var o=t.event;t.event=i;try{n.call(this,this.__data__,e,r)}finally{t.event=o}}}function At(t){return function(){var n=this.__on;if(n){for(var e,r=0,i=-1,o=n.length;r=x&&(x=m+1);!(b=y[x])&&++x=0;)(r=i[o])&&(a&&4^r.compareDocumentPosition(a)&&a.parentNode.insertBefore(r,a),a=r);return this},sort:function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=at);for(var e=this._groups,r=e.length,i=new Array(r),o=0;o1?this.each((null==n?function(t){return function(){this.style.removeProperty(t)}}:"function"==typeof n?function(t,n,e){return function(){var r=n.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,e)}}:function(t,n,e){return function(){this.style.setProperty(t,n,e)}})(t,n,null==e?"":e)):ct(this.node(),t)},property:function(t,n){return arguments.length>1?this.each((null==n?function(t){return function(){delete this[t]}}:"function"==typeof n?function(t,n){return function(){var e=n.apply(this,arguments);null==e?delete this[t]:this[t]=e}}:function(t,n){return function(){this[t]=n}})(t,n)):this.node()[t]},classed:function(t,n){var e=ft(t+"");if(arguments.length<2){for(var r=st(this.node()),i=-1,o=e.length;++i=0&&(n=t.slice(e+1),t=t.slice(0,e)),{type:t,name:n}})}(t+""),a=o.length;if(!(arguments.length<2)){for(u=n?Tt:At,null==e&&(e=!1),r=0;r>8&15|n>>4&240,n>>4&15|240&n,(15&n)<<4|15&n,1):(n=rn.exec(t))?dn(parseInt(n[1],16)):(n=on.exec(t))?new yn(n[1],n[2],n[3],1):(n=an.exec(t))?new yn(255*n[1]/100,255*n[2]/100,255*n[3]/100,1):(n=un.exec(t))?pn(n[1],n[2],n[3],n[4]):(n=cn.exec(t))?pn(255*n[1]/100,255*n[2]/100,255*n[3]/100,n[4]):(n=fn.exec(t))?bn(n[1],n[2]/100,n[3]/100,1):(n=sn.exec(t))?bn(n[1],n[2]/100,n[3]/100,n[4]):ln.hasOwnProperty(t)?dn(ln[t]):"transparent"===t?new yn(NaN,NaN,NaN,0):null}function dn(t){return new yn(t>>16&255,t>>8&255,255&t,1)}function pn(t,n,e,r){return r<=0&&(t=n=e=NaN),new yn(t,n,e,r)}function vn(t){return t instanceof Jt||(t=hn(t)),t?new yn((t=t.rgb()).r,t.g,t.b,t.opacity):new yn}function gn(t,n,e,r){return 1===arguments.length?vn(t):new yn(t,n,e,null==r?1:r)}function yn(t,n,e,r){this.r=+t,this.g=+n,this.b=+e,this.opacity=+r}function _n(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function bn(t,n,e,r){return r<=0?t=n=e=NaN:e<=0||e>=1?t=n=NaN:n<=0&&(t=NaN),new xn(t,n,e,r)}function mn(t,n,e,r){return 1===arguments.length?function(t){if(t instanceof xn)return new xn(t.h,t.s,t.l,t.opacity);if(t instanceof Jt||(t=hn(t)),!t)return new xn;if(t instanceof xn)return t;var n=(t=t.rgb()).r/255,e=t.g/255,r=t.b/255,i=Math.min(n,e,r),o=Math.max(n,e,r),a=NaN,u=o-i,c=(o+i)/2;return u?(a=n===o?(e-r)/u+6*(e0&&c<1?0:a,new xn(a,u,c,t.opacity)}(t):new xn(t,n,e,null==r?1:r)}function xn(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r}function wn(t,n,e){return 255*(t<60?n+(e-n)*t/60:t<180?e:t<240?n+(e-n)*(240-t)/60:n)}Zt(Jt,hn,{displayable:function(){return this.rgb().displayable()},hex:function(){return this.rgb().hex()},toString:function(){return this.rgb()+""}}),Zt(yn,gn,Qt(Jt,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new yn(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new yn(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return 0<=this.r&&this.r<=255&&0<=this.g&&this.g<=255&&0<=this.b&&this.b<=255&&0<=this.opacity&&this.opacity<=1},hex:function(){return"#"+_n(this.r)+_n(this.g)+_n(this.b)},toString:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}})),Zt(xn,mn,Qt(Jt,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new xn(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new xn(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),n=isNaN(t)||isNaN(this.s)?0:this.s,e=this.l,r=e+(e<.5?e:1-e)*n,i=2*e-r;return new yn(wn(t>=240?t-240:t+120,i,r),wn(t,i,r),wn(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1}}));var Mn=Math.PI/180,Nn=180/Math.PI,An=.96422,Tn=1,Sn=.82521,kn=4/29,En=6/29,Cn=3*En*En,Pn=En*En*En;function zn(t){if(t instanceof Dn)return new Dn(t.l,t.a,t.b,t.opacity);if(t instanceof Fn){if(isNaN(t.h))return new Dn(t.l,0,0,t.opacity);var n=t.h*Mn;return new Dn(t.l,Math.cos(n)*t.c,Math.sin(n)*t.c,t.opacity)}t instanceof yn||(t=vn(t));var e,r,i=On(t.r),o=On(t.g),a=On(t.b),u=qn((.2225045*i+.7168786*o+.0606169*a)/Tn);return i===o&&o===a?e=r=u:(e=qn((.4360747*i+.3850649*o+.1430804*a)/An),r=qn((.0139322*i+.0971045*o+.7141733*a)/Sn)),new Dn(116*u-16,500*(e-u),200*(u-r),t.opacity)}function Rn(t,n,e,r){return 1===arguments.length?zn(t):new Dn(t,n,e,null==r?1:r)}function Dn(t,n,e,r){this.l=+t,this.a=+n,this.b=+e,this.opacity=+r}function qn(t){return t>Pn?Math.pow(t,1/3):t/Cn+kn}function Ln(t){return t>En?t*t*t:Cn*(t-kn)}function Un(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function On(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function Bn(t){if(t instanceof Fn)return new Fn(t.h,t.c,t.l,t.opacity);if(t instanceof Dn||(t=zn(t)),0===t.a&&0===t.b)return new Fn(NaN,0,t.l,t.opacity);var n=Math.atan2(t.b,t.a)*Nn;return new Fn(n<0?n+360:n,Math.sqrt(t.a*t.a+t.b*t.b),t.l,t.opacity)}function Yn(t,n,e,r){return 1===arguments.length?Bn(t):new Fn(t,n,e,null==r?1:r)}function Fn(t,n,e,r){this.h=+t,this.c=+n,this.l=+e,this.opacity=+r}Zt(Dn,Rn,Qt(Jt,{brighter:function(t){return new Dn(this.l+18*(null==t?1:t),this.a,this.b,this.opacity)},darker:function(t){return new Dn(this.l-18*(null==t?1:t),this.a,this.b,this.opacity)},rgb:function(){var t=(this.l+16)/116,n=isNaN(this.a)?t:t+this.a/500,e=isNaN(this.b)?t:t-this.b/200;return new yn(Un(3.1338561*(n=An*Ln(n))-1.6168667*(t=Tn*Ln(t))-.4906146*(e=Sn*Ln(e))),Un(-.9787684*n+1.9161415*t+.033454*e),Un(.0719453*n-.2289914*t+1.4052427*e),this.opacity)}})),Zt(Fn,Yn,Qt(Jt,{brighter:function(t){return new Fn(this.h,this.c,this.l+18*(null==t?1:t),this.opacity)},darker:function(t){return new Fn(this.h,this.c,this.l-18*(null==t?1:t),this.opacity)},rgb:function(){return zn(this).rgb()}}));var In=-.14861,jn=1.78277,Hn=-.29227,Xn=-.90649,Gn=1.97294,Vn=Gn*Xn,$n=Gn*jn,Wn=jn*Hn-Xn*In;function Zn(t,n,e,r){return 1===arguments.length?function(t){if(t instanceof Qn)return new Qn(t.h,t.s,t.l,t.opacity);t instanceof yn||(t=vn(t));var n=t.r/255,e=t.g/255,r=t.b/255,i=(Wn*r+Vn*n-$n*e)/(Wn+Vn-$n),o=r-i,a=(Gn*(e-i)-Hn*o)/Xn,u=Math.sqrt(a*a+o*o)/(Gn*i*(1-i)),c=u?Math.atan2(a,o)*Nn-120:NaN;return new Qn(c<0?c+360:c,u,i,t.opacity)}(t):new Qn(t,n,e,null==r?1:r)}function Qn(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r}function Jn(t,n,e,r,i){var o=t*t,a=o*t;return((1-3*t+3*o-a)*n+(4-6*o+3*a)*e+(1+3*t+3*o-3*a)*r+a*i)/6}function Kn(t){var n=t.length-1;return function(e){var r=e<=0?e=0:e>=1?(e=1,n-1):Math.floor(e*n),i=t[r],o=t[r+1],a=r>0?t[r-1]:2*i-o,u=r180||e<-180?e-360*Math.round(e/360):e):ne(isNaN(t)?n:t)}function ie(t){return 1==(t=+t)?oe:function(n,e){return e-n?function(t,n,e){return t=Math.pow(t,e),n=Math.pow(n,e)-t,e=1/e,function(r){return Math.pow(t+r*n,e)}}(n,e,t):ne(isNaN(n)?e:n)}}function oe(t,n){var e=n-t;return e?ee(t,e):ne(isNaN(t)?n:t)}Zt(Qn,Zn,Qt(Jt,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new Qn(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new Qn(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=isNaN(this.h)?0:(this.h+120)*Mn,n=+this.l,e=isNaN(this.s)?0:this.s*n*(1-n),r=Math.cos(t),i=Math.sin(t);return new yn(255*(n+e*(In*r+jn*i)),255*(n+e*(Hn*r+Xn*i)),255*(n+e*(Gn*r)),this.opacity)}}));var ae=function t(n){var e=ie(n);function r(t,n){var r=e((t=gn(t)).r,(n=gn(n)).r),i=e(t.g,n.g),o=e(t.b,n.b),a=oe(t.opacity,n.opacity);return function(n){return t.r=r(n),t.g=i(n),t.b=o(n),t.opacity=a(n),t+""}}return r.gamma=t,r}(1);function ue(t){return function(n){var e,r,i=n.length,o=new Array(i),a=new Array(i),u=new Array(i);for(e=0;eo&&(i=n.slice(o,i),u[a]?u[a]+=i:u[++a]=i),(e=e[0])===(r=r[0])?u[a]?u[a]+=r:u[++a]=r:(u[++a]=null,c.push({i:a,x:he(e,r)})),o=ve.lastIndex;return o180?n+=360:n-t>180&&(t+=360),o.push({i:e.push(i(e)+"rotate(",null,r)-2,x:he(t,n)})):n&&e.push(i(e)+"rotate("+n+r)}(o.rotate,a.rotate,u,c),function(t,n,e,o){t!==n?o.push({i:e.push(i(e)+"skewX(",null,r)-2,x:he(t,n)}):n&&e.push(i(e)+"skewX("+n+r)}(o.skewX,a.skewX,u,c),function(t,n,e,r,o,a){if(t!==e||n!==r){var u=o.push(i(o)+"scale(",null,",",null,")");a.push({i:u-4,x:he(t,e)},{i:u-2,x:he(n,r)})}else 1===e&&1===r||o.push(i(o)+"scale("+e+","+r+")")}(o.scaleX,o.scaleY,a.scaleX,a.scaleY,u,c),o=a=null,function(t){for(var n,e=-1,r=c.length;++e=0&&n._call.call(null,t),n=n._next;--Ge}function ar(){Qe=(Ze=Ke.now())+Je,Ge=Ve=0;try{or()}finally{Ge=0,function(){var t,n,e=He,r=1/0;for(;e;)e._call?(r>e._time&&(r=e._time),t=e,e=e._next):(n=e._next,e._next=null,e=t?t._next=n:He=n);Xe=t,cr(r)}(),Qe=0}}function ur(){var t=Ke.now(),n=t-Ze;n>We&&(Je-=n,Ze=t)}function cr(t){Ge||(Ve&&(Ve=clearTimeout(Ve)),t-Qe>24?(t<1/0&&(Ve=setTimeout(ar,t-Ke.now()-Je)),$e&&($e=clearInterval($e))):($e||(Ze=Ke.now(),$e=setInterval(ur,We)),Ge=1,tr(ar)))}function fr(t,n,e){var r=new rr;return n=null==n?0:+n,r.restart(function(e){r.stop(),t(e+n)},n,e),r}rr.prototype=ir.prototype={constructor:rr,restart:function(t,n,e){if("function"!=typeof t)throw new TypeError("callback is not a function");e=(null==e?nr():+e)+(null==n?0:+n),this._next||Xe===this||(Xe?Xe._next=this:He=this,Xe=this),this._call=t,this._time=e,cr()},stop:function(){this._call&&(this._call=null,this._time=1/0,cr())}};var sr=I("start","end","cancel","interrupt"),lr=[],hr=0,dr=1,pr=2,vr=3,gr=4,yr=5,_r=6;function br(t,n,e,r,i,o){var a=t.__transition;if(a){if(e in a)return}else t.__transition={};!function(t,n,e){var r,i=t.__transition;function o(c){var f,s,l,h;if(e.state!==dr)return u();for(f in i)if((h=i[f]).name===e.name){if(h.state===vr)return fr(o);h.state===gr?(h.state=_r,h.timer.stop(),h.on.call("interrupt",t,t.__data__,h.index,h.group),delete i[f]):+fhr)throw new Error("too late; already scheduled");return e}function xr(t,n){var e=wr(t,n);if(e.state>vr)throw new Error("too late; already running");return e}function wr(t,n){var e=t.__transition;if(!e||!(e=e[n]))throw new Error("transition not found");return e}function Mr(t,n){var e,r,i,o=t.__transition,a=!0;if(o){for(i in n=null==n?null:n+"",o)(e=o[i]).name===n?(r=e.state>pr&&e.state=0&&(t=t.slice(0,n)),!t||"start"===t})}(n)?mr:xr;return function(){var a=o(this,t),u=a.on;u!==r&&(i=(r=u).copy()).on(n,e),a.on=i}}(e,t,n))},attr:function(t,n){var e=$(t),r="transform"===e?ke:Ar;return this.attrTween(t,"function"==typeof n?(e.local?function(t,n,e){var r,i,o;return function(){var a,u,c=e(this);if(null!=c)return(a=this.getAttributeNS(t.space,t.local))===(u=c+"")?null:a===r&&u===i?o:(i=u,o=n(r=a,c));this.removeAttributeNS(t.space,t.local)}}:function(t,n,e){var r,i,o;return function(){var a,u,c=e(this);if(null!=c)return(a=this.getAttribute(t))===(u=c+"")?null:a===r&&u===i?o:(i=u,o=n(r=a,c));this.removeAttribute(t)}})(e,r,Nr(this,"attr."+t,n)):null==n?(e.local?function(t){return function(){this.removeAttributeNS(t.space,t.local)}}:function(t){return function(){this.removeAttribute(t)}})(e):(e.local?function(t,n,e){var r,i,o=e+"";return function(){var a=this.getAttributeNS(t.space,t.local);return a===o?null:a===r?i:i=n(r=a,e)}}:function(t,n,e){var r,i,o=e+"";return function(){var a=this.getAttribute(t);return a===o?null:a===r?i:i=n(r=a,e)}})(e,r,n))},attrTween:function(t,n){var e="attr."+t;if(arguments.length<2)return(e=this.tween(e))&&e._value;if(null==n)return this.tween(e,null);if("function"!=typeof n)throw new Error;var r=$(t);return this.tween(e,(r.local?function(t,n){var e,r;function i(){var i=n.apply(this,arguments);return i!==r&&(e=(r=i)&&function(t,n){return function(e){this.setAttributeNS(t.space,t.local,n(e))}}(t,i)),e}return i._value=n,i}:function(t,n){var e,r;function i(){var i=n.apply(this,arguments);return i!==r&&(e=(r=i)&&function(t,n){return function(e){this.setAttribute(t,n(e))}}(t,i)),e}return i._value=n,i})(r,n))},style:function(t,n,e){var r="transform"==(t+="")?Se:Ar;return null==n?this.styleTween(t,function(t,n){var e,r,i;return function(){var o=ct(this,t),a=(this.style.removeProperty(t),ct(this,t));return o===a?null:o===e&&a===r?i:i=n(e=o,r=a)}}(t,r)).on("end.style."+t,Sr(t)):"function"==typeof n?this.styleTween(t,function(t,n,e){var r,i,o;return function(){var a=ct(this,t),u=e(this),c=u+"";return null==u&&(this.style.removeProperty(t),c=u=ct(this,t)),a===c?null:a===r&&c===i?o:(i=c,o=n(r=a,u))}}(t,r,Nr(this,"style."+t,n))).each(function(t,n){var e,r,i,o,a="style."+n,u="end."+a;return function(){var c=xr(this,t),f=c.on,s=null==c.value[a]?o||(o=Sr(n)):void 0;f===e&&i===s||(r=(e=f).copy()).on(u,i=s),c.on=r}}(this._id,t)):this.styleTween(t,function(t,n,e){var r,i,o=e+"";return function(){var a=ct(this,t);return a===o?null:a===r?i:i=n(r=a,e)}}(t,r,n),e).on("end.style."+t,null)},styleTween:function(t,n,e){var r="style."+(t+="");if(arguments.length<2)return(r=this.tween(r))&&r._value;if(null==n)return this.tween(r,null);if("function"!=typeof n)throw new Error;return this.tween(r,function(t,n,e){var r,i;function o(){var o=n.apply(this,arguments);return o!==i&&(r=(i=o)&&function(t,n,e){return function(r){this.style.setProperty(t,n(r),e)}}(t,o,e)),r}return o._value=n,o}(t,n,null==e?"":e))},text:function(t){return this.tween("text","function"==typeof t?function(t){return function(){var n=t(this);this.textContent=null==n?"":n}}(Nr(this,"text",t)):function(t){return function(){this.textContent=t}}(null==t?"":t+""))},remove:function(){return this.on("end.remove",(t=this._id,function(){var n=this.parentNode;for(var e in this.__transition)if(+e!==t)return;n&&n.removeChild(this)}));var t},tween:function(t,n){var e=this._id;if(t+="",arguments.length<2){for(var r,i=wr(this.node(),e).tween,o=0,a=i.length;o0&&(r=o-p),M<0?h=d-v:M>0&&(a=u-v),x=vi,L.attr("cursor",xi.selection),B());break;default:return}di()},!0).on("keyup.brush",function(){switch(t.event.keyCode){case 16:P&&(y=_=P=!1,B());break;case 18:x===yi&&(w<0?s=l:w>0&&(r=o),M<0?h=d:M>0&&(a=u),x=gi,B());break;case 32:x===vi&&(t.event.altKey?(w&&(s=l-p*w,r=o+p*w),M&&(h=d-v*M,a=u+v*M),x=yi):(w<0?s=l:w>0&&(r=o),M<0?h=d:M>0&&(a=u),x=gi),L.attr("cursor",xi[m]),B());break;default:return}di()},!0).on("mousemove.brush",O,!0).on("mouseup.brush",Y,!0);It(t.event.view)}hi(),Mr(b),c.call(b),D.start()}function O(){var t=Ot(b);!P||y||_||(Math.abs(t[0]-R[0])>Math.abs(t[1]-R[1])?_=!0:y=!0),R=t,g=!0,di(),B()}function B(){var t;switch(p=R[0]-z[0],v=R[1]-z[1],x){case vi:case pi:w&&(p=Math.max(S-r,Math.min(E-s,p)),o=r+p,l=s+p),M&&(v=Math.max(k-a,Math.min(C-h,v)),u=a+v,d=h+v);break;case gi:w<0?(p=Math.max(S-r,Math.min(E-r,p)),o=r+p,l=s):w>0&&(p=Math.max(S-s,Math.min(E-s,p)),o=r,l=s+p),M<0?(v=Math.max(k-a,Math.min(C-a,v)),u=a+v,d=h):M>0&&(v=Math.max(k-h,Math.min(C-h,v)),u=a,d=h+v);break;case yi:w&&(o=Math.max(S,Math.min(E,r-p*w)),l=Math.max(S,Math.min(E,s+p*w))),M&&(u=Math.max(k,Math.min(C,a-v*M)),d=Math.max(k,Math.min(C,h+v*M)))}l1e-6)if(Math.abs(s*u-c*f)>1e-6&&i){var h=e-o,d=r-a,p=u*u+c*c,v=h*h+d*d,g=Math.sqrt(p),y=Math.sqrt(l),_=i*Math.tan((Yi-Math.acos((p+l-v)/(2*g*y)))/2),b=_/y,m=_/g;Math.abs(b-1)>1e-6&&(this._+="L"+(t+b*f)+","+(n+b*s)),this._+="A"+i+","+i+",0,0,"+ +(s*h>f*d)+","+(this._x1=t+m*u)+","+(this._y1=n+m*c)}else this._+="L"+(this._x1=t)+","+(this._y1=n);else;},arc:function(t,n,e,r,i,o){t=+t,n=+n;var a=(e=+e)*Math.cos(r),u=e*Math.sin(r),c=t+a,f=n+u,s=1^o,l=o?r-i:i-r;if(e<0)throw new Error("negative radius: "+e);null===this._x1?this._+="M"+c+","+f:(Math.abs(this._x1-c)>1e-6||Math.abs(this._y1-f)>1e-6)&&(this._+="L"+c+","+f),e&&(l<0&&(l=l%Fi+Fi),l>Ii?this._+="A"+e+","+e+",0,1,"+s+","+(t-a)+","+(n-u)+"A"+e+","+e+",0,1,"+s+","+(this._x1=c)+","+(this._y1=f):l>1e-6&&(this._+="A"+e+","+e+",0,"+ +(l>=Yi)+","+s+","+(this._x1=t+e*Math.cos(i))+","+(this._y1=n+e*Math.sin(i))))},rect:function(t,n,e,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+n)+"h"+ +e+"v"+ +r+"h"+-e+"Z"},toString:function(){return this._}};function Zi(){}function Qi(t,n){var e=new Zi;if(t instanceof Zi)t.each(function(t,n){e.set(n,t)});else if(Array.isArray(t)){var r,i=-1,o=t.length;if(null==n)for(;++ir!=d>r&&e<(h-f)*(r-s)/(d-s)+f&&(i=-i)}return i}function so(t,n,e){var r,i,o,a;return function(t,n,e){return(n[0]-t[0])*(e[1]-t[1])==(e[0]-t[0])*(n[1]-t[1])}(t,n,e)&&(i=t[r=+(t[0]===n[0])],o=e[r],a=n[r],i<=o&&o<=a||a<=o&&o<=i)}function lo(){}var ho=[[],[[[1,1.5],[.5,1]]],[[[1.5,1],[1,1.5]]],[[[1.5,1],[.5,1]]],[[[1,.5],[1.5,1]]],[[[1,1.5],[.5,1]],[[1,.5],[1.5,1]]],[[[1,.5],[1,1.5]]],[[[1,.5],[.5,1]]],[[[.5,1],[1,.5]]],[[[1,1.5],[1,.5]]],[[[.5,1],[1,.5]],[[1.5,1],[1,1.5]]],[[[1.5,1],[1,.5]]],[[[.5,1],[1.5,1]]],[[[1,1.5],[1.5,1]]],[[[.5,1],[1,1.5]]],[]];function po(){var t=1,n=1,e=M,r=u;function i(t){var n=e(t);if(Array.isArray(n))n=n.slice().sort(ao);else{var r=s(t),i=r[0],a=r[1];n=w(i,a,n),n=g(Math.floor(i/n)*n,Math.floor(a/n)*n,n)}return n.map(function(n){return o(t,n)})}function o(e,i){var o=[],u=[];return function(e,r,i){var o,u,c,f,s,l,h=new Array,d=new Array;o=u=-1,f=e[0]>=r,ho[f<<1].forEach(p);for(;++o=r,ho[c|f<<1].forEach(p);ho[f<<0].forEach(p);for(;++u=r,s=e[u*t]>=r,ho[f<<1|s<<2].forEach(p);++o=r,l=s,s=e[u*t+o+1]>=r,ho[c|f<<1|s<<2|l<<3].forEach(p);ho[f|s<<3].forEach(p)}o=-1,s=e[u*t]>=r,ho[s<<2].forEach(p);for(;++o=r,ho[s<<2|l<<3].forEach(p);function p(t){var n,e,r=[t[0][0]+o,t[0][1]+u],c=[t[1][0]+o,t[1][1]+u],f=a(r),s=a(c);(n=d[f])?(e=h[s])?(delete d[n.end],delete h[e.start],n===e?(n.ring.push(c),i(n.ring)):h[n.start]=d[e.end]={start:n.start,end:e.end,ring:n.ring.concat(e.ring)}):(delete d[n.end],n.ring.push(c),d[n.end=s]=n):(n=h[s])?(e=d[f])?(delete h[n.start],delete d[e.end],n===e?(n.ring.push(c),i(n.ring)):h[e.start]=d[n.end]={start:e.start,end:n.end,ring:e.ring.concat(n.ring)}):(delete h[n.start],n.ring.unshift(r),h[n.start=f]=n):h[f]=d[s]={start:f,end:s,ring:[r,c]}}ho[s<<3].forEach(p)}(e,i,function(t){r(t,e,i),function(t){for(var n=0,e=t.length,r=t[e-1][1]*t[0][0]-t[e-1][0]*t[0][1];++n0?o.push([t]):u.push(t)}),u.forEach(function(t){for(var n,e=0,r=o.length;e0&&a0&&u0&&o>0))throw new Error("invalid size");return t=r,n=o,i},i.thresholds=function(t){return arguments.length?(e="function"==typeof t?t:Array.isArray(t)?uo(oo.call(t)):uo(t),i):e},i.smooth=function(t){return arguments.length?(r=t?u:lo,i):r===u},i}function vo(t,n,e){for(var r=t.width,i=t.height,o=1+(e<<1),a=0;a=e&&(u>=o&&(c-=t.data[u-o+a*r]),n.data[u-e+a*r]=c/Math.min(u+1,r-1+o-u,o))}function go(t,n,e){for(var r=t.width,i=t.height,o=1+(e<<1),a=0;a=e&&(u>=o&&(c-=t.data[a+(u-o)*r]),n.data[a+(u-e)*r]=c/Math.min(u+1,i-1+o-u,o))}function yo(t){return t[0]}function _o(t){return t[1]}function bo(){return 1}var mo={},xo={},wo=34,Mo=10,No=13;function Ao(t){return new Function("d","return {"+t.map(function(t,n){return JSON.stringify(t)+": d["+n+"]"}).join(",")+"}")}function To(t){var n=Object.create(null),e=[];return t.forEach(function(t){for(var r in t)r in n||e.push(n[r]=r)}),e}function So(t,n){var e=t+"",r=e.length;return r9999?"+"+So(n,6):So(n,4))+"-"+So(t.getUTCMonth()+1,2)+"-"+So(t.getUTCDate(),2)+(o?"T"+So(e,2)+":"+So(r,2)+":"+So(i,2)+"."+So(o,3)+"Z":i?"T"+So(e,2)+":"+So(r,2)+":"+So(i,2)+"Z":r||e?"T"+So(e,2)+":"+So(r,2)+"Z":"")}function Eo(t){var n=new RegExp('["'+t+"\n\r]"),e=t.charCodeAt(0);function r(t,n){var r,i=[],o=t.length,a=0,u=0,c=o<=0,f=!1;function s(){if(c)return xo;if(f)return f=!1,mo;var n,r,i=a;if(t.charCodeAt(i)===wo){for(;a++=o?c=!0:(r=t.charCodeAt(a++))===Mo?f=!0:r===No&&(f=!0,t.charCodeAt(a)===Mo&&++a),t.slice(i+1,n-1).replace(/""/g,'"')}for(;a=(o=(v+y)/2))?v=o:y=o,(s=e>=(a=(g+_)/2))?g=a:_=a,i=d,!(d=d[l=s<<1|f]))return i[l]=p,t;if(u=+t._x.call(null,d.data),c=+t._y.call(null,d.data),n===u&&e===c)return p.next=d,i?i[l]=p:t._root=p,t;do{i=i?i[l]=new Array(4):t._root=new Array(4),(f=n>=(o=(v+y)/2))?v=o:y=o,(s=e>=(a=(g+_)/2))?g=a:_=a}while((l=s<<1|f)==(h=(c>=a)<<1|u>=o));return i[h]=d,i[l]=p,t}function ra(t,n,e,r,i){this.node=t,this.x0=n,this.y0=e,this.x1=r,this.y1=i}function ia(t){return t[0]}function oa(t){return t[1]}function aa(t,n,e){var r=new ua(null==n?ia:n,null==e?oa:e,NaN,NaN,NaN,NaN);return null==t?r:r.addAll(t)}function ua(t,n,e,r,i,o){this._x=t,this._y=n,this._x0=e,this._y0=r,this._x1=i,this._y1=o,this._root=void 0}function ca(t){for(var n={data:t.data},e=n;t=t.next;)e=e.next={data:t.data};return n}var fa=aa.prototype=ua.prototype;function sa(t){return t.x+t.vx}function la(t){return t.y+t.vy}function ha(t){return t.index}function da(t,n){var e=t.get(n);if(!e)throw new Error("missing: "+n);return e}function pa(t){return t.x}function va(t){return t.y}fa.copy=function(){var t,n,e=new ua(this._x,this._y,this._x0,this._y0,this._x1,this._y1),r=this._root;if(!r)return e;if(!r.length)return e._root=ca(r),e;for(t=[{source:r,target:e._root=new Array(4)}];r=t.pop();)for(var i=0;i<4;++i)(n=r.source[i])&&(n.length?t.push({source:n,target:r.target[i]=new Array(4)}):r.target[i]=ca(n));return e},fa.add=function(t){var n=+this._x.call(null,t),e=+this._y.call(null,t);return ea(this.cover(n,e),n,e,t)},fa.addAll=function(t){var n,e,r,i,o=t.length,a=new Array(o),u=new Array(o),c=1/0,f=1/0,s=-1/0,l=-1/0;for(e=0;es&&(s=r),il&&(l=i));if(c>s||f>l)return this;for(this.cover(c,f).cover(s,l),e=0;et||t>=i||r>n||n>=o;)switch(u=(nh||(o=c.y0)>d||(a=c.x1)=y)<<1|t>=g)&&(c=p[p.length-1],p[p.length-1]=p[p.length-1-f],p[p.length-1-f]=c)}else{var _=t-+this._x.call(null,v.data),b=n-+this._y.call(null,v.data),m=_*_+b*b;if(m=(u=(p+g)/2))?p=u:g=u,(s=a>=(c=(v+y)/2))?v=c:y=c,n=d,!(d=d[l=s<<1|f]))return this;if(!d.length)break;(n[l+1&3]||n[l+2&3]||n[l+3&3])&&(e=n,h=l)}for(;d.data!==t;)if(r=d,!(d=d.next))return this;return(i=d.next)&&delete d.next,r?(i?r.next=i:delete r.next,this):n?(i?n[l]=i:delete n[l],(d=n[0]||n[1]||n[2]||n[3])&&d===(n[3]||n[2]||n[1]||n[0])&&!d.length&&(e?e[h]=d:this._root=d),this):(this._root=i,this)},fa.removeAll=function(t){for(var n=0,e=t.length;n1?r[0]+r.slice(2):r,+t.slice(e+1)]}function ba(t){return(t=_a(Math.abs(t)))?t[1]:NaN}var ma,xa=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function wa(t){return new Ma(t)}function Ma(t){if(!(n=xa.exec(t)))throw new Error("invalid format: "+t);var n;this.fill=n[1]||" ",this.align=n[2]||">",this.sign=n[3]||"-",this.symbol=n[4]||"",this.zero=!!n[5],this.width=n[6]&&+n[6],this.comma=!!n[7],this.precision=n[8]&&+n[8].slice(1),this.trim=!!n[9],this.type=n[10]||""}function Na(t,n){var e=_a(t,n);if(!e)return t+"";var r=e[0],i=e[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")}wa.prototype=Ma.prototype,Ma.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(null==this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(null==this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var Aa={"%":function(t,n){return(100*t).toFixed(n)},b:function(t){return Math.round(t).toString(2)},c:function(t){return t+""},d:function(t){return Math.round(t).toString(10)},e:function(t,n){return t.toExponential(n)},f:function(t,n){return t.toFixed(n)},g:function(t,n){return t.toPrecision(n)},o:function(t){return Math.round(t).toString(8)},p:function(t,n){return Na(100*t,n)},r:Na,s:function(t,n){var e=_a(t,n);if(!e)return t+"";var r=e[0],i=e[1],o=i-(ma=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,a=r.length;return o===a?r:o>a?r+new Array(o-a+1).join("0"):o>0?r.slice(0,o)+"."+r.slice(o):"0."+new Array(1-o).join("0")+_a(t,Math.max(0,n+o-1))[0]},X:function(t){return Math.round(t).toString(16).toUpperCase()},x:function(t){return Math.round(t).toString(16)}};function Ta(t){return t}var Sa,ka=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];function Ea(t){var n,e,r=t.grouping&&t.thousands?(n=t.grouping,e=t.thousands,function(t,r){for(var i=t.length,o=[],a=0,u=n[0],c=0;i>0&&u>0&&(c+u+1>r&&(u=Math.max(1,r-c)),o.push(t.substring(i-=u,i+u)),!((c+=u+1)>r));)u=n[a=(a+1)%n.length];return o.reverse().join(e)}):Ta,i=t.currency,o=t.decimal,a=t.numerals?function(t){return function(n){return n.replace(/[0-9]/g,function(n){return t[+n]})}}(t.numerals):Ta,u=t.percent||"%";function c(t){var n=(t=wa(t)).fill,e=t.align,c=t.sign,f=t.symbol,s=t.zero,l=t.width,h=t.comma,d=t.precision,p=t.trim,v=t.type;"n"===v?(h=!0,v="g"):Aa[v]||(null==d&&(d=12),p=!0,v="g"),(s||"0"===n&&"="===e)&&(s=!0,n="0",e="=");var g="$"===f?i[0]:"#"===f&&/[boxX]/.test(v)?"0"+v.toLowerCase():"",y="$"===f?i[1]:/[%p]/.test(v)?u:"",_=Aa[v],b=/[defgprs%]/.test(v);function m(t){var i,u,f,m=g,x=y;if("c"===v)x=_(t)+x,t="";else{var w=(t=+t)<0;if(t=_(Math.abs(t),d),p&&(t=function(t){t:for(var n,e=t.length,r=1,i=-1;r0){if(!+t[r])break t;i=0}}return i>0?t.slice(0,i)+t.slice(n+1):t}(t)),w&&0==+t&&(w=!1),m=(w?"("===c?c:"-":"-"===c||"("===c?"":c)+m,x=("s"===v?ka[8+ma/3]:"")+x+(w&&"("===c?")":""),b)for(i=-1,u=t.length;++i(f=t.charCodeAt(i))||f>57){x=(46===f?o+t.slice(i+1):t.slice(i))+x,t=t.slice(0,i);break}}h&&!s&&(t=r(t,1/0));var M=m.length+t.length+x.length,N=M>1)+m+t+x+N.slice(M);break;default:t=N+m+t+x}return a(t)}return d=null==d?6:/[gprs]/.test(v)?Math.max(1,Math.min(21,d)):Math.max(0,Math.min(20,d)),m.toString=function(){return t+""},m}return{format:c,formatPrefix:function(t,n){var e=c(((t=wa(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor(ba(n)/3))),i=Math.pow(10,-r),o=ka[8+r/3];return function(t){return e(i*t)+o}}}}function Ca(n){return Sa=Ea(n),t.format=Sa.format,t.formatPrefix=Sa.formatPrefix,Sa}function Pa(t){return Math.max(0,-ba(Math.abs(t)))}function za(t,n){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(ba(n)/3)))-ba(Math.abs(t)))}function Ra(t,n){return t=Math.abs(t),n=Math.abs(n)-t,Math.max(0,ba(n)-ba(t))+1}function Da(){return new qa}function qa(){this.reset()}Ca({decimal:".",thousands:",",grouping:[3],currency:["$",""]}),qa.prototype={constructor:qa,reset:function(){this.s=this.t=0},add:function(t){Ua(La,t,this.t),Ua(this,La.s,this.s),this.s?this.t+=La.t:this.s=La.t},valueOf:function(){return this.s}};var La=new qa;function Ua(t,n,e){var r=t.s=n+e,i=r-n,o=r-i;t.t=n-o+(e-i)}var Oa=1e-6,Ba=1e-12,Ya=Math.PI,Fa=Ya/2,Ia=Ya/4,ja=2*Ya,Ha=180/Ya,Xa=Ya/180,Ga=Math.abs,Va=Math.atan,$a=Math.atan2,Wa=Math.cos,Za=Math.ceil,Qa=Math.exp,Ja=Math.log,Ka=Math.pow,tu=Math.sin,nu=Math.sign||function(t){return t>0?1:t<0?-1:0},eu=Math.sqrt,ru=Math.tan;function iu(t){return t>1?0:t<-1?Ya:Math.acos(t)}function ou(t){return t>1?Fa:t<-1?-Fa:Math.asin(t)}function au(t){return(t=tu(t/2))*t}function uu(){}function cu(t,n){t&&su.hasOwnProperty(t.type)&&su[t.type](t,n)}var fu={Feature:function(t,n){cu(t.geometry,n)},FeatureCollection:function(t,n){for(var e=t.features,r=-1,i=e.length;++r=0?1:-1,i=r*e,o=Wa(n=(n*=Xa)/2+Ia),a=tu(n),u=_u*a,c=yu*o+u*Wa(i),f=u*r*tu(i);bu.add($a(f,c)),gu=t,yu=o,_u=a}function Tu(t){return[$a(t[1],t[0]),ou(t[2])]}function Su(t){var n=t[0],e=t[1],r=Wa(e);return[r*Wa(n),r*tu(n),tu(e)]}function ku(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]}function Eu(t,n){return[t[1]*n[2]-t[2]*n[1],t[2]*n[0]-t[0]*n[2],t[0]*n[1]-t[1]*n[0]]}function Cu(t,n){t[0]+=n[0],t[1]+=n[1],t[2]+=n[2]}function Pu(t,n){return[t[0]*n,t[1]*n,t[2]*n]}function zu(t){var n=eu(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=n,t[1]/=n,t[2]/=n}var Ru,Du,qu,Lu,Uu,Ou,Bu,Yu,Fu,Iu,ju,Hu,Xu,Gu,Vu,$u,Wu,Zu,Qu,Ju,Ku,tc,nc,ec,rc,ic,oc=Da(),ac={point:uc,lineStart:fc,lineEnd:sc,polygonStart:function(){ac.point=lc,ac.lineStart=hc,ac.lineEnd=dc,oc.reset(),xu.polygonStart()},polygonEnd:function(){xu.polygonEnd(),ac.point=uc,ac.lineStart=fc,ac.lineEnd=sc,bu<0?(Ru=-(qu=180),Du=-(Lu=90)):oc>Oa?Lu=90:oc<-Oa&&(Du=-90),Iu[0]=Ru,Iu[1]=qu}};function uc(t,n){Fu.push(Iu=[Ru=t,qu=t]),nLu&&(Lu=n)}function cc(t,n){var e=Su([t*Xa,n*Xa]);if(Yu){var r=Eu(Yu,e),i=Eu([r[1],-r[0],0],r);zu(i),i=Tu(i);var o,a=t-Uu,u=a>0?1:-1,c=i[0]*Ha*u,f=Ga(a)>180;f^(u*UuLu&&(Lu=o):f^(u*Uu<(c=(c+360)%360-180)&&cLu&&(Lu=n)),f?tpc(Ru,qu)&&(qu=t):pc(t,qu)>pc(Ru,qu)&&(Ru=t):qu>=Ru?(tqu&&(qu=t)):t>Uu?pc(Ru,t)>pc(Ru,qu)&&(qu=t):pc(t,qu)>pc(Ru,qu)&&(Ru=t)}else Fu.push(Iu=[Ru=t,qu=t]);nLu&&(Lu=n),Yu=e,Uu=t}function fc(){ac.point=cc}function sc(){Iu[0]=Ru,Iu[1]=qu,ac.point=uc,Yu=null}function lc(t,n){if(Yu){var e=t-Uu;oc.add(Ga(e)>180?e+(e>0?360:-360):e)}else Ou=t,Bu=n;xu.point(t,n),cc(t,n)}function hc(){xu.lineStart()}function dc(){lc(Ou,Bu),xu.lineEnd(),Ga(oc)>Oa&&(Ru=-(qu=180)),Iu[0]=Ru,Iu[1]=qu,Yu=null}function pc(t,n){return(n-=t)<0?n+360:n}function vc(t,n){return t[0]-n[0]}function gc(t,n){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nYa?t+Math.round(-t/ja)*ja:t,n]}function Pc(t,n,e){return(t%=ja)?n||e?Ec(Rc(t),Dc(n,e)):Rc(t):n||e?Dc(n,e):Cc}function zc(t){return function(n,e){return[(n+=t)>Ya?n-ja:n<-Ya?n+ja:n,e]}}function Rc(t){var n=zc(t);return n.invert=zc(-t),n}function Dc(t,n){var e=Wa(t),r=tu(t),i=Wa(n),o=tu(n);function a(t,n){var a=Wa(n),u=Wa(t)*a,c=tu(t)*a,f=tu(n),s=f*e+u*r;return[$a(c*i-s*o,u*e-f*r),ou(s*i+c*o)]}return a.invert=function(t,n){var a=Wa(n),u=Wa(t)*a,c=tu(t)*a,f=tu(n),s=f*i-c*o;return[$a(c*i+f*o,u*e+s*r),ou(s*e-u*r)]},a}function qc(t){function n(n){return(n=t(n[0]*Xa,n[1]*Xa))[0]*=Ha,n[1]*=Ha,n}return t=Pc(t[0]*Xa,t[1]*Xa,t.length>2?t[2]*Xa:0),n.invert=function(n){return(n=t.invert(n[0]*Xa,n[1]*Xa))[0]*=Ha,n[1]*=Ha,n},n}function Lc(t,n,e,r,i,o){if(e){var a=Wa(n),u=tu(n),c=r*e;null==i?(i=n+r*ja,o=n-c/2):(i=Uc(a,i),o=Uc(a,o),(r>0?io)&&(i+=r*ja));for(var f,s=i;r>0?s>o:s1&&n.push(n.pop().concat(n.shift()))},result:function(){var e=n;return n=[],t=null,e}}}function Bc(t,n){return Ga(t[0]-n[0])=0;--o)i.point((s=f[o])[0],s[1]);else r(h.x,h.p.x,-1,i);h=h.p}f=(h=h.o).z,d=!d}while(!h.v);i.lineEnd()}}}function Ic(t){if(n=t.length){for(var n,e,r=0,i=t[0];++r=0?1:-1,A=N*M,T=A>Ya,S=v*x;if(jc.add($a(S*N*tu(A),g*w+S*Wa(A))),a+=T?M+N*ja:M,T^d>=e^b>=e){var k=Eu(Su(h),Su(_));zu(k);var E=Eu(o,k);zu(E);var C=(T^M>=0?-1:1)*ou(E[2]);(r>C||r===C&&(k[0]||k[1]))&&(u+=T^M>=0?1:-1)}}return(a<-Oa||a0){for(l||(i.polygonStart(),l=!0),i.lineStart(),t=0;t1&&2&c&&h.push(h.pop().concat(h.shift())),a.push(h.filter(Gc))}return h}}function Gc(t){return t.length>1}function Vc(t,n){return((t=t.x)[0]<0?t[1]-Fa-Oa:Fa-t[1])-((n=n.x)[0]<0?n[1]-Fa-Oa:Fa-n[1])}var $c=Xc(function(){return!0},function(t){var n,e=NaN,r=NaN,i=NaN;return{lineStart:function(){t.lineStart(),n=1},point:function(o,a){var u=o>0?Ya:-Ya,c=Ga(o-e);Ga(c-Ya)0?Fa:-Fa),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(u,r),t.point(o,r),n=0):i!==u&&c>=Ya&&(Ga(e-i)Oa?Va((tu(n)*(o=Wa(r))*tu(e)-tu(r)*(i=Wa(n))*tu(t))/(i*o*a)):(n+r)/2}(e,r,o,a),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(u,r),n=0),t.point(e=o,r=a),i=u},lineEnd:function(){t.lineEnd(),e=r=NaN},clean:function(){return 2-n}}},function(t,n,e,r){var i;if(null==t)i=e*Fa,r.point(-Ya,i),r.point(0,i),r.point(Ya,i),r.point(Ya,0),r.point(Ya,-i),r.point(0,-i),r.point(-Ya,-i),r.point(-Ya,0),r.point(-Ya,i);else if(Ga(t[0]-n[0])>Oa){var o=t[0]0,i=Ga(n)>Oa;function o(t,e){return Wa(t)*Wa(e)>n}function a(t,e,r){var i=[1,0,0],o=Eu(Su(t),Su(e)),a=ku(o,o),u=o[0],c=a-u*u;if(!c)return!r&&t;var f=n*a/c,s=-n*u/c,l=Eu(i,o),h=Pu(i,f);Cu(h,Pu(o,s));var d=l,p=ku(h,d),v=ku(d,d),g=p*p-v*(ku(h,h)-1);if(!(g<0)){var y=eu(g),_=Pu(d,(-p-y)/v);if(Cu(_,h),_=Tu(_),!r)return _;var b,m=t[0],x=e[0],w=t[1],M=e[1];x0^_[1]<(Ga(_[0]-m)Ya^(m<=_[0]&&_[0]<=x)){var T=Pu(d,(-p+y)/v);return Cu(T,h),[_,Tu(T)]}}}function u(n,e){var i=r?t:Ya-t,o=0;return n<-i?o|=1:n>i&&(o|=2),e<-i?o|=4:e>i&&(o|=8),o}return Xc(o,function(t){var n,e,c,f,s;return{lineStart:function(){f=c=!1,s=1},point:function(l,h){var d,p=[l,h],v=o(l,h),g=r?v?0:u(l,h):v?u(l+(l<0?Ya:-Ya),h):0;if(!n&&(f=c=v)&&t.lineStart(),v!==c&&(!(d=a(n,p))||Bc(n,d)||Bc(p,d))&&(p[0]+=Oa,p[1]+=Oa,v=o(p[0],p[1])),v!==c)s=0,v?(t.lineStart(),d=a(p,n),t.point(d[0],d[1])):(d=a(n,p),t.point(d[0],d[1]),t.lineEnd()),n=d;else if(i&&n&&r^v){var y;g&e||!(y=a(p,n,!0))||(s=0,r?(t.lineStart(),t.point(y[0][0],y[0][1]),t.point(y[1][0],y[1][1]),t.lineEnd()):(t.point(y[1][0],y[1][1]),t.lineEnd(),t.lineStart(),t.point(y[0][0],y[0][1])))}!v||n&&Bc(n,p)||t.point(p[0],p[1]),n=p,c=v,e=g},lineEnd:function(){c&&t.lineEnd(),n=null},clean:function(){return s|(f&&c)<<1}}},function(n,r,i,o){Lc(o,t,e,i,n,r)},r?[0,-t]:[-Ya,t-Ya])}var Zc=1e9,Qc=-Zc;function Jc(t,n,e,r){function i(i,o){return t<=i&&i<=e&&n<=o&&o<=r}function o(i,o,u,f){var s=0,l=0;if(null==i||(s=a(i,u))!==(l=a(o,u))||c(i,o)<0^u>0)do{f.point(0===s||3===s?t:e,s>1?r:n)}while((s=(s+u+4)%4)!==l);else f.point(o[0],o[1])}function a(r,i){return Ga(r[0]-t)0?0:3:Ga(r[0]-e)0?2:1:Ga(r[1]-n)0?1:0:i>0?3:2}function u(t,n){return c(t.x,n.x)}function c(t,n){var e=a(t,1),r=a(n,1);return e!==r?e-r:0===e?n[1]-t[1]:1===e?t[0]-n[0]:2===e?t[1]-n[1]:n[0]-t[0]}return function(a){var c,f,s,l,h,d,p,v,g,y,_,b=a,m=Oc(),x={point:w,lineStart:function(){x.point=M,f&&f.push(s=[]);y=!0,g=!1,p=v=NaN},lineEnd:function(){c&&(M(l,h),d&&g&&m.rejoin(),c.push(m.result()));x.point=w,g&&b.lineEnd()},polygonStart:function(){b=m,c=[],f=[],_=!0},polygonEnd:function(){var n=function(){for(var n=0,e=0,i=f.length;er&&(h-o)*(r-a)>(d-a)*(t-o)&&++n:d<=r&&(h-o)*(r-a)<(d-a)*(t-o)&&--n;return n}(),e=_&&n,i=(c=T(c)).length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),o(null,null,1,a),a.lineEnd()),i&&Fc(c,u,n,o,a),a.polygonEnd());b=a,c=f=s=null}};function w(t,n){i(t,n)&&b.point(t,n)}function M(o,a){var u=i(o,a);if(f&&s.push([o,a]),y)l=o,h=a,d=u,y=!1,u&&(b.lineStart(),b.point(o,a));else if(u&&g)b.point(o,a);else{var c=[p=Math.max(Qc,Math.min(Zc,p)),v=Math.max(Qc,Math.min(Zc,v))],m=[o=Math.max(Qc,Math.min(Zc,o)),a=Math.max(Qc,Math.min(Zc,a))];!function(t,n,e,r,i,o){var a,u=t[0],c=t[1],f=0,s=1,l=n[0]-u,h=n[1]-c;if(a=e-u,l||!(a>0)){if(a/=l,l<0){if(a0){if(a>s)return;a>f&&(f=a)}if(a=i-u,l||!(a<0)){if(a/=l,l<0){if(a>s)return;a>f&&(f=a)}else if(l>0){if(a0)){if(a/=h,h<0){if(a0){if(a>s)return;a>f&&(f=a)}if(a=o-c,h||!(a<0)){if(a/=h,h<0){if(a>s)return;a>f&&(f=a)}else if(h>0){if(a0&&(t[0]=u+f*l,t[1]=c+f*h),s<1&&(n[0]=u+s*l,n[1]=c+s*h),!0}}}}}(c,m,t,n,e,r)?u&&(b.lineStart(),b.point(o,a),_=!1):(g||(b.lineStart(),b.point(c[0],c[1])),b.point(m[0],m[1]),u||b.lineEnd(),_=!1)}p=o,v=a,g=u}return x}}var Kc,tf,nf,ef=Da(),rf={sphere:uu,point:uu,lineStart:function(){rf.point=af,rf.lineEnd=of},lineEnd:uu,polygonStart:uu,polygonEnd:uu};function of(){rf.point=rf.lineEnd=uu}function af(t,n){Kc=t*=Xa,tf=tu(n*=Xa),nf=Wa(n),rf.point=uf}function uf(t,n){t*=Xa;var e=tu(n*=Xa),r=Wa(n),i=Ga(t-Kc),o=Wa(i),a=r*tu(i),u=nf*e-tf*r*o,c=tf*e+nf*r*o;ef.add($a(eu(a*a+u*u),c)),Kc=t,tf=e,nf=r}function cf(t){return ef.reset(),du(t,rf),+ef}var ff=[null,null],sf={type:"LineString",coordinates:ff};function lf(t,n){return ff[0]=t,ff[1]=n,cf(sf)}var hf={Feature:function(t,n){return pf(t.geometry,n)},FeatureCollection:function(t,n){for(var e=t.features,r=-1,i=e.length;++rOa}).map(c)).concat(g(Za(o/d)*d,i,d).filter(function(t){return Ga(t%v)>Oa}).map(f))}return _.lines=function(){return b().map(function(t){return{type:"LineString",coordinates:t}})},_.outline=function(){return{type:"Polygon",coordinates:[s(r).concat(l(a).slice(1),s(e).reverse().slice(1),l(u).reverse().slice(1))]}},_.extent=function(t){return arguments.length?_.extentMajor(t).extentMinor(t):_.extentMinor()},_.extentMajor=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],u=+t[0][1],a=+t[1][1],r>e&&(t=r,r=e,e=t),u>a&&(t=u,u=a,a=t),_.precision(y)):[[r,u],[e,a]]},_.extentMinor=function(e){return arguments.length?(n=+e[0][0],t=+e[1][0],o=+e[0][1],i=+e[1][1],n>t&&(e=n,n=t,t=e),o>i&&(e=o,o=i,i=e),_.precision(y)):[[n,o],[t,i]]},_.step=function(t){return arguments.length?_.stepMajor(t).stepMinor(t):_.stepMinor()},_.stepMajor=function(t){return arguments.length?(p=+t[0],v=+t[1],_):[p,v]},_.stepMinor=function(t){return arguments.length?(h=+t[0],d=+t[1],_):[h,d]},_.precision=function(h){return arguments.length?(y=+h,c=mf(o,i,90),f=xf(n,t,y),s=mf(u,a,90),l=xf(r,e,y),_):y},_.extentMajor([[-180,-90+Oa],[180,90-Oa]]).extentMinor([[-180,-80-Oa],[180,80+Oa]])}function Mf(t){return t}var Nf,Af,Tf,Sf,kf=Da(),Ef=Da(),Cf={point:uu,lineStart:uu,lineEnd:uu,polygonStart:function(){Cf.lineStart=Pf,Cf.lineEnd=Df},polygonEnd:function(){Cf.lineStart=Cf.lineEnd=Cf.point=uu,kf.add(Ga(Ef)),Ef.reset()},result:function(){var t=kf/2;return kf.reset(),t}};function Pf(){Cf.point=zf}function zf(t,n){Cf.point=Rf,Nf=Tf=t,Af=Sf=n}function Rf(t,n){Ef.add(Sf*t-Tf*n),Tf=t,Sf=n}function Df(){Rf(Nf,Af)}var qf=1/0,Lf=qf,Uf=-qf,Of=Uf,Bf={point:function(t,n){tUf&&(Uf=t);nOf&&(Of=n)},lineStart:uu,lineEnd:uu,polygonStart:uu,polygonEnd:uu,result:function(){var t=[[qf,Lf],[Uf,Of]];return Uf=Of=-(Lf=qf=1/0),t}};var Yf,Ff,If,jf,Hf=0,Xf=0,Gf=0,Vf=0,$f=0,Wf=0,Zf=0,Qf=0,Jf=0,Kf={point:ts,lineStart:ns,lineEnd:is,polygonStart:function(){Kf.lineStart=os,Kf.lineEnd=as},polygonEnd:function(){Kf.point=ts,Kf.lineStart=ns,Kf.lineEnd=is},result:function(){var t=Jf?[Zf/Jf,Qf/Jf]:Wf?[Vf/Wf,$f/Wf]:Gf?[Hf/Gf,Xf/Gf]:[NaN,NaN];return Hf=Xf=Gf=Vf=$f=Wf=Zf=Qf=Jf=0,t}};function ts(t,n){Hf+=t,Xf+=n,++Gf}function ns(){Kf.point=es}function es(t,n){Kf.point=rs,ts(If=t,jf=n)}function rs(t,n){var e=t-If,r=n-jf,i=eu(e*e+r*r);Vf+=i*(If+t)/2,$f+=i*(jf+n)/2,Wf+=i,ts(If=t,jf=n)}function is(){Kf.point=ts}function os(){Kf.point=us}function as(){cs(Yf,Ff)}function us(t,n){Kf.point=cs,ts(Yf=If=t,Ff=jf=n)}function cs(t,n){var e=t-If,r=n-jf,i=eu(e*e+r*r);Vf+=i*(If+t)/2,$f+=i*(jf+n)/2,Wf+=i,Zf+=(i=jf*t-If*n)*(If+t),Qf+=i*(jf+n),Jf+=3*i,ts(If=t,jf=n)}function fs(t){this._context=t}fs.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._context.moveTo(t,n),this._point=1;break;case 1:this._context.lineTo(t,n);break;default:this._context.moveTo(t+this._radius,n),this._context.arc(t,n,this._radius,0,ja)}},result:uu};var ss,ls,hs,ds,ps,vs=Da(),gs={point:uu,lineStart:function(){gs.point=ys},lineEnd:function(){ss&&_s(ls,hs),gs.point=uu},polygonStart:function(){ss=!0},polygonEnd:function(){ss=null},result:function(){var t=+vs;return vs.reset(),t}};function ys(t,n){gs.point=_s,ls=ds=t,hs=ps=n}function _s(t,n){ds-=t,ps-=n,vs.add(eu(ds*ds+ps*ps)),ds=t,ps=n}function bs(){this._string=[]}function ms(t){return"m0,"+t+"a"+t+","+t+" 0 1,1 0,"+-2*t+"a"+t+","+t+" 0 1,1 0,"+2*t+"z"}function xs(t){return function(n){var e=new ws;for(var r in t)e[r]=t[r];return e.stream=n,e}}function ws(){}function Ms(t,n,e){var r=t.clipExtent&&t.clipExtent();return t.scale(150).translate([0,0]),null!=r&&t.clipExtent(null),du(e,t.stream(Bf)),n(Bf.result()),null!=r&&t.clipExtent(r),t}function Ns(t,n,e){return Ms(t,function(e){var r=n[1][0]-n[0][0],i=n[1][1]-n[0][1],o=Math.min(r/(e[1][0]-e[0][0]),i/(e[1][1]-e[0][1])),a=+n[0][0]+(r-o*(e[1][0]+e[0][0]))/2,u=+n[0][1]+(i-o*(e[1][1]+e[0][1]))/2;t.scale(150*o).translate([a,u])},e)}function As(t,n,e){return Ns(t,[[0,0],n],e)}function Ts(t,n,e){return Ms(t,function(e){var r=+n,i=r/(e[1][0]-e[0][0]),o=(r-i*(e[1][0]+e[0][0]))/2,a=-i*e[0][1];t.scale(150*i).translate([o,a])},e)}function Ss(t,n,e){return Ms(t,function(e){var r=+n,i=r/(e[1][1]-e[0][1]),o=-i*e[0][0],a=(r-i*(e[1][1]+e[0][1]))/2;t.scale(150*i).translate([o,a])},e)}bs.prototype={_radius:4.5,_circle:ms(4.5),pointRadius:function(t){return(t=+t)!==this._radius&&(this._radius=t,this._circle=null),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._string.push("Z"),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._string.push("M",t,",",n),this._point=1;break;case 1:this._string.push("L",t,",",n);break;default:null==this._circle&&(this._circle=ms(this._radius)),this._string.push("M",t,",",n,this._circle)}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}return null}},ws.prototype={constructor:ws,point:function(t,n){this.stream.point(t,n)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};var ks=16,Es=Wa(30*Xa);function Cs(t,n){return+n?function(t,n){function e(r,i,o,a,u,c,f,s,l,h,d,p,v,g){var y=f-r,_=s-i,b=y*y+_*_;if(b>4*n&&v--){var m=a+h,x=u+d,w=c+p,M=eu(m*m+x*x+w*w),N=ou(w/=M),A=Ga(Ga(w)-1)n||Ga((y*E+_*C)/b-.5)>.3||a*h+u*d+c*p2?t[2]%360*Xa:0,S()):[g*Ha,y*Ha,_*Ha]},A.angle=function(t){return arguments.length?(b=t%360*Xa,S()):b*Ha},A.precision=function(t){return arguments.length?(a=Cs(u,N=t*t),k()):eu(N)},A.fitExtent=function(t,n){return Ns(A,t,n)},A.fitSize=function(t,n){return As(A,t,n)},A.fitWidth=function(t,n){return Ts(A,t,n)},A.fitHeight=function(t,n){return Ss(A,t,n)},function(){return n=t.apply(this,arguments),A.invert=n.invert&&T,S()}}function qs(t){var n=0,e=Ya/3,r=Ds(t),i=r(n,e);return i.parallels=function(t){return arguments.length?r(n=t[0]*Xa,e=t[1]*Xa):[n*Ha,e*Ha]},i}function Ls(t,n){var e=tu(t),r=(e+tu(n))/2;if(Ga(r)0?n<-Fa+Oa&&(n=-Fa+Oa):n>Fa-Oa&&(n=Fa-Oa);var e=i/Ka(Xs(n),r);return[e*tu(r*t),i-e*Wa(r*t)]}return o.invert=function(t,n){var e=i-n,o=nu(r)*eu(t*t+e*e);return[$a(t,Ga(e))/r*nu(e),2*Va(Ka(i/o,1/r))-Fa]},o}function Vs(t,n){return[t,n]}function $s(t,n){var e=Wa(t),r=t===n?tu(t):(e-Wa(n))/(n-t),i=e/r+t;if(Ga(r)=0;)n+=e[r].value;else n=1;t.value=n}function ll(t,n){var e,r,i,o,a,u=new vl(t),c=+t.value&&(u.value=t.value),f=[u];for(null==n&&(n=hl);e=f.pop();)if(c&&(e.value=+e.data.value),(i=n(e.data))&&(a=i.length))for(e.children=new Array(a),o=a-1;o>=0;--o)f.push(r=e.children[o]=new vl(i[o])),r.parent=e,r.depth=e.depth+1;return u.eachBefore(pl)}function hl(t){return t.children}function dl(t){t.data=t.data.data}function pl(t){var n=0;do{t.height=n}while((t=t.parent)&&t.height<++n)}function vl(t){this.data=t,this.depth=this.height=0,this.parent=null}tl.invert=function(t,n){for(var e,r=n,i=r*r,o=i*i*i,a=0;a<12&&(o=(i=(r-=e=(r*(Ws+Zs*i+o*(Qs+Js*i))-n)/(Ws+3*Zs*i+o*(7*Qs+9*Js*i)))*r)*i*i,!(Ga(e)Oa&&--i>0);return[t/(.8707+(o=r*r)*(o*(o*o*o*(.003971-.001529*o)-.013791)-.131979)),r]},il.invert=Ys(ou),ol.invert=Ys(function(t){return 2*Va(t)}),al.invert=function(t,n){return[-n,2*Va(Qa(t))-Fa]},vl.prototype=ll.prototype={constructor:vl,count:function(){return this.eachAfter(sl)},each:function(t){var n,e,r,i,o=this,a=[o];do{for(n=a.reverse(),a=[];o=n.pop();)if(t(o),e=o.children)for(r=0,i=e.length;r=0;--e)i.push(n[e]);return this},sum:function(t){return this.eachAfter(function(n){for(var e=+t(n.data)||0,r=n.children,i=r&&r.length;--i>=0;)e+=r[i].value;n.value=e})},sort:function(t){return this.eachBefore(function(n){n.children&&n.children.sort(t)})},path:function(t){for(var n=this,e=function(t,n){if(t===n)return t;var e=t.ancestors(),r=n.ancestors(),i=null;for(t=e.pop(),n=r.pop();t===n;)i=t,t=e.pop(),n=r.pop();return i}(n,t),r=[n];n!==e;)n=n.parent,r.push(n);for(var i=r.length;t!==e;)r.splice(i,0,t),t=t.parent;return r},ancestors:function(){for(var t=this,n=[t];t=t.parent;)n.push(t);return n},descendants:function(){var t=[];return this.each(function(n){t.push(n)}),t},leaves:function(){var t=[];return this.eachBefore(function(n){n.children||t.push(n)}),t},links:function(){var t=this,n=[];return t.each(function(e){e!==t&&n.push({source:e.parent,target:e})}),n},copy:function(){return ll(this).eachBefore(dl)}};var gl=Array.prototype.slice;function yl(t){for(var n,e,r=0,i=(t=function(t){for(var n,e,r=t.length;r;)e=Math.random()*r--|0,n=t[r],t[r]=t[e],t[e]=n;return t}(gl.call(t))).length,o=[];r0&&e*e>r*r+i*i}function xl(t,n){for(var e=0;e(a*=a)?(r=(f+a-i)/(2*f),o=Math.sqrt(Math.max(0,a/f-r*r)),e.x=t.x-r*u-o*c,e.y=t.y-r*c+o*u):(r=(f+i-a)/(2*f),o=Math.sqrt(Math.max(0,i/f-r*r)),e.x=n.x+r*u-o*c,e.y=n.y+r*c+o*u)):(e.x=n.x+e.r,e.y=n.y)}function Tl(t,n){var e=t.r+n.r-1e-6,r=n.x-t.x,i=n.y-t.y;return e>0&&e*e>r*r+i*i}function Sl(t){var n=t._,e=t.next._,r=n.r+e.r,i=(n.x*e.r+e.x*n.r)/r,o=(n.y*e.r+e.y*n.r)/r;return i*i+o*o}function kl(t){this._=t,this.next=null,this.previous=null}function El(t){if(!(i=t.length))return 0;var n,e,r,i,o,a,u,c,f,s,l;if((n=t[0]).x=0,n.y=0,!(i>1))return n.r;if(e=t[1],n.x=-e.r,e.x=n.r,e.y=0,!(i>2))return n.r+e.r;Al(e,n,r=t[2]),n=new kl(n),e=new kl(e),r=new kl(r),n.next=r.previous=e,e.next=n.previous=r,r.next=e.previous=n;t:for(u=3;uh&&(h=u),g=s*s*v,(d=Math.max(h/g,g/l))>p){s-=u;break}p=d}y.push(a={value:s,dice:c1?n:1)},e}(Ql);var th=function t(n){function e(t,e,r,i,o){if((a=t._squarify)&&a.ratio===n)for(var a,u,c,f,s,l=-1,h=a.length,d=t.value;++l1?n:1)},e}(Ql);function nh(t,n){return t[0]-n[0]||t[1]-n[1]}function eh(t){for(var n,e,r,i=t.length,o=[0,1],a=2,u=2;u1&&(n=t[o[a-2]],e=t[o[a-1]],r=t[u],(e[0]-n[0])*(r[1]-n[1])-(e[1]-n[1])*(r[0]-n[0])<=0);)--a;o[a++]=u}return o.slice(0,a)}function rh(){return Math.random()}var ih=function t(n){function e(t,e){return t=null==t?0:+t,e=null==e?1:+e,1===arguments.length?(e=t,t=0):e-=t,function(){return n()*e+t}}return e.source=t,e}(rh),oh=function t(n){function e(t,e){var r,i;return t=null==t?0:+t,e=null==e?1:+e,function(){var o;if(null!=r)o=r,r=null;else do{r=2*n()-1,o=2*n()-1,i=r*r+o*o}while(!i||i>1);return t+e*o*Math.sqrt(-2*Math.log(i)/i)}}return e.source=t,e}(rh),ah=function t(n){function e(){var t=oh.source(n).apply(this,arguments);return function(){return Math.exp(t())}}return e.source=t,e}(rh),uh=function t(n){function e(t){return function(){for(var e=0,r=0;rr&&(n=e,e=r,r=n),function(t){return Math.max(e,Math.min(r,t))}}function Mh(t,n,e){var r=t[0],i=t[1],o=n[0],a=n[1];return i2?Nh:Mh,i=o=null,l}function l(n){return isNaN(n=+n)?e:(i||(i=r(a.map(t),u,c)))(t(f(n)))}return l.invert=function(e){return f(n((o||(o=r(u,a.map(t),he)))(e)))},l.domain=function(t){return arguments.length?(a=dh.call(t,_h),f===mh||(f=wh(a)),s()):a.slice()},l.range=function(t){return arguments.length?(u=ph.call(t),s()):u.slice()},l.rangeRound=function(t){return u=ph.call(t),c=_e,s()},l.clamp=function(t){return arguments.length?(f=t?wh(a):mh,l):f!==mh},l.interpolate=function(t){return arguments.length?(c=t,s()):c},l.unknown=function(t){return arguments.length?(e=t,l):e},function(e,r){return t=e,n=r,s()}}function Sh(t,n){return Th()(t,n)}function kh(n,e,r,i){var o,a=w(n,e,r);switch((i=wa(null==i?",f":i)).type){case"s":var u=Math.max(Math.abs(n),Math.abs(e));return null!=i.precision||isNaN(o=za(a,u))||(i.precision=o),t.formatPrefix(i,u);case"":case"e":case"g":case"p":case"r":null!=i.precision||isNaN(o=Ra(a,Math.max(Math.abs(n),Math.abs(e))))||(i.precision=o-("e"===i.type));break;case"f":case"%":null!=i.precision||isNaN(o=Pa(a))||(i.precision=o-2*("%"===i.type))}return t.format(i)}function Eh(t){var n=t.domain;return t.ticks=function(t){var e=n();return m(e[0],e[e.length-1],null==t?10:t)},t.tickFormat=function(t,e){var r=n();return kh(r[0],r[r.length-1],null==t?10:t,e)},t.nice=function(e){null==e&&(e=10);var r,i=n(),o=0,a=i.length-1,u=i[o],c=i[a];return c0?r=x(u=Math.floor(u/r)*r,c=Math.ceil(c/r)*r,e):r<0&&(r=x(u=Math.ceil(u*r)/r,c=Math.floor(c*r)/r,e)),r>0?(i[o]=Math.floor(u/r)*r,i[a]=Math.ceil(c/r)*r,n(i)):r<0&&(i[o]=Math.ceil(u*r)/r,i[a]=Math.floor(c*r)/r,n(i)),t},t}function Ch(t,n){var e,r=0,i=(t=t.slice()).length-1,o=t[r],a=t[i];return a0){for(;hc)break;v.push(l)}}else for(;h=1;--s)if(!((l=f*s)c)break;v.push(l)}}else v=m(h,d,Math.min(d-h,p)).map(r);return n?v.reverse():v},i.tickFormat=function(n,o){if(null==o&&(o=10===a?".0e":","),"function"!=typeof o&&(o=t.format(o)),n===1/0)return o;null==n&&(n=10);var u=Math.max(1,a*n/i.ticks().length);return function(t){var n=t/r(Math.round(e(t)));return n*a0))return u;do{u.push(a=new Date(+e)),n(e,o),t(e)}while(a=n)for(;t(n),!e(n);)n.setTime(n-1)},function(t,r){if(t>=t)if(r<0)for(;++r<=0;)for(;n(t,-1),!e(t););else for(;--r>=0;)for(;n(t,1),!e(t););})},e&&(i.count=function(n,r){return Gh.setTime(+n),Vh.setTime(+r),t(Gh),t(Vh),Math.floor(e(Gh,Vh))},i.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?function(n){return r(n)%t==0}:function(n){return i.count(0,n)%t==0}):i:null}),i}var Wh=$h(function(){},function(t,n){t.setTime(+t+n)},function(t,n){return n-t});Wh.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?$h(function(n){n.setTime(Math.floor(n/t)*t)},function(n,e){n.setTime(+n+e*t)},function(n,e){return(e-n)/t}):Wh:null};var Zh=Wh.range,Qh=6e4,Jh=6048e5,Kh=$h(function(t){t.setTime(t-t.getMilliseconds())},function(t,n){t.setTime(+t+1e3*n)},function(t,n){return(n-t)/1e3},function(t){return t.getUTCSeconds()}),td=Kh.range,nd=$h(function(t){t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds())},function(t,n){t.setTime(+t+n*Qh)},function(t,n){return(n-t)/Qh},function(t){return t.getMinutes()}),ed=nd.range,rd=$h(function(t){t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds()-t.getMinutes()*Qh)},function(t,n){t.setTime(+t+36e5*n)},function(t,n){return(n-t)/36e5},function(t){return t.getHours()}),id=rd.range,od=$h(function(t){t.setHours(0,0,0,0)},function(t,n){t.setDate(t.getDate()+n)},function(t,n){return(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*Qh)/864e5},function(t){return t.getDate()-1}),ad=od.range;function ud(t){return $h(function(n){n.setDate(n.getDate()-(n.getDay()+7-t)%7),n.setHours(0,0,0,0)},function(t,n){t.setDate(t.getDate()+7*n)},function(t,n){return(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*Qh)/Jh})}var cd=ud(0),fd=ud(1),sd=ud(2),ld=ud(3),hd=ud(4),dd=ud(5),pd=ud(6),vd=cd.range,gd=fd.range,yd=sd.range,_d=ld.range,bd=hd.range,md=dd.range,xd=pd.range,wd=$h(function(t){t.setDate(1),t.setHours(0,0,0,0)},function(t,n){t.setMonth(t.getMonth()+n)},function(t,n){return n.getMonth()-t.getMonth()+12*(n.getFullYear()-t.getFullYear())},function(t){return t.getMonth()}),Md=wd.range,Nd=$h(function(t){t.setMonth(0,1),t.setHours(0,0,0,0)},function(t,n){t.setFullYear(t.getFullYear()+n)},function(t,n){return n.getFullYear()-t.getFullYear()},function(t){return t.getFullYear()});Nd.every=function(t){return isFinite(t=Math.floor(t))&&t>0?$h(function(n){n.setFullYear(Math.floor(n.getFullYear()/t)*t),n.setMonth(0,1),n.setHours(0,0,0,0)},function(n,e){n.setFullYear(n.getFullYear()+e*t)}):null};var Ad=Nd.range,Td=$h(function(t){t.setUTCSeconds(0,0)},function(t,n){t.setTime(+t+n*Qh)},function(t,n){return(n-t)/Qh},function(t){return t.getUTCMinutes()}),Sd=Td.range,kd=$h(function(t){t.setUTCMinutes(0,0,0)},function(t,n){t.setTime(+t+36e5*n)},function(t,n){return(n-t)/36e5},function(t){return t.getUTCHours()}),Ed=kd.range,Cd=$h(function(t){t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCDate(t.getUTCDate()+n)},function(t,n){return(n-t)/864e5},function(t){return t.getUTCDate()-1}),Pd=Cd.range;function zd(t){return $h(function(n){n.setUTCDate(n.getUTCDate()-(n.getUTCDay()+7-t)%7),n.setUTCHours(0,0,0,0)},function(t,n){t.setUTCDate(t.getUTCDate()+7*n)},function(t,n){return(n-t)/Jh})}var Rd=zd(0),Dd=zd(1),qd=zd(2),Ld=zd(3),Ud=zd(4),Od=zd(5),Bd=zd(6),Yd=Rd.range,Fd=Dd.range,Id=qd.range,jd=Ld.range,Hd=Ud.range,Xd=Od.range,Gd=Bd.range,Vd=$h(function(t){t.setUTCDate(1),t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCMonth(t.getUTCMonth()+n)},function(t,n){return n.getUTCMonth()-t.getUTCMonth()+12*(n.getUTCFullYear()-t.getUTCFullYear())},function(t){return t.getUTCMonth()}),$d=Vd.range,Wd=$h(function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCFullYear(t.getUTCFullYear()+n)},function(t,n){return n.getUTCFullYear()-t.getUTCFullYear()},function(t){return t.getUTCFullYear()});Wd.every=function(t){return isFinite(t=Math.floor(t))&&t>0?$h(function(n){n.setUTCFullYear(Math.floor(n.getUTCFullYear()/t)*t),n.setUTCMonth(0,1),n.setUTCHours(0,0,0,0)},function(n,e){n.setUTCFullYear(n.getUTCFullYear()+e*t)}):null};var Zd=Wd.range;function Qd(t){if(0<=t.y&&t.y<100){var n=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return n.setFullYear(t.y),n}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function Jd(t){if(0<=t.y&&t.y<100){var n=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return n.setUTCFullYear(t.y),n}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function Kd(t){return{y:t,m:0,d:1,H:0,M:0,S:0,L:0}}function tp(t){var n=t.dateTime,e=t.date,r=t.time,i=t.periods,o=t.days,a=t.shortDays,u=t.months,c=t.shortMonths,f=cp(i),s=fp(i),l=cp(o),h=fp(o),d=cp(a),p=fp(a),v=cp(u),g=fp(u),y=cp(c),_=fp(c),b={a:function(t){return a[t.getDay()]},A:function(t){return o[t.getDay()]},b:function(t){return c[t.getMonth()]},B:function(t){return u[t.getMonth()]},c:null,d:Ep,e:Ep,f:Dp,H:Cp,I:Pp,j:zp,L:Rp,m:qp,M:Lp,p:function(t){return i[+(t.getHours()>=12)]},Q:sv,s:lv,S:Up,u:Op,U:Bp,V:Yp,w:Fp,W:Ip,x:null,X:null,y:jp,Y:Hp,Z:Xp,"%":fv},m={a:function(t){return a[t.getUTCDay()]},A:function(t){return o[t.getUTCDay()]},b:function(t){return c[t.getUTCMonth()]},B:function(t){return u[t.getUTCMonth()]},c:null,d:Gp,e:Gp,f:Qp,H:Vp,I:$p,j:Wp,L:Zp,m:Jp,M:Kp,p:function(t){return i[+(t.getUTCHours()>=12)]},Q:sv,s:lv,S:tv,u:nv,U:ev,V:rv,w:iv,W:ov,x:null,X:null,y:av,Y:uv,Z:cv,"%":fv},x={a:function(t,n,e){var r=d.exec(n.slice(e));return r?(t.w=p[r[0].toLowerCase()],e+r[0].length):-1},A:function(t,n,e){var r=l.exec(n.slice(e));return r?(t.w=h[r[0].toLowerCase()],e+r[0].length):-1},b:function(t,n,e){var r=y.exec(n.slice(e));return r?(t.m=_[r[0].toLowerCase()],e+r[0].length):-1},B:function(t,n,e){var r=v.exec(n.slice(e));return r?(t.m=g[r[0].toLowerCase()],e+r[0].length):-1},c:function(t,e,r){return N(t,n,e,r)},d:bp,e:bp,f:Ap,H:xp,I:xp,j:mp,L:Np,m:_p,M:wp,p:function(t,n,e){var r=f.exec(n.slice(e));return r?(t.p=s[r[0].toLowerCase()],e+r[0].length):-1},Q:Sp,s:kp,S:Mp,u:lp,U:hp,V:dp,w:sp,W:pp,x:function(t,n,r){return N(t,e,n,r)},X:function(t,n,e){return N(t,r,n,e)},y:gp,Y:vp,Z:yp,"%":Tp};function w(t,n){return function(e){var r,i,o,a=[],u=-1,c=0,f=t.length;for(e instanceof Date||(e=new Date(+e));++u53)return null;"w"in o||(o.w=1),"Z"in o?(i=(r=Jd(Kd(o.y))).getUTCDay(),r=i>4||0===i?Dd.ceil(r):Dd(r),r=Cd.offset(r,7*(o.V-1)),o.y=r.getUTCFullYear(),o.m=r.getUTCMonth(),o.d=r.getUTCDate()+(o.w+6)%7):(i=(r=n(Kd(o.y))).getDay(),r=i>4||0===i?fd.ceil(r):fd(r),r=od.offset(r,7*(o.V-1)),o.y=r.getFullYear(),o.m=r.getMonth(),o.d=r.getDate()+(o.w+6)%7)}else("W"in o||"U"in o)&&("w"in o||(o.w="u"in o?o.u%7:"W"in o?1:0),i="Z"in o?Jd(Kd(o.y)).getUTCDay():n(Kd(o.y)).getDay(),o.m=0,o.d="W"in o?(o.w+6)%7+7*o.W-(i+5)%7:o.w+7*o.U-(i+6)%7);return"Z"in o?(o.H+=o.Z/100|0,o.M+=o.Z%100,Jd(o)):n(o)}}function N(t,n,e,r){for(var i,o,a=0,u=n.length,c=e.length;a=c)return-1;if(37===(i=n.charCodeAt(a++))){if(i=n.charAt(a++),!(o=x[i in ep?n.charAt(a++):i])||(r=o(t,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}return b.x=w(e,b),b.X=w(r,b),b.c=w(n,b),m.x=w(e,m),m.X=w(r,m),m.c=w(n,m),{format:function(t){var n=w(t+="",b);return n.toString=function(){return t},n},parse:function(t){var n=M(t+="",Qd);return n.toString=function(){return t},n},utcFormat:function(t){var n=w(t+="",m);return n.toString=function(){return t},n},utcParse:function(t){var n=M(t,Jd);return n.toString=function(){return t},n}}}var np,ep={"-":"",_:" ",0:"0"},rp=/^\s*\d+/,ip=/^%/,op=/[\\^$*+?|[\]().{}]/g;function ap(t,n,e){var r=t<0?"-":"",i=(r?-t:t)+"",o=i.length;return r+(o68?1900:2e3),e+r[0].length):-1}function yp(t,n,e){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(n.slice(e,e+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),e+r[0].length):-1}function _p(t,n,e){var r=rp.exec(n.slice(e,e+2));return r?(t.m=r[0]-1,e+r[0].length):-1}function bp(t,n,e){var r=rp.exec(n.slice(e,e+2));return r?(t.d=+r[0],e+r[0].length):-1}function mp(t,n,e){var r=rp.exec(n.slice(e,e+3));return r?(t.m=0,t.d=+r[0],e+r[0].length):-1}function xp(t,n,e){var r=rp.exec(n.slice(e,e+2));return r?(t.H=+r[0],e+r[0].length):-1}function wp(t,n,e){var r=rp.exec(n.slice(e,e+2));return r?(t.M=+r[0],e+r[0].length):-1}function Mp(t,n,e){var r=rp.exec(n.slice(e,e+2));return r?(t.S=+r[0],e+r[0].length):-1}function Np(t,n,e){var r=rp.exec(n.slice(e,e+3));return r?(t.L=+r[0],e+r[0].length):-1}function Ap(t,n,e){var r=rp.exec(n.slice(e,e+6));return r?(t.L=Math.floor(r[0]/1e3),e+r[0].length):-1}function Tp(t,n,e){var r=ip.exec(n.slice(e,e+1));return r?e+r[0].length:-1}function Sp(t,n,e){var r=rp.exec(n.slice(e));return r?(t.Q=+r[0],e+r[0].length):-1}function kp(t,n,e){var r=rp.exec(n.slice(e));return r?(t.Q=1e3*+r[0],e+r[0].length):-1}function Ep(t,n){return ap(t.getDate(),n,2)}function Cp(t,n){return ap(t.getHours(),n,2)}function Pp(t,n){return ap(t.getHours()%12||12,n,2)}function zp(t,n){return ap(1+od.count(Nd(t),t),n,3)}function Rp(t,n){return ap(t.getMilliseconds(),n,3)}function Dp(t,n){return Rp(t,n)+"000"}function qp(t,n){return ap(t.getMonth()+1,n,2)}function Lp(t,n){return ap(t.getMinutes(),n,2)}function Up(t,n){return ap(t.getSeconds(),n,2)}function Op(t){var n=t.getDay();return 0===n?7:n}function Bp(t,n){return ap(cd.count(Nd(t),t),n,2)}function Yp(t,n){var e=t.getDay();return t=e>=4||0===e?hd(t):hd.ceil(t),ap(hd.count(Nd(t),t)+(4===Nd(t).getDay()),n,2)}function Fp(t){return t.getDay()}function Ip(t,n){return ap(fd.count(Nd(t),t),n,2)}function jp(t,n){return ap(t.getFullYear()%100,n,2)}function Hp(t,n){return ap(t.getFullYear()%1e4,n,4)}function Xp(t){var n=t.getTimezoneOffset();return(n>0?"-":(n*=-1,"+"))+ap(n/60|0,"0",2)+ap(n%60,"0",2)}function Gp(t,n){return ap(t.getUTCDate(),n,2)}function Vp(t,n){return ap(t.getUTCHours(),n,2)}function $p(t,n){return ap(t.getUTCHours()%12||12,n,2)}function Wp(t,n){return ap(1+Cd.count(Wd(t),t),n,3)}function Zp(t,n){return ap(t.getUTCMilliseconds(),n,3)}function Qp(t,n){return Zp(t,n)+"000"}function Jp(t,n){return ap(t.getUTCMonth()+1,n,2)}function Kp(t,n){return ap(t.getUTCMinutes(),n,2)}function tv(t,n){return ap(t.getUTCSeconds(),n,2)}function nv(t){var n=t.getUTCDay();return 0===n?7:n}function ev(t,n){return ap(Rd.count(Wd(t),t),n,2)}function rv(t,n){var e=t.getUTCDay();return t=e>=4||0===e?Ud(t):Ud.ceil(t),ap(Ud.count(Wd(t),t)+(4===Wd(t).getUTCDay()),n,2)}function iv(t){return t.getUTCDay()}function ov(t,n){return ap(Dd.count(Wd(t),t),n,2)}function av(t,n){return ap(t.getUTCFullYear()%100,n,2)}function uv(t,n){return ap(t.getUTCFullYear()%1e4,n,4)}function cv(){return"+0000"}function fv(){return"%"}function sv(t){return+t}function lv(t){return Math.floor(+t/1e3)}function hv(n){return np=tp(n),t.timeFormat=np.format,t.timeParse=np.parse,t.utcFormat=np.utcFormat,t.utcParse=np.utcParse,np}hv({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var dv=Date.prototype.toISOString?function(t){return t.toISOString()}:t.utcFormat("%Y-%m-%dT%H:%M:%S.%LZ");var pv=+new Date("2000-01-01T00:00:00.000Z")?function(t){var n=new Date(t);return isNaN(n)?null:n}:t.utcParse("%Y-%m-%dT%H:%M:%S.%LZ"),vv=1e3,gv=60*vv,yv=60*gv,_v=24*yv,bv=7*_v,mv=30*_v,xv=365*_v;function wv(t){return new Date(t)}function Mv(t){return t instanceof Date?+t:+new Date(+t)}function Nv(t,n,r,i,o,a,u,c,f){var s=Sh(mh,mh),l=s.invert,h=s.domain,d=f(".%L"),p=f(":%S"),v=f("%I:%M"),g=f("%I %p"),y=f("%a %d"),_=f("%b %d"),b=f("%B"),m=f("%Y"),x=[[u,1,vv],[u,5,5*vv],[u,15,15*vv],[u,30,30*vv],[a,1,gv],[a,5,5*gv],[a,15,15*gv],[a,30,30*gv],[o,1,yv],[o,3,3*yv],[o,6,6*yv],[o,12,12*yv],[i,1,_v],[i,2,2*_v],[r,1,bv],[n,1,mv],[n,3,3*mv],[t,1,xv]];function M(e){return(u(e)=1?fy:t<=-1?-fy:Math.asin(t)}function hy(t){return t.innerRadius}function dy(t){return t.outerRadius}function py(t){return t.startAngle}function vy(t){return t.endAngle}function gy(t){return t&&t.padAngle}function yy(t,n,e,r,i,o,a){var u=t-e,c=n-r,f=(a?o:-o)/ay(u*u+c*c),s=f*c,l=-f*u,h=t+s,d=n+l,p=e+s,v=r+l,g=(h+p)/2,y=(d+v)/2,_=p-h,b=v-d,m=_*_+b*b,x=i-o,w=h*v-p*d,M=(b<0?-1:1)*ay(ry(0,x*x*m-w*w)),N=(w*b-_*M)/m,A=(-w*_-b*M)/m,T=(w*b+_*M)/m,S=(-w*_+b*M)/m,k=N-g,E=A-y,C=T-g,P=S-y;return k*k+E*E>C*C+P*P&&(N=T,A=S),{cx:N,cy:A,x01:-s,y01:-l,x11:N*(i/x-1),y11:A*(i/x-1)}}function _y(t){this._context=t}function by(t){return new _y(t)}function my(t){return t[0]}function xy(t){return t[1]}function wy(){var t=my,n=xy,e=Kg(!0),r=null,i=by,o=null;function a(a){var u,c,f,s=a.length,l=!1;for(null==r&&(o=i(f=Hi())),u=0;u<=s;++u)!(u=s;--l)u.point(g[l],y[l]);u.lineEnd(),u.areaEnd()}v&&(g[f]=+t(h,f,c),y[f]=+e(h,f,c),u.point(n?+n(h,f,c):g[f],r?+r(h,f,c):y[f]))}if(d)return u=null,d+""||null}function f(){return wy().defined(i).curve(a).context(o)}return c.x=function(e){return arguments.length?(t="function"==typeof e?e:Kg(+e),n=null,c):t},c.x0=function(n){return arguments.length?(t="function"==typeof n?n:Kg(+n),c):t},c.x1=function(t){return arguments.length?(n=null==t?null:"function"==typeof t?t:Kg(+t),c):n},c.y=function(t){return arguments.length?(e="function"==typeof t?t:Kg(+t),r=null,c):e},c.y0=function(t){return arguments.length?(e="function"==typeof t?t:Kg(+t),c):e},c.y1=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:Kg(+t),c):r},c.lineX0=c.lineY0=function(){return f().x(t).y(e)},c.lineY1=function(){return f().x(t).y(r)},c.lineX1=function(){return f().x(n).y(e)},c.defined=function(t){return arguments.length?(i="function"==typeof t?t:Kg(!!t),c):i},c.curve=function(t){return arguments.length?(a=t,null!=o&&(u=a(o)),c):a},c.context=function(t){return arguments.length?(null==t?o=u=null:u=a(o=t),c):o},c}function Ny(t,n){return nt?1:n>=t?0:NaN}function Ay(t){return t}_y.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:this._context.lineTo(t,n)}}};var Ty=ky(by);function Sy(t){this._curve=t}function ky(t){function n(n){return new Sy(t(n))}return n._curve=t,n}function Ey(t){var n=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(t){return arguments.length?n(ky(t)):n()._curve},t}function Cy(){return Ey(wy().curve(Ty))}function Py(){var t=My().curve(Ty),n=t.curve,e=t.lineX0,r=t.lineX1,i=t.lineY0,o=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return Ey(e())},delete t.lineX0,t.lineEndAngle=function(){return Ey(r())},delete t.lineX1,t.lineInnerRadius=function(){return Ey(i())},delete t.lineY0,t.lineOuterRadius=function(){return Ey(o())},delete t.lineY1,t.curve=function(t){return arguments.length?n(ky(t)):n()._curve},t}function zy(t,n){return[(n=+n)*Math.cos(t-=Math.PI/2),n*Math.sin(t)]}Sy.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,n){this._curve.point(n*Math.sin(t),n*-Math.cos(t))}};var Ry=Array.prototype.slice;function Dy(t){return t.source}function qy(t){return t.target}function Ly(t){var n=Dy,e=qy,r=my,i=xy,o=null;function a(){var a,u=Ry.call(arguments),c=n.apply(this,u),f=e.apply(this,u);if(o||(o=a=Hi()),t(o,+r.apply(this,(u[0]=c,u)),+i.apply(this,u),+r.apply(this,(u[0]=f,u)),+i.apply(this,u)),a)return o=null,a+""||null}return a.source=function(t){return arguments.length?(n=t,a):n},a.target=function(t){return arguments.length?(e=t,a):e},a.x=function(t){return arguments.length?(r="function"==typeof t?t:Kg(+t),a):r},a.y=function(t){return arguments.length?(i="function"==typeof t?t:Kg(+t),a):i},a.context=function(t){return arguments.length?(o=null==t?null:t,a):o},a}function Uy(t,n,e,r,i){t.moveTo(n,e),t.bezierCurveTo(n=(n+r)/2,e,n,i,r,i)}function Oy(t,n,e,r,i){t.moveTo(n,e),t.bezierCurveTo(n,e=(e+i)/2,r,e,r,i)}function By(t,n,e,r,i){var o=zy(n,e),a=zy(n,e=(e+i)/2),u=zy(r,e),c=zy(r,i);t.moveTo(o[0],o[1]),t.bezierCurveTo(a[0],a[1],u[0],u[1],c[0],c[1])}var Yy={draw:function(t,n){var e=Math.sqrt(n/cy);t.moveTo(e,0),t.arc(0,0,e,0,sy)}},Fy={draw:function(t,n){var e=Math.sqrt(n/5)/2;t.moveTo(-3*e,-e),t.lineTo(-e,-e),t.lineTo(-e,-3*e),t.lineTo(e,-3*e),t.lineTo(e,-e),t.lineTo(3*e,-e),t.lineTo(3*e,e),t.lineTo(e,e),t.lineTo(e,3*e),t.lineTo(-e,3*e),t.lineTo(-e,e),t.lineTo(-3*e,e),t.closePath()}},Iy=Math.sqrt(1/3),jy=2*Iy,Hy={draw:function(t,n){var e=Math.sqrt(n/jy),r=e*Iy;t.moveTo(0,-e),t.lineTo(r,0),t.lineTo(0,e),t.lineTo(-r,0),t.closePath()}},Xy=Math.sin(cy/10)/Math.sin(7*cy/10),Gy=Math.sin(sy/10)*Xy,Vy=-Math.cos(sy/10)*Xy,$y={draw:function(t,n){var e=Math.sqrt(.8908130915292852*n),r=Gy*e,i=Vy*e;t.moveTo(0,-e),t.lineTo(r,i);for(var o=1;o<5;++o){var a=sy*o/5,u=Math.cos(a),c=Math.sin(a);t.lineTo(c*e,-u*e),t.lineTo(u*r-c*i,c*r+u*i)}t.closePath()}},Wy={draw:function(t,n){var e=Math.sqrt(n),r=-e/2;t.rect(r,r,e,e)}},Zy=Math.sqrt(3),Qy={draw:function(t,n){var e=-Math.sqrt(n/(3*Zy));t.moveTo(0,2*e),t.lineTo(-Zy*e,-e),t.lineTo(Zy*e,-e),t.closePath()}},Jy=Math.sqrt(3)/2,Ky=1/Math.sqrt(12),t_=3*(Ky/2+1),n_={draw:function(t,n){var e=Math.sqrt(n/t_),r=e/2,i=e*Ky,o=r,a=e*Ky+e,u=-o,c=a;t.moveTo(r,i),t.lineTo(o,a),t.lineTo(u,c),t.lineTo(-.5*r-Jy*i,Jy*r+-.5*i),t.lineTo(-.5*o-Jy*a,Jy*o+-.5*a),t.lineTo(-.5*u-Jy*c,Jy*u+-.5*c),t.lineTo(-.5*r+Jy*i,-.5*i-Jy*r),t.lineTo(-.5*o+Jy*a,-.5*a-Jy*o),t.lineTo(-.5*u+Jy*c,-.5*c-Jy*u),t.closePath()}},e_=[Yy,Fy,Hy,Wy,$y,Qy,n_];function r_(){}function i_(t,n,e){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+n)/6,(t._y0+4*t._y1+e)/6)}function o_(t){this._context=t}function a_(t){this._context=t}function u_(t){this._context=t}function c_(t,n){this._basis=new o_(t),this._beta=n}o_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:i_(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:i_(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}},a_.prototype={areaStart:r_,areaEnd:r_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x2=t,this._y2=n;break;case 1:this._point=2,this._x3=t,this._y3=n;break;case 2:this._point=3,this._x4=t,this._y4=n,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+n)/6);break;default:i_(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}},u_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var e=(this._x0+4*this._x1+t)/6,r=(this._y0+4*this._y1+n)/6;this._line?this._context.lineTo(e,r):this._context.moveTo(e,r);break;case 3:this._point=4;default:i_(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}},c_.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,n=this._y,e=t.length-1;if(e>0)for(var r,i=t[0],o=n[0],a=t[e]-i,u=n[e]-o,c=-1;++c<=e;)r=c/e,this._basis.point(this._beta*t[c]+(1-this._beta)*(i+r*a),this._beta*n[c]+(1-this._beta)*(o+r*u));this._x=this._y=null,this._basis.lineEnd()},point:function(t,n){this._x.push(+t),this._y.push(+n)}};var f_=function t(n){function e(t){return 1===n?new o_(t):new c_(t,n)}return e.beta=function(n){return t(+n)},e}(.85);function s_(t,n,e){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-n),t._y2+t._k*(t._y1-e),t._x2,t._y2)}function l_(t,n){this._context=t,this._k=(1-n)/6}l_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:s_(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2,this._x1=t,this._y1=n;break;case 2:this._point=3;default:s_(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var h_=function t(n){function e(t){return new l_(t,n)}return e.tension=function(n){return t(+n)},e}(0);function d_(t,n){this._context=t,this._k=(1-n)/6}d_.prototype={areaStart:r_,areaEnd:r_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:s_(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var p_=function t(n){function e(t){return new d_(t,n)}return e.tension=function(n){return t(+n)},e}(0);function v_(t,n){this._context=t,this._k=(1-n)/6}v_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:s_(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var g_=function t(n){function e(t){return new v_(t,n)}return e.tension=function(n){return t(+n)},e}(0);function y_(t,n,e){var r=t._x1,i=t._y1,o=t._x2,a=t._y2;if(t._l01_a>uy){var u=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,c=3*t._l01_a*(t._l01_a+t._l12_a);r=(r*u-t._x0*t._l12_2a+t._x2*t._l01_2a)/c,i=(i*u-t._y0*t._l12_2a+t._y2*t._l01_2a)/c}if(t._l23_a>uy){var f=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,s=3*t._l23_a*(t._l23_a+t._l12_a);o=(o*f+t._x1*t._l23_2a-n*t._l12_2a)/s,a=(a*f+t._y1*t._l23_2a-e*t._l12_2a)/s}t._context.bezierCurveTo(r,i,o,a,t._x2,t._y2)}function __(t,n){this._context=t,this._alpha=n}__.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3;default:y_(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var b_=function t(n){function e(t){return n?new __(t,n):new l_(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function m_(t,n){this._context=t,this._alpha=n}m_.prototype={areaStart:r_,areaEnd:r_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:y_(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var x_=function t(n){function e(t){return n?new m_(t,n):new d_(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function w_(t,n){this._context=t,this._alpha=n}w_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:y_(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var M_=function t(n){function e(t){return n?new w_(t,n):new v_(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function N_(t){this._context=t}function A_(t){return t<0?-1:1}function T_(t,n,e){var r=t._x1-t._x0,i=n-t._x1,o=(t._y1-t._y0)/(r||i<0&&-0),a=(e-t._y1)/(i||r<0&&-0),u=(o*i+a*r)/(r+i);return(A_(o)+A_(a))*Math.min(Math.abs(o),Math.abs(a),.5*Math.abs(u))||0}function S_(t,n){var e=t._x1-t._x0;return e?(3*(t._y1-t._y0)/e-n)/2:n}function k_(t,n,e){var r=t._x0,i=t._y0,o=t._x1,a=t._y1,u=(o-r)/3;t._context.bezierCurveTo(r+u,i+u*n,o-u,a-u*e,o,a)}function E_(t){this._context=t}function C_(t){this._context=new P_(t)}function P_(t){this._context=t}function z_(t){this._context=t}function R_(t){var n,e,r=t.length-1,i=new Array(r),o=new Array(r),a=new Array(r);for(i[0]=0,o[0]=2,a[0]=t[0]+2*t[1],n=1;n=0;--n)i[n]=(a[n]-i[n+1])/o[n];for(o[r-1]=(t[r]+i[r-1])/2,n=0;n1)for(var e,r,i,o=1,a=t[n[0]],u=a.length;o=0;)e[n]=n;return e}function U_(t,n){return t[n]}function O_(t){var n=t.map(B_);return L_(t).sort(function(t,e){return n[t]-n[e]})}function B_(t){for(var n,e=-1,r=0,i=t.length,o=-1/0;++eo&&(o=n,r=e);return r}function Y_(t){var n=t.map(F_);return L_(t).sort(function(t,e){return n[t]-n[e]})}function F_(t){for(var n,e=0,r=-1,i=t.length;++r0)){if(o/=h,h<0){if(o0){if(o>l)return;o>s&&(s=o)}if(o=r-c,h||!(o<0)){if(o/=h,h<0){if(o>l)return;o>s&&(s=o)}else if(h>0){if(o0)){if(o/=d,d<0){if(o0){if(o>l)return;o>s&&(s=o)}if(o=i-f,d||!(o<0)){if(o/=d,d<0){if(o>l)return;o>s&&(s=o)}else if(d>0){if(o0||l<1)||(s>0&&(t[0]=[c+s*h,f+s*d]),l<1&&(t[1]=[c+l*h,f+l*d]),!0)}}}}}function tb(t,n,e,r,i){var o=t[1];if(o)return!0;var a,u,c=t[0],f=t.left,s=t.right,l=f[0],h=f[1],d=s[0],p=s[1],v=(l+d)/2,g=(h+p)/2;if(p===h){if(v=r)return;if(l>d){if(c){if(c[1]>=i)return}else c=[v,e];o=[v,i]}else{if(c){if(c[1]1)if(l>d){if(c){if(c[1]>=i)return}else c=[(e-u)/a,e];o=[(i-u)/a,i]}else{if(c){if(c[1]=r)return}else c=[n,a*n+u];o=[r,a*r+u]}else{if(c){if(c[0]=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,n),this._context.lineTo(t,n);else{var e=this._x*(1-this._t)+t*this._t;this._context.lineTo(e,this._y),this._context.lineTo(e,n)}}this._x=t,this._y=n}},X_.prototype={constructor:X_,insert:function(t,n){var e,r,i;if(t){if(n.P=t,n.N=t.N,t.N&&(t.N.P=n),t.N=n,t.R){for(t=t.R;t.L;)t=t.L;t.L=n}else t.R=n;e=t}else this._?(t=W_(this._),n.P=null,n.N=t,t.P=t.L=n,e=t):(n.P=n.N=null,this._=n,e=null);for(n.L=n.R=null,n.U=e,n.C=!0,t=n;e&&e.C;)e===(r=e.U).L?(i=r.R)&&i.C?(e.C=i.C=!1,r.C=!0,t=r):(t===e.R&&(V_(this,e),e=(t=e).U),e.C=!1,r.C=!0,$_(this,r)):(i=r.L)&&i.C?(e.C=i.C=!1,r.C=!0,t=r):(t===e.L&&($_(this,e),e=(t=e).U),e.C=!1,r.C=!0,V_(this,r)),e=t.U;this._.C=!1},remove:function(t){t.N&&(t.N.P=t.P),t.P&&(t.P.N=t.N),t.N=t.P=null;var n,e,r,i=t.U,o=t.L,a=t.R;if(e=o?a?W_(a):o:a,i?i.L===t?i.L=e:i.R=e:this._=e,o&&a?(r=e.C,e.C=t.C,e.L=o,o.U=e,e!==a?(i=e.U,e.U=t.U,t=e.R,i.L=t,e.R=a,a.U=e):(e.U=i,i=e,t=e.R)):(r=t.C,t=e),t&&(t.U=i),!r)if(t&&t.C)t.C=!1;else{do{if(t===this._)break;if(t===i.L){if((n=i.R).C&&(n.C=!1,i.C=!0,V_(this,i),n=i.R),n.L&&n.L.C||n.R&&n.R.C){n.R&&n.R.C||(n.L.C=!1,n.C=!0,$_(this,n),n=i.R),n.C=i.C,i.C=n.R.C=!1,V_(this,i),t=this._;break}}else if((n=i.L).C&&(n.C=!1,i.C=!0,$_(this,i),n=i.L),n.L&&n.L.C||n.R&&n.R.C){n.L&&n.L.C||(n.R.C=!1,n.C=!0,V_(this,n),n=i.L),n.C=i.C,i.C=n.L.C=!1,$_(this,i),t=this._;break}n.C=!0,t=i,i=i.U}while(!t.C);t&&(t.C=!1)}}};var ib,ob=[];function ab(){G_(this),this.x=this.y=this.arc=this.site=this.cy=null}function ub(t){var n=t.P,e=t.N;if(n&&e){var r=n.site,i=t.site,o=e.site;if(r!==o){var a=i[0],u=i[1],c=r[0]-a,f=r[1]-u,s=o[0]-a,l=o[1]-u,h=2*(c*l-f*s);if(!(h>=-wb)){var d=c*c+f*f,p=s*s+l*l,v=(l*d-f*p)/h,g=(c*p-s*d)/h,y=ob.pop()||new ab;y.arc=t,y.site=i,y.x=v+a,y.y=(y.cy=g+u)+Math.sqrt(v*v+g*g),t.circle=y;for(var _=null,b=bb._;b;)if(y.yxb)u=u.L;else{if(!((i=o-gb(u,a))>xb)){r>-xb?(n=u.P,e=u):i>-xb?(n=u,e=u.N):n=e=u;break}if(!u.R){n=u;break}u=u.R}!function(t){_b[t.index]={site:t,halfedges:[]}}(t);var c=lb(t);if(yb.insert(n,c),n||e){if(n===e)return cb(n),e=lb(n.site),yb.insert(c,e),c.edge=e.edge=Z_(n.site,c.site),ub(n),void ub(e);if(e){cb(n),cb(e);var f=n.site,s=f[0],l=f[1],h=t[0]-s,d=t[1]-l,p=e.site,v=p[0]-s,g=p[1]-l,y=2*(h*g-d*v),_=h*h+d*d,b=v*v+g*g,m=[(g*_-d*b)/y+s,(h*b-v*_)/y+l];J_(e.edge,f,p,m),c.edge=Z_(f,t,null,m),e.edge=Z_(t,p,null,m),ub(n),ub(e)}else c.edge=Z_(n.site,c.site)}}function vb(t,n){var e=t.site,r=e[0],i=e[1],o=i-n;if(!o)return r;var a=t.P;if(!a)return-1/0;var u=(e=a.site)[0],c=e[1],f=c-n;if(!f)return u;var s=u-r,l=1/o-1/f,h=s/f;return l?(-h+Math.sqrt(h*h-2*l*(s*s/(-2*f)-c+f/2+i-o/2)))/l+r:(r+u)/2}function gb(t,n){var e=t.N;if(e)return vb(e,n);var r=t.site;return r[1]===n?r[0]:1/0}var yb,_b,bb,mb,xb=1e-6,wb=1e-12;function Mb(t,n){return n[1]-t[1]||n[0]-t[0]}function Nb(t,n){var e,r,i,o=t.sort(Mb).pop();for(mb=[],_b=new Array(t.length),yb=new X_,bb=new X_;;)if(i=ib,o&&(!i||o[1]xb||Math.abs(i[0][1]-i[1][1])>xb)||delete mb[o]}(a,u,c,f),function(t,n,e,r){var i,o,a,u,c,f,s,l,h,d,p,v,g=_b.length,y=!0;for(i=0;ixb||Math.abs(v-h)>xb)&&(c.splice(u,0,mb.push(Q_(a,d,Math.abs(p-t)xb?[t,Math.abs(l-t)xb?[Math.abs(h-r)xb?[e,Math.abs(l-e)xb?[Math.abs(h-n)=u)return null;var c=t-i.site[0],f=n-i.site[1],s=c*c+f*f;do{i=o.cells[r=a],a=null,i.halfedges.forEach(function(e){var r=o.edges[e],u=r.left;if(u!==i.site&&u||(u=r.right)){var c=t-u[0],f=n-u[1],l=c*c+f*f;lr?(r+i)/2:Math.min(0,r)||Math.max(0,i),a>o?(o+a)/2:Math.min(0,o)||Math.max(0,a))}Eb.prototype=Sb.prototype,t.version="5.9.2",t.bisect=i,t.bisectRight=i,t.bisectLeft=o,t.ascending=n,t.bisector=e,t.cross=function(t,n,e){var r,i,o,u,c=t.length,f=n.length,s=new Array(c*f);for(null==e&&(e=a),r=o=0;rt?1:n>=t?0:NaN},t.deviation=f,t.extent=s,t.histogram=function(){var t=v,n=s,e=M;function r(r){var o,a,u=r.length,c=new Array(u);for(o=0;ol;)h.pop(),--d;var p,v=new Array(d+1);for(o=0;o<=d;++o)(p=v[o]=[]).x0=o>0?h[o-1]:s,p.x1=o=r.length)return null!=t&&e.sort(t),null!=n?n(e):e;for(var c,f,s,l=-1,h=e.length,d=r[i++],p=Qi(),v=a();++lr.length)return e;var a,u=i[o-1];return null!=n&&o>=r.length?a=e.entries():(a=[],e.each(function(n,e){a.push({key:e,values:t(n,o)})})),null!=u?a.sort(function(t,n){return u(t.key,n.key)}):a}(o(t,0,to,no),0)},key:function(t){return r.push(t),e},sortKeys:function(t){return i[r.length-1]=t,e},sortValues:function(n){return t=n,e},rollup:function(t){return n=t,e}}},t.set=io,t.map=Qi,t.keys=function(t){var n=[];for(var e in t)n.push(e);return n},t.values=function(t){var n=[];for(var e in t)n.push(t[e]);return n},t.entries=function(t){var n=[];for(var e in t)n.push({key:e,value:t[e]});return n},t.color=hn,t.rgb=gn,t.hsl=mn,t.lab=Rn,t.hcl=Yn,t.lch=function(t,n,e,r){return 1===arguments.length?Bn(t):new Fn(e,n,t,null==r?1:r)},t.gray=function(t,n){return new Dn(t,0,0,null==n?1:n)},t.cubehelix=Zn,t.contours=po,t.contourDensity=function(){var t=yo,n=_o,e=bo,r=960,i=500,o=20,a=2,u=3*o,c=r+2*u>>a,f=i+2*u>>a,s=uo(20);function l(r){var i=new Float32Array(c*f),l=new Float32Array(c*f);r.forEach(function(r,o,s){var l=+t(r,o,s)+u>>a,h=+n(r,o,s)+u>>a,d=+e(r,o,s);l>=0&&l=0&&h>a),go({width:c,height:f,data:l},{width:c,height:f,data:i},o>>a),vo({width:c,height:f,data:i},{width:c,height:f,data:l},o>>a),go({width:c,height:f,data:l},{width:c,height:f,data:i},o>>a),vo({width:c,height:f,data:i},{width:c,height:f,data:l},o>>a),go({width:c,height:f,data:l},{width:c,height:f,data:i},o>>a);var d=s(i);if(!Array.isArray(d)){var p=A(i);d=w(0,p,d),(d=g(0,Math.floor(p/d)*d,d)).shift()}return po().thresholds(d).size([c,f])(i).map(h)}function h(t){return t.value*=Math.pow(2,-2*a),t.coordinates.forEach(d),t}function d(t){t.forEach(p)}function p(t){t.forEach(v)}function v(t){t[0]=t[0]*Math.pow(2,a)-u,t[1]=t[1]*Math.pow(2,a)-u}function y(){return c=r+2*(u=3*o)>>a,f=i+2*u>>a,l}return l.x=function(n){return arguments.length?(t="function"==typeof n?n:uo(+n),l):t},l.y=function(t){return arguments.length?(n="function"==typeof t?t:uo(+t),l):n},l.weight=function(t){return arguments.length?(e="function"==typeof t?t:uo(+t),l):e},l.size=function(t){if(!arguments.length)return[r,i];var n=Math.ceil(t[0]),e=Math.ceil(t[1]);if(!(n>=0||n>=0))throw new Error("invalid size");return r=n,i=e,y()},l.cellSize=function(t){if(!arguments.length)return 1<=1))throw new Error("invalid cell size");return a=Math.floor(Math.log(t)/Math.LN2),y()},l.thresholds=function(t){return arguments.length?(s="function"==typeof t?t:Array.isArray(t)?uo(oo.call(t)):uo(t),l):s},l.bandwidth=function(t){if(!arguments.length)return Math.sqrt(o*(o+1));if(!((t=+t)>=0))throw new Error("invalid bandwidth");return o=Math.round((Math.sqrt(4*t*t+1)-1)/2),y()},l},t.dispatch=I,t.drag=function(){var n,e,r,i,o=Gt,a=Vt,u=$t,c=Wt,f={},s=I("start","drag","end"),l=0,h=0;function d(t){t.on("mousedown.drag",p).filter(c).on("touchstart.drag",y).on("touchmove.drag",_).on("touchend.drag touchcancel.drag",b).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function p(){if(!i&&o.apply(this,arguments)){var u=m("mouse",a.apply(this,arguments),Ot,this,arguments);u&&(zt(t.event.view).on("mousemove.drag",v,!0).on("mouseup.drag",g,!0),It(t.event.view),Yt(),r=!1,n=t.event.clientX,e=t.event.clientY,u("start"))}}function v(){if(Ft(),!r){var i=t.event.clientX-n,o=t.event.clientY-e;r=i*i+o*o>h}f.mouse("drag")}function g(){zt(t.event.view).on("mousemove.drag mouseup.drag",null),jt(t.event.view,r),Ft(),f.mouse("end")}function y(){if(o.apply(this,arguments)){var n,e,r=t.event.changedTouches,i=a.apply(this,arguments),u=r.length;for(n=0;nc+d||if+d||ou.index){var p=c-a.x-a.vx,v=f-a.y-a.vy,g=p*p+v*v;gt.r&&(t.r=t[n].r)}function u(){if(n){var r,i,o=n.length;for(e=new Array(o),r=0;r=a)){(t.data!==n||t.next)&&(0===s&&(d+=(s=na())*s),0===l&&(d+=(l=na())*l),d1?(null==e?u.remove(t):u.set(t,d(e)),n):u.get(t)},find:function(n,e,r){var i,o,a,u,c,f=0,s=t.length;for(null==r?r=1/0:r*=r,f=0;f1?(f.on(t,e),n):f.on(t)}}},t.forceX=function(t){var n,e,r,i=ta(.1);function o(t){for(var i,o=0,a=n.length;opc(r[0],r[1])&&(r[1]=i[1]),pc(i[0],r[1])>pc(r[0],r[1])&&(r[0]=i[0])):o.push(r=i);for(a=-1/0,n=0,r=o[e=o.length-1];n<=e;r=i,++n)i=o[n],(u=pc(r[1],i[0]))>a&&(a=u,Ru=i[0],qu=r[1])}return Fu=Iu=null,Ru===1/0||Du===1/0?[[NaN,NaN],[NaN,NaN]]:[[Ru,Du],[qu,Lu]]},t.geoCentroid=function(t){ju=Hu=Xu=Gu=Vu=$u=Wu=Zu=Qu=Ju=Ku=0,du(t,yc);var n=Qu,e=Ju,r=Ku,i=n*n+e*e+r*r;return i=.12&&i<.234&&r>=-.425&&r<-.214?u:i>=.166&&i<.234&&r>=-.214&&r<-.115?c:a).invert(t)},s.stream=function(e){return t&&n===e?t:(r=[a.stream(n=e),u.stream(e),c.stream(e)],i=r.length,t={point:function(t,n){for(var e=-1;++e2?t[2]+90:90]):[(t=e())[0],t[1],t[2]-90]},e([0,0,90]).scale(159.155)},t.geoTransverseMercatorRaw=al,t.geoRotation=qc,t.geoStream=du,t.geoTransform=function(t){return{stream:xs(t)}},t.cluster=function(){var t=ul,n=1,e=1,r=!1;function i(i){var o,a=0;i.eachAfter(function(n){var e=n.children;e?(n.x=function(t){return t.reduce(cl,0)/t.length}(e),n.y=function(t){return 1+t.reduce(fl,0)}(e)):(n.x=o?a+=t(n,o):0,n.y=0,o=n)});var u=function(t){for(var n;n=t.children;)t=n[0];return t}(i),c=function(t){for(var n;n=t.children;)t=n[n.length-1];return t}(i),f=u.x-t(u,c)/2,s=c.x+t(c,u)/2;return i.eachAfter(r?function(t){t.x=(t.x-i.x)*n,t.y=(i.y-t.y)*e}:function(t){t.x=(t.x-f)/(s-f)*n,t.y=(1-(i.y?t.y/i.y:1))*e})}return i.separation=function(n){return arguments.length?(t=n,i):t},i.size=function(t){return arguments.length?(r=!1,n=+t[0],e=+t[1],i):r?null:[n,e]},i.nodeSize=function(t){return arguments.length?(r=!0,n=+t[0],e=+t[1],i):r?[n,e]:null},i},t.hierarchy=ll,t.pack=function(){var t=null,n=1,e=1,r=Pl;function i(i){return i.x=n/2,i.y=e/2,t?i.eachBefore(Dl(t)).eachAfter(ql(r,.5)).eachBefore(Ll(1)):i.eachBefore(Dl(Rl)).eachAfter(ql(Pl,1)).eachAfter(ql(r,i.r/Math.min(n,e))).eachBefore(Ll(Math.min(n,e)/(2*i.r))),i}return i.radius=function(n){return arguments.length?(t=null==(e=n)?null:Cl(e),i):t;var e},i.size=function(t){return arguments.length?(n=+t[0],e=+t[1],i):[n,e]},i.padding=function(t){return arguments.length?(r="function"==typeof t?t:zl(+t),i):r},i},t.packSiblings=function(t){return El(t),t},t.packEnclose=yl,t.partition=function(){var t=1,n=1,e=0,r=!1;function i(i){var o=i.height+1;return i.x0=i.y0=e,i.x1=t,i.y1=n/o,i.eachBefore(function(t,n){return function(r){r.children&&Ol(r,r.x0,t*(r.depth+1)/n,r.x1,t*(r.depth+2)/n);var i=r.x0,o=r.y0,a=r.x1-e,u=r.y1-e;a0)throw new Error("cycle");return o}return e.id=function(n){return arguments.length?(t=Cl(n),e):t},e.parentId=function(t){return arguments.length?(n=Cl(t),e):n},e},t.tree=function(){var t=Hl,n=1,e=1,r=null;function i(i){var c=function(t){for(var n,e,r,i,o,a=new Wl(t,0),u=[a];n=u.pop();)if(r=n._.children)for(n.children=new Array(o=r.length),i=o-1;i>=0;--i)u.push(e=n.children[i]=new Wl(r[i],i)),e.parent=n;return(a.parent=new Wl(null,0)).children=[a],a}(i);if(c.eachAfter(o),c.parent.m=-c.z,c.eachBefore(a),r)i.eachBefore(u);else{var f=i,s=i,l=i;i.eachBefore(function(t){t.xs.x&&(s=t),t.depth>l.depth&&(l=t)});var h=f===s?1:t(f,s)/2,d=h-f.x,p=n/(s.x+h+d),v=e/(l.depth||1);i.eachBefore(function(t){t.x=(t.x+d)*p,t.y=t.depth*v})}return i}function o(n){var e=n.children,r=n.parent.children,i=n.i?r[n.i-1]:null;if(e){!function(t){for(var n,e=0,r=0,i=t.children,o=i.length;--o>=0;)(n=i[o]).z+=e,n.m+=e,e+=n.s+(r+=n.c)}(n);var o=(e[0].z+e[e.length-1].z)/2;i?(n.z=i.z+t(n._,i._),n.m=n.z-o):n.z=o}else i&&(n.z=i.z+t(n._,i._));n.parent.A=function(n,e,r){if(e){for(var i,o=n,a=n,u=e,c=o.parent.children[0],f=o.m,s=a.m,l=u.m,h=c.m;u=Gl(u),o=Xl(o),u&&o;)c=Xl(c),(a=Gl(a)).a=n,(i=u.z+l-o.z-f+t(u._,o._))>0&&(Vl($l(u,n,r),n,i),f+=i,s+=i),l+=u.m,f+=o.m,h+=c.m,s+=a.m;u&&!Gl(a)&&(a.t=u,a.m+=l-s),o&&!Xl(c)&&(c.t=o,c.m+=f-h,r=n)}return r}(n,i,n.parent.A||r[0])}function a(t){t._.x=t.z+t.parent.m,t.m+=t.parent.m}function u(t){t.x*=n,t.y=t.depth*e}return i.separation=function(n){return arguments.length?(t=n,i):t},i.size=function(t){return arguments.length?(r=!1,n=+t[0],e=+t[1],i):r?null:[n,e]},i.nodeSize=function(t){return arguments.length?(r=!0,n=+t[0],e=+t[1],i):r?[n,e]:null},i},t.treemap=function(){var t=Kl,n=!1,e=1,r=1,i=[0],o=Pl,a=Pl,u=Pl,c=Pl,f=Pl;function s(t){return t.x0=t.y0=0,t.x1=e,t.y1=r,t.eachBefore(l),i=[0],n&&t.eachBefore(Ul),t}function l(n){var e=i[n.depth],r=n.x0+e,s=n.y0+e,l=n.x1-e,h=n.y1-e;l=e-1){var s=u[n];return s.x0=i,s.y0=o,s.x1=a,void(s.y1=c)}for(var l=f[n],h=r/2+l,d=n+1,p=e-1;d>>1;f[v]c-o){var _=(i*y+a*g)/r;t(n,d,g,i,o,_,c),t(d,e,y,_,o,a,c)}else{var b=(o*y+c*g)/r;t(n,d,g,i,o,a,b),t(d,e,y,i,b,a,c)}}(0,c,t.value,n,e,r,i)},t.treemapDice=Ol,t.treemapSlice=Zl,t.treemapSliceDice=function(t,n,e,r,i){(1&t.depth?Zl:Ol)(t,n,e,r,i)},t.treemapSquarify=Kl,t.treemapResquarify=th,t.interpolate=ye,t.interpolateArray=se,t.interpolateBasis=Kn,t.interpolateBasisClosed=te,t.interpolateDate=le,t.interpolateDiscrete=function(t){var n=t.length;return function(e){return t[Math.max(0,Math.min(n-1,Math.floor(e*n)))]}},t.interpolateHue=function(t,n){var e=re(+t,+n);return function(t){var n=e(t);return n-360*Math.floor(n/360)}},t.interpolateNumber=he,t.interpolateObject=de,t.interpolateRound=_e,t.interpolateString=ge,t.interpolateTransformCss=Se,t.interpolateTransformSvg=ke,t.interpolateZoom=De,t.interpolateRgb=ae,t.interpolateRgbBasis=ce,t.interpolateRgbBasisClosed=fe,t.interpolateHsl=Le,t.interpolateHslLong=Ue,t.interpolateLab=function(t,n){var e=oe((t=Rn(t)).l,(n=Rn(n)).l),r=oe(t.a,n.a),i=oe(t.b,n.b),o=oe(t.opacity,n.opacity);return function(n){return t.l=e(n),t.a=r(n),t.b=i(n),t.opacity=o(n),t+""}},t.interpolateHcl=Be,t.interpolateHclLong=Ye,t.interpolateCubehelix=Ie,t.interpolateCubehelixLong=je,t.piecewise=function(t,n){for(var e=0,r=n.length-1,i=n[0],o=new Array(r<0?0:r);e=0;--n)f.push(t[r[o[n]][2]]);for(n=+u;nu!=f>u&&a<(c-e)*(u-r)/(f-r)+e&&(s=!s),c=e,f=r;return s},t.polygonLength=function(t){for(var n,e,r=-1,i=t.length,o=t[i-1],a=o[0],u=o[1],c=0;++r0?a[n-1]:r[0],n=o?[a[o-1],r]:[a[n-1],a[n]]},c.unknown=function(t){return arguments.length?(n=t,c):c},c.thresholds=function(){return a.slice()},c.copy=function(){return t().domain([e,r]).range(u).unknown(n)},sh.apply(Eh(c),arguments)},t.scaleThreshold=function t(){var n,e=[.5],r=[0,1],o=1;function a(t){return t<=t?r[i(e,t,0,o)]:n}return a.domain=function(t){return arguments.length?(e=ph.call(t),o=Math.min(e.length,r.length-1),a):e.slice()},a.range=function(t){return arguments.length?(r=ph.call(t),o=Math.min(e.length,r.length-1),a):r.slice()},a.invertExtent=function(t){var n=r.indexOf(t);return[e[n-1],e[n]]},a.unknown=function(t){return arguments.length?(n=t,a):n},a.copy=function(){return t().domain(e).range(r).unknown(n)},sh.apply(a,arguments)},t.scaleTime=function(){return sh.apply(Nv(Nd,wd,cd,od,rd,nd,Kh,Wh,t.timeFormat).domain([new Date(2e3,0,1),new Date(2e3,0,2)]),arguments)},t.scaleUtc=function(){return sh.apply(Nv(Wd,Vd,Rd,Cd,kd,Td,Kh,Wh,t.utcFormat).domain([Date.UTC(2e3,0,1),Date.UTC(2e3,0,2)]),arguments)},t.scaleSequential=function t(){var n=Eh(Av()(mh));return n.copy=function(){return Tv(n,t())},lh.apply(n,arguments)},t.scaleSequentialLog=function t(){var n=Uh(Av()).domain([1,10]);return n.copy=function(){return Tv(n,t()).base(n.base())},lh.apply(n,arguments)},t.scaleSequentialPow=Sv,t.scaleSequentialSqrt=function(){return Sv.apply(null,arguments).exponent(.5)},t.scaleSequentialSymlog=function t(){var n=Yh(Av());return n.copy=function(){return Tv(n,t()).constant(n.constant())},lh.apply(n,arguments)},t.scaleSequentialQuantile=function t(){var e=[],r=mh;function o(t){if(!isNaN(t=+t))return r((i(e,t)-1)/(e.length-1))}return o.domain=function(t){if(!arguments.length)return e.slice();e=[];for(var r,i=0,a=t.length;i1)&&(t-=Math.floor(t));var n=Math.abs(t-.5);return Hg.h=360*t-100,Hg.s=1.5-1.5*n,Hg.l=.8-.9*n,Hg+""},t.interpolateWarm=Ig,t.interpolateCool=jg,t.interpolateSinebow=function(t){var n;return t=(.5-t)*Math.PI,Xg.r=255*(n=Math.sin(t))*n,Xg.g=255*(n=Math.sin(t+Gg))*n,Xg.b=255*(n=Math.sin(t+Vg))*n,Xg+""},t.interpolateViridis=Wg,t.interpolateMagma=Zg,t.interpolateInferno=Qg,t.interpolatePlasma=Jg,t.create=function(t){return zt(W(t).call(document.documentElement))},t.creator=W,t.local=Dt,t.matcher=tt,t.mouse=Ot,t.namespace=$,t.namespaces=V,t.clientPoint=Ut,t.select=zt,t.selectAll=function(t){return"string"==typeof t?new Ct([document.querySelectorAll(t)],[document.documentElement]):new Ct([null==t?[]:t],Et)},t.selection=Pt,t.selector=Q,t.selectorAll=K,t.style=ct,t.touch=Bt,t.touches=function(t,n){null==n&&(n=Lt().touches);for(var e=0,r=n?n.length:0,i=new Array(r);ed;if(u||(u=c=Hi()),huy)if(v>sy-uy)u.moveTo(h*ey(d),h*oy(d)),u.arc(0,0,h,d,p,!g),l>uy&&(u.moveTo(l*ey(p),l*oy(p)),u.arc(0,0,l,p,d,g));else{var y,_,b=d,m=p,x=d,w=p,M=v,N=v,A=a.apply(this,arguments)/2,T=A>uy&&(r?+r.apply(this,arguments):ay(l*l+h*h)),S=iy(ty(h-l)/2,+e.apply(this,arguments)),k=S,E=S;if(T>uy){var C=ly(T/l*oy(A)),P=ly(T/h*oy(A));(M-=2*C)>uy?(x+=C*=g?1:-1,w-=C):(M=0,x=w=(d+p)/2),(N-=2*P)>uy?(b+=P*=g?1:-1,m-=P):(N=0,b=m=(d+p)/2)}var z=h*ey(b),R=h*oy(b),D=l*ey(w),q=l*oy(w);if(S>uy){var L,U=h*ey(m),O=h*oy(m),B=l*ey(x),Y=l*oy(x);if(v1?0:s<-1?cy:Math.acos(s))/2),G=ay(L[0]*L[0]+L[1]*L[1]);k=iy(S,(l-G)/(X-1)),E=iy(S,(h-G)/(X+1))}}N>uy?E>uy?(y=yy(B,Y,z,R,h,E,g),_=yy(U,O,D,q,h,E,g),u.moveTo(y.cx+y.x01,y.cy+y.y01),Euy&&M>uy?k>uy?(y=yy(D,q,U,O,l,-k,g),_=yy(z,R,B,Y,l,-k,g),u.lineTo(y.cx+y.x01,y.cy+y.y01),k0&&(d+=l);for(null!=n?p.sort(function(t,e){return n(v[t],v[e])}):null!=e&&p.sort(function(t,n){return e(a[t],a[n])}),u=0,f=d?(y-h*b)/d:0;u0?l*f:0)+b,v[c]={data:a[c],index:u,value:l,startAngle:g,endAngle:s,padAngle:_};return v}return a.value=function(n){return arguments.length?(t="function"==typeof n?n:Kg(+n),a):t},a.sortValues=function(t){return arguments.length?(n=t,e=null,a):n},a.sort=function(t){return arguments.length?(e=t,n=null,a):e},a.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:Kg(+t),a):r},a.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:Kg(+t),a):i},a.padAngle=function(t){return arguments.length?(o="function"==typeof t?t:Kg(+t),a):o},a},t.areaRadial=Py,t.radialArea=Py,t.lineRadial=Cy,t.radialLine=Cy,t.pointRadial=zy,t.linkHorizontal=function(){return Ly(Uy)},t.linkVertical=function(){return Ly(Oy)},t.linkRadial=function(){var t=Ly(By);return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t},t.symbol=function(){var t=Kg(Yy),n=Kg(64),e=null;function r(){var r;if(e||(e=r=Hi()),t.apply(this,arguments).draw(e,+n.apply(this,arguments)),r)return e=null,r+""||null}return r.type=function(n){return arguments.length?(t="function"==typeof n?n:Kg(n),r):t},r.size=function(t){return arguments.length?(n="function"==typeof t?t:Kg(+t),r):n},r.context=function(t){return arguments.length?(e=null==t?null:t,r):e},r},t.symbols=e_,t.symbolCircle=Yy,t.symbolCross=Fy,t.symbolDiamond=Hy,t.symbolSquare=Wy,t.symbolStar=$y,t.symbolTriangle=Qy,t.symbolWye=n_,t.curveBasisClosed=function(t){return new a_(t)},t.curveBasisOpen=function(t){return new u_(t)},t.curveBasis=function(t){return new o_(t)},t.curveBundle=f_,t.curveCardinalClosed=p_,t.curveCardinalOpen=g_,t.curveCardinal=h_,t.curveCatmullRomClosed=x_,t.curveCatmullRomOpen=M_,t.curveCatmullRom=b_,t.curveLinearClosed=function(t){return new N_(t)},t.curveLinear=by,t.curveMonotoneX=function(t){return new E_(t)},t.curveMonotoneY=function(t){return new C_(t)},t.curveNatural=function(t){return new z_(t)},t.curveStep=function(t){return new D_(t,.5)},t.curveStepAfter=function(t){return new D_(t,1)},t.curveStepBefore=function(t){return new D_(t,0)},t.stack=function(){var t=Kg([]),n=L_,e=q_,r=U_;function i(i){var o,a,u=t.apply(this,arguments),c=i.length,f=u.length,s=new Array(f);for(o=0;o0){for(var e,r,i,o=0,a=t[0].length;o1)for(var e,r,i,o,a,u,c=0,f=t[n[0]].length;c=0?(r[0]=o,r[1]=o+=i):i<0?(r[1]=a,r[0]=a+=i):r[0]=o},t.stackOffsetNone=q_,t.stackOffsetSilhouette=function(t,n){if((e=t.length)>0){for(var e,r=0,i=t[n[0]],o=i.length;r0&&(r=(e=t[n[0]]).length)>0){for(var e,r,i,o=0,a=1;adr&&e.name===n)return new Er([[t]],fi,n,+r);return null},t.interrupt=Mr,t.voronoi=function(){var t=j_,n=H_,e=null;function r(r){return new Nb(r.map(function(e,i){var o=[Math.round(t(e,i,r)/xb)*xb,Math.round(n(e,i,r)/xb)*xb];return o.index=i,o.data=e,o}),e)}return r.polygons=function(t){return r(t).polygons()},r.links=function(t){return r(t).links()},r.triangles=function(t){return r(t).triangles()},r.x=function(n){return arguments.length?(t="function"==typeof n?n:I_(+n),r):t},r.y=function(t){return arguments.length?(n="function"==typeof t?t:I_(+t),r):n},r.extent=function(t){return arguments.length?(e=null==t?null:[[+t[0][0],+t[0][1]],[+t[1][0],+t[1][1]]],r):e&&[[e[0][0],e[0][1]],[e[1][0],e[1][1]]]},r.size=function(t){return arguments.length?(e=null==t?null:[[0,0],[+t[0],+t[1]]],r):e&&[e[1][0]-e[0][0],e[1][1]-e[0][1]]},r},t.zoom=function(){var n,e,r=zb,i=Rb,o=Ub,a=qb,u=Lb,c=[0,1/0],f=[[-1/0,-1/0],[1/0,1/0]],s=250,l=De,h=[],d=I("start","zoom","end"),p=500,v=150,g=0;function y(t){t.property("__zoom",Db).on("wheel.zoom",N).on("mousedown.zoom",A).on("dblclick.zoom",T).filter(u).on("touchstart.zoom",S).on("touchmove.zoom",k).on("touchend.zoom touchcancel.zoom",E).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function _(t,n){return(n=Math.max(c[0],Math.min(c[1],n)))===t.k?t:new Sb(n,t.x,t.y)}function b(t,n,e){var r=n[0]-e[0]*t.k,i=n[1]-e[1]*t.k;return r===t.x&&i===t.y?t:new Sb(t.k,r,i)}function m(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function x(t,n,e){t.on("start.zoom",function(){w(this,arguments).start()}).on("interrupt.zoom end.zoom",function(){w(this,arguments).end()}).tween("zoom",function(){var t=arguments,r=w(this,t),o=i.apply(this,t),a=e||m(o),u=Math.max(o[1][0]-o[0][0],o[1][1]-o[0][1]),c=this.__zoom,f="function"==typeof n?n.apply(this,t):n,s=l(c.invert(a).concat(u/c.k),f.invert(a).concat(u/f.k));return function(t){if(1===t)t=f;else{var n=s(t),e=u/n[2];t=new Sb(e,a[0]-n[0]*e,a[1]-n[1]*e)}r.zoom(null,t)}})}function w(t,n){for(var e,r=0,i=h.length;rg}n.zoom("mouse",o(b(n.that.__zoom,n.mouse[0]=Ot(n.that),n.mouse[1]),n.extent,f))},!0).on("mouseup.zoom",function(){i.on("mousemove.zoom mouseup.zoom",null),jt(t.event.view,n.moved),Pb(),n.end()},!0),a=Ot(this),u=t.event.clientX,c=t.event.clientY;It(t.event.view),Cb(),n.mouse=[a,this.__zoom.invert(a)],Mr(this),n.start()}}function T(){if(r.apply(this,arguments)){var n=this.__zoom,e=Ot(this),a=n.invert(e),u=n.k*(t.event.shiftKey?.5:2),c=o(b(_(n,u),e,a),i.apply(this,arguments),f);Pb(),s>0?zt(this).transition().duration(s).call(x,c,e):zt(this).call(y.transform,c)}}function S(){if(r.apply(this,arguments)){var e,i,o,a,u=w(this,arguments),c=t.event.changedTouches,f=c.length;for(Cb(),i=0;in?1:t>=n?0:NaN}function e(t){let e=t,r=t;function i(t,n,e,i){for(null==e&&(e=0),null==i&&(i=t.length);e>>1;r(t[o],n)<0?e=o+1:i=o}return e}return 1===t.length&&(e=(n,e)=>t(n)-e,r=function(t){return(e,r)=>n(t(e),r)}(t)),{left:i,center:function(t,n,r,o){null==r&&(r=0),null==o&&(o=t.length);const a=i(t,n,r,o-1);return a>r&&e(t[a-1],n)>-e(t[a],n)?a-1:a},right:function(t,n,e,i){for(null==e&&(e=0),null==i&&(i=t.length);e>>1;r(t[o],n)>0?i=o:e=o+1}return e}}}function r(t){return null===t?NaN:+t}const i=e(n),o=i.right,a=i.left,u=e(r).center;function c(t,n){let e=0;if(void 0===n)for(let n of t)null!=n&&(n=+n)>=n&&++e;else{let r=-1;for(let i of t)null!=(i=n(i,++r,t))&&(i=+i)>=i&&++e}return e}function f(t){return 0|t.length}function s(t){return!(t>0)}function l(t){return"object"!=typeof t||"length"in t?t:Array.from(t)}function h(t,n){let e,r=0,i=0,o=0;if(void 0===n)for(let n of t)null!=n&&(n=+n)>=n&&(e=n-i,i+=e/++r,o+=e*(n-i));else{let a=-1;for(let u of t)null!=(u=n(u,++a,t))&&(u=+u)>=u&&(e=u-i,i+=e/++r,o+=e*(u-i))}if(r>1)return o/(r-1)}function d(t,n){const e=h(t,n);return e?Math.sqrt(e):e}function p(t,n){let e,r;if(void 0===n)for(const n of t)null!=n&&(void 0===e?n>=n&&(e=r=n):(e>n&&(e=n),r=o&&(e=r=o):(e>o&&(e=o),r0){for(o=t[--i];i>0&&(n=o,e=t[--i],o=n+e,r=e-(o-n),!r););i>0&&(r<0&&t[i-1]<0||r>0&&t[i-1]>0)&&(e=2*r,n=o+e,e==n-o&&(o=n))}return o}}class y extends Map{constructor(t,n=x){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:n}}),null!=t)for(const[n,e]of t)this.set(n,e)}get(t){return super.get(_(this,t))}has(t){return super.has(_(this,t))}set(t,n){return super.set(b(this,t),n)}delete(t){return super.delete(m(this,t))}}class v extends Set{constructor(t,n=x){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:n}}),null!=t)for(const n of t)this.add(n)}has(t){return super.has(_(this,t))}add(t){return super.add(b(this,t))}delete(t){return super.delete(m(this,t))}}function _({_intern:t,_key:n},e){const r=n(e);return t.has(r)?t.get(r):e}function b({_intern:t,_key:n},e){const r=n(e);return t.has(r)?t.get(r):(t.set(r,e),e)}function m({_intern:t,_key:n},e){const r=n(e);return t.has(r)&&(e=t.get(e),t.delete(r)),e}function x(t){return null!==t&&"object"==typeof t?t.valueOf():t}function w(t){return t}function M(t,...n){return S(t,w,w,n)}function A(t,n,...e){return S(t,w,n,e)}function T(t){if(1!==t.length)throw new Error("duplicate key");return t[0]}function S(t,n,e,r){return function t(i,o){if(o>=r.length)return e(i);const a=new y,u=r[o++];let c=-1;for(const t of i){const n=u(t,++c,i),e=a.get(n);e?e.push(t):a.set(n,[t])}for(const[n,e]of a)a.set(n,t(e,o));return n(a)}(t,0)}function E(t,n){return Array.from(n,(n=>t[n]))}function k(t,...e){if("function"!=typeof t[Symbol.iterator])throw new TypeError("values is not iterable");t=Array.from(t);let[r=n]=e;if(1===r.length||e.length>1){const i=Uint32Array.from(t,((t,n)=>n));return e.length>1?(e=e.map((n=>t.map(n))),i.sort(((t,r)=>{for(const i of e){const e=n(i[t],i[r]);if(e)return e}}))):(r=t.map(r),i.sort(((t,e)=>n(r[t],r[e])))),E(t,i)}return t.sort(r)}var N=Array.prototype.slice;function C(t){return function(){return t}}var P=Math.sqrt(50),z=Math.sqrt(10),D=Math.sqrt(2);function q(t,n,e){var r,i,o,a,u=-1;if(e=+e,(t=+t)===(n=+n)&&e>0)return[t];if((r=n0){let e=Math.round(t/a),r=Math.round(n/a);for(e*an&&--r,o=new Array(i=r-e+1);++un&&--r,o=new Array(i=r-e+1);++u=0?(o>=P?10:o>=z?5:o>=D?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(o>=P?10:o>=z?5:o>=D?2:1)}function F(t,n,e){var r=Math.abs(n-t)/Math.max(0,e),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),o=r/i;return o>=P?i*=10:o>=z?i*=5:o>=D&&(i*=2),n0?(t=Math.floor(t/i)*i,n=Math.ceil(n/i)*i):i<0&&(t=Math.ceil(t*i)/i,n=Math.floor(n*i)/i),r=i}}function I(t){return Math.ceil(Math.log(c(t))/Math.LN2)+1}function U(){var t=w,n=p,e=I;function r(r){Array.isArray(r)||(r=Array.from(r));var i,a,u=r.length,c=new Array(u);for(i=0;i=l)if(t>=l&&n===p){const t=R(s,l,e);isFinite(t)&&(t>0?l=(Math.floor(l/t)+1)*t:t<0&&(l=(Math.ceil(l*-t)+1)/-t))}else h.pop()}for(var d=h.length;h[0]<=s;)h.shift(),--d;for(;h[d-1]>l;)h.pop(),--d;var g,y=new Array(d+1);for(i=0;i<=d;++i)(g=y[i]=[]).x0=i>0?h[i-1]:s,g.x1=i=n)&&(e=n);else{let r=-1;for(let i of t)null!=(i=n(i,++r,t))&&(e=i)&&(e=i)}return e}function Y(t,n){let e;if(void 0===n)for(const n of t)null!=n&&(e>n||void 0===e&&n>=n)&&(e=n);else{let r=-1;for(let i of t)null!=(i=n(i,++r,t))&&(e>i||void 0===e&&i>=i)&&(e=i)}return e}function L(t,e,r=0,i=t.length-1,o=n){for(;i>r;){if(i-r>600){const n=i-r+1,a=e-r+1,u=Math.log(n),c=.5*Math.exp(2*u/3),f=.5*Math.sqrt(u*c*(n-c)/n)*(a-n/2<0?-1:1);L(t,e,Math.max(r,Math.floor(e-a*c/n+f)),Math.min(i,Math.floor(e+(n-a)*c/n+f)),o)}const n=t[e];let a=r,u=i;for(j(t,r,e),o(t[i],n)>0&&j(t,r,i);a0;)--u}0===o(t[r],n)?j(t,r,u):(++u,j(t,u,i)),u<=e&&(r=u+1),e<=u&&(i=u-1)}return t}function j(t,n,e){const r=t[n];t[n]=t[e],t[e]=r}function H(t,n,e){if(r=(t=Float64Array.from(function*(t,n){if(void 0===n)for(let n of t)null!=n&&(n=+n)>=n&&(yield n);else{let e=-1;for(let r of t)null!=(r=n(r,++e,t))&&(r=+r)>=r&&(yield r)}}(t,e))).length){if((n=+n)<=0||r<2)return Y(t);if(n>=1)return B(t);var r,i=(r-1)*n,o=Math.floor(i),a=B(L(t,o).subarray(0,o+1));return a+(Y(t.subarray(o+1))-a)*(i-o)}}function X(t,n,e=r){if(i=t.length){if((n=+n)<=0||i<2)return+e(t[0],0,t);if(n>=1)return+e(t[i-1],i-1,t);var i,o=(i-1)*n,a=Math.floor(o),u=+e(t[a],a,t);return u+(+e(t[a+1],a+1,t)-u)*(o-a)}}function G(t,n){let e,r=-1,i=-1;if(void 0===n)for(const n of t)++i,null!=n&&(e=n)&&(e=n,r=i);else for(let o of t)null!=(o=n(o,++i,t))&&(e=o)&&(e=o,r=i);return r}function V(t){return Array.from(function*(t){for(const n of t)yield*n}(t))}function $(t,n){let e,r=-1,i=-1;if(void 0===n)for(const n of t)++i,null!=n&&(e>n||void 0===e&&n>=n)&&(e=n,r=i);else for(let o of t)null!=(o=n(o,++i,t))&&(e>o||void 0===e&&o>=o)&&(e=o,r=i);return r}function W(t,n){return[t,n]}function Z(t,n,e){t=+t,n=+n,e=(i=arguments.length)<2?(n=t,t=0,1):i<3?1:+e;for(var r=-1,i=0|Math.max(0,Math.ceil((n-t)/e)),o=new Array(i);++r+t(n)}function st(t,n){return n=Math.max(0,t.bandwidth()-2*n)/2,t.round()&&(n=Math.round(n)),e=>+t(e)+n}function lt(){return!this.__axis}function ht(t,n){var e=[],r=null,i=null,o=6,a=6,u=3,c="undefined"!=typeof window&&window.devicePixelRatio>1?0:.5,f=1===t||4===t?-1:1,s=4===t||2===t?"x":"y",l=1===t||3===t?ut:ct;function h(h){var d=null==r?n.ticks?n.ticks.apply(n,e):n.domain():r,p=null==i?n.tickFormat?n.tickFormat.apply(n,e):ot:i,g=Math.max(o,0)+u,y=n.range(),v=+y[0]+c,_=+y[y.length-1]+c,b=(n.bandwidth?st:ft)(n.copy(),c),m=h.selection?h.selection():h,x=m.selectAll(".domain").data([null]),w=m.selectAll(".tick").data(d,n).order(),M=w.exit(),A=w.enter().append("g").attr("class","tick"),T=w.select("line"),S=w.select("text");x=x.merge(x.enter().insert("path",".tick").attr("class","domain").attr("stroke","currentColor")),w=w.merge(A),T=T.merge(A.append("line").attr("stroke","currentColor").attr(s+"2",f*o)),S=S.merge(A.append("text").attr("fill","currentColor").attr(s,f*g).attr("dy",1===t?"0em":3===t?"0.71em":"0.32em")),h!==m&&(x=x.transition(h),w=w.transition(h),T=T.transition(h),S=S.transition(h),M=M.transition(h).attr("opacity",at).attr("transform",(function(t){return isFinite(t=b(t))?l(t+c):this.getAttribute("transform")})),A.attr("opacity",at).attr("transform",(function(t){var n=this.parentNode.__axis;return l((n&&isFinite(n=n(t))?n:b(t))+c)}))),M.remove(),x.attr("d",4===t||2===t?a?"M"+f*a+","+v+"H"+c+"V"+_+"H"+f*a:"M"+c+","+v+"V"+_:a?"M"+v+","+f*a+"V"+c+"H"+_+"V"+f*a:"M"+v+","+c+"H"+_),w.attr("opacity",1).attr("transform",(function(t){return l(b(t)+c)})),T.attr(s+"2",f*o),S.attr(s,f*g).text(p),m.filter(lt).attr("fill","none").attr("font-size",10).attr("font-family","sans-serif").attr("text-anchor",2===t?"start":4===t?"end":"middle"),m.each((function(){this.__axis=b}))}return h.scale=function(t){return arguments.length?(n=t,h):n},h.ticks=function(){return e=it.call(arguments),h},h.tickArguments=function(t){return arguments.length?(e=null==t?[]:it.call(t),h):e.slice()},h.tickValues=function(t){return arguments.length?(r=null==t?null:it.call(t),h):r&&r.slice()},h.tickFormat=function(t){return arguments.length?(i=t,h):i},h.tickSize=function(t){return arguments.length?(o=a=+t,h):o},h.tickSizeInner=function(t){return arguments.length?(o=+t,h):o},h.tickSizeOuter=function(t){return arguments.length?(a=+t,h):a},h.tickPadding=function(t){return arguments.length?(u=+t,h):u},h.offset=function(t){return arguments.length?(c=+t,h):c},h}var dt={value:()=>{}};function pt(){for(var t,n=0,e=arguments.length,r={};n=0&&(e=t.slice(r+1),t=t.slice(0,r)),t&&!n.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:e}}))}function vt(t,n){for(var e,r=0,i=t.length;r0)for(var e,r,i=new Array(e),o=0;o=0&&"xmlns"!==(n=t.slice(0,e))&&(t=t.slice(e+1)),mt.hasOwnProperty(n)?{space:mt[n],local:t}:t}function wt(t){return function(){var n=this.ownerDocument,e=this.namespaceURI;return e===bt&&n.documentElement.namespaceURI===bt?n.createElement(t):n.createElementNS(e,t)}}function Mt(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function At(t){var n=xt(t);return(n.local?Mt:wt)(n)}function Tt(){}function St(t){return null==t?Tt:function(){return this.querySelector(t)}}function Et(t){return"object"==typeof t&&"length"in t?t:Array.from(t)}function kt(){return[]}function Nt(t){return null==t?kt:function(){return this.querySelectorAll(t)}}function Ct(t){return function(){return this.matches(t)}}function Pt(t){return function(n){return n.matches(t)}}var zt=Array.prototype.find;function Dt(){return this.firstElementChild}var qt=Array.prototype.filter;function Rt(){return this.children}function Ft(t){return new Array(t.length)}function Ot(t,n){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=n}function It(t){return function(){return t}}function Ut(t,n,e,r,i,o){for(var a,u=0,c=n.length,f=o.length;un?1:t>=n?0:NaN}function jt(t){return function(){this.removeAttribute(t)}}function Ht(t){return function(){this.removeAttributeNS(t.space,t.local)}}function Xt(t,n){return function(){this.setAttribute(t,n)}}function Gt(t,n){return function(){this.setAttributeNS(t.space,t.local,n)}}function Vt(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttribute(t):this.setAttribute(t,e)}}function $t(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,e)}}function Wt(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function Zt(t){return function(){this.style.removeProperty(t)}}function Kt(t,n,e){return function(){this.style.setProperty(t,n,e)}}function Qt(t,n,e){return function(){var r=n.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,e)}}function Jt(t,n){return t.style.getPropertyValue(n)||Wt(t).getComputedStyle(t,null).getPropertyValue(n)}function tn(t){return function(){delete this[t]}}function nn(t,n){return function(){this[t]=n}}function en(t,n){return function(){var e=n.apply(this,arguments);null==e?delete this[t]:this[t]=e}}function rn(t){return t.trim().split(/^|\s+/)}function on(t){return t.classList||new an(t)}function an(t){this._node=t,this._names=rn(t.getAttribute("class")||"")}function un(t,n){for(var e=on(t),r=-1,i=n.length;++r=0&&(n=t.slice(e+1),t=t.slice(0,e)),{type:t,name:n}}))}function Tn(t){return function(){var n=this.__on;if(n){for(var e,r=0,i=-1,o=n.length;r=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var Cn=[null];function Pn(t,n){this._groups=t,this._parents=n}function zn(){return new Pn([[document.documentElement]],Cn)}function Dn(t){return"string"==typeof t?new Pn([[document.querySelector(t)]],[document.documentElement]):new Pn([[t]],Cn)}Pn.prototype=zn.prototype={constructor:Pn,select:function(t){"function"!=typeof t&&(t=St(t));for(var n=this._groups,e=n.length,r=new Array(e),i=0;i=x&&(x=m+1);!(b=y[x])&&++x=0;)(r=i[o])&&(a&&4^r.compareDocumentPosition(a)&&a.parentNode.insertBefore(r,a),a=r);return this},sort:function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=Lt);for(var e=this._groups,r=e.length,i=new Array(r),o=0;o1?this.each((null==n?Zt:"function"==typeof n?Qt:Kt)(t,n,null==e?"":e)):Jt(this.node(),t)},property:function(t,n){return arguments.length>1?this.each((null==n?tn:"function"==typeof n?en:nn)(t,n)):this.node()[t]},classed:function(t,n){var e=rn(t+"");if(arguments.length<2){for(var r=on(this.node()),i=-1,o=e.length;++i()=>t;function Hn(t,{sourceEvent:n,subject:e,target:r,identifier:i,active:o,x:a,y:u,dx:c,dy:f,dispatch:s}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},subject:{value:e,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},identifier:{value:i,enumerable:!0,configurable:!0},active:{value:o,enumerable:!0,configurable:!0},x:{value:a,enumerable:!0,configurable:!0},y:{value:u,enumerable:!0,configurable:!0},dx:{value:c,enumerable:!0,configurable:!0},dy:{value:f,enumerable:!0,configurable:!0},_:{value:s}})}function Xn(t){return!t.ctrlKey&&!t.button}function Gn(){return this.parentNode}function Vn(t,n){return null==n?{x:t.x,y:t.y}:n}function $n(){return navigator.maxTouchPoints||"ontouchstart"in this}function Wn(t,n,e){t.prototype=n.prototype=e,e.constructor=t}function Zn(t,n){var e=Object.create(t.prototype);for(var r in n)e[r]=n[r];return e}function Kn(){}Hn.prototype.on=function(){var t=this._.on.apply(this._,arguments);return t===this._?this:t};var Qn=.7,Jn=1/Qn,te="\\s*([+-]?\\d+)\\s*",ne="\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*",ee="\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*",re=/^#([0-9a-f]{3,8})$/,ie=new RegExp("^rgb\\("+[te,te,te]+"\\)$"),oe=new RegExp("^rgb\\("+[ee,ee,ee]+"\\)$"),ae=new RegExp("^rgba\\("+[te,te,te,ne]+"\\)$"),ue=new RegExp("^rgba\\("+[ee,ee,ee,ne]+"\\)$"),ce=new RegExp("^hsl\\("+[ne,ee,ee]+"\\)$"),fe=new RegExp("^hsla\\("+[ne,ee,ee,ne]+"\\)$"),se={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};function le(){return this.rgb().formatHex()}function he(){return this.rgb().formatRgb()}function de(t){var n,e;return t=(t+"").trim().toLowerCase(),(n=re.exec(t))?(e=n[1].length,n=parseInt(n[1],16),6===e?pe(n):3===e?new _e(n>>8&15|n>>4&240,n>>4&15|240&n,(15&n)<<4|15&n,1):8===e?ge(n>>24&255,n>>16&255,n>>8&255,(255&n)/255):4===e?ge(n>>12&15|n>>8&240,n>>8&15|n>>4&240,n>>4&15|240&n,((15&n)<<4|15&n)/255):null):(n=ie.exec(t))?new _e(n[1],n[2],n[3],1):(n=oe.exec(t))?new _e(255*n[1]/100,255*n[2]/100,255*n[3]/100,1):(n=ae.exec(t))?ge(n[1],n[2],n[3],n[4]):(n=ue.exec(t))?ge(255*n[1]/100,255*n[2]/100,255*n[3]/100,n[4]):(n=ce.exec(t))?we(n[1],n[2]/100,n[3]/100,1):(n=fe.exec(t))?we(n[1],n[2]/100,n[3]/100,n[4]):se.hasOwnProperty(t)?pe(se[t]):"transparent"===t?new _e(NaN,NaN,NaN,0):null}function pe(t){return new _e(t>>16&255,t>>8&255,255&t,1)}function ge(t,n,e,r){return r<=0&&(t=n=e=NaN),new _e(t,n,e,r)}function ye(t){return t instanceof Kn||(t=de(t)),t?new _e((t=t.rgb()).r,t.g,t.b,t.opacity):new _e}function ve(t,n,e,r){return 1===arguments.length?ye(t):new _e(t,n,e,null==r?1:r)}function _e(t,n,e,r){this.r=+t,this.g=+n,this.b=+e,this.opacity=+r}function be(){return"#"+xe(this.r)+xe(this.g)+xe(this.b)}function me(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function xe(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function we(t,n,e,r){return r<=0?t=n=e=NaN:e<=0||e>=1?t=n=NaN:n<=0&&(t=NaN),new Te(t,n,e,r)}function Me(t){if(t instanceof Te)return new Te(t.h,t.s,t.l,t.opacity);if(t instanceof Kn||(t=de(t)),!t)return new Te;if(t instanceof Te)return t;var n=(t=t.rgb()).r/255,e=t.g/255,r=t.b/255,i=Math.min(n,e,r),o=Math.max(n,e,r),a=NaN,u=o-i,c=(o+i)/2;return u?(a=n===o?(e-r)/u+6*(e0&&c<1?0:a,new Te(a,u,c,t.opacity)}function Ae(t,n,e,r){return 1===arguments.length?Me(t):new Te(t,n,e,null==r?1:r)}function Te(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r}function Se(t,n,e){return 255*(t<60?n+(e-n)*t/60:t<180?e:t<240?n+(e-n)*(240-t)/60:n)}Wn(Kn,de,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:le,formatHex:le,formatHsl:function(){return Me(this).formatHsl()},formatRgb:he,toString:he}),Wn(_e,ve,Zn(Kn,{brighter:function(t){return t=null==t?Jn:Math.pow(Jn,t),new _e(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?Qn:Math.pow(Qn,t),new _e(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:be,formatHex:be,formatRgb:me,toString:me})),Wn(Te,Ae,Zn(Kn,{brighter:function(t){return t=null==t?Jn:Math.pow(Jn,t),new Te(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?Qn:Math.pow(Qn,t),new Te(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),n=isNaN(t)||isNaN(this.s)?0:this.s,e=this.l,r=e+(e<.5?e:1-e)*n,i=2*e-r;return new _e(Se(t>=240?t-240:t+120,i,r),Se(t,i,r),Se(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));const Ee=Math.PI/180,ke=180/Math.PI,Ne=.96422,Ce=.82521,Pe=4/29,ze=6/29,De=3*ze*ze;function qe(t){if(t instanceof Fe)return new Fe(t.l,t.a,t.b,t.opacity);if(t instanceof je)return He(t);t instanceof _e||(t=ye(t));var n,e,r=Be(t.r),i=Be(t.g),o=Be(t.b),a=Oe((.2225045*r+.7168786*i+.0606169*o)/1);return r===i&&i===o?n=e=a:(n=Oe((.4360747*r+.3850649*i+.1430804*o)/Ne),e=Oe((.0139322*r+.0971045*i+.7141733*o)/Ce)),new Fe(116*a-16,500*(n-a),200*(a-e),t.opacity)}function Re(t,n,e,r){return 1===arguments.length?qe(t):new Fe(t,n,e,null==r?1:r)}function Fe(t,n,e,r){this.l=+t,this.a=+n,this.b=+e,this.opacity=+r}function Oe(t){return t>.008856451679035631?Math.pow(t,1/3):t/De+Pe}function Ie(t){return t>ze?t*t*t:De*(t-Pe)}function Ue(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function Be(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function Ye(t){if(t instanceof je)return new je(t.h,t.c,t.l,t.opacity);if(t instanceof Fe||(t=qe(t)),0===t.a&&0===t.b)return new je(NaN,0=1?(e=1,n-1):Math.floor(e*n),i=t[r],o=t[r+1],a=r>0?t[r-1]:2*i-o,u=r()=>t;function ar(t,n){return function(e){return t+e*n}}function ur(t,n){var e=n-t;return e?ar(t,e>180||e<-180?e-360*Math.round(e/360):e):or(isNaN(t)?n:t)}function cr(t){return 1==(t=+t)?fr:function(n,e){return e-n?function(t,n,e){return t=Math.pow(t,e),n=Math.pow(n,e)-t,e=1/e,function(r){return Math.pow(t+r*n,e)}}(n,e,t):or(isNaN(n)?e:n)}}function fr(t,n){var e=n-t;return e?ar(t,e):or(isNaN(t)?n:t)}var sr=function t(n){var e=cr(n);function r(t,n){var r=e((t=ve(t)).r,(n=ve(n)).r),i=e(t.g,n.g),o=e(t.b,n.b),a=fr(t.opacity,n.opacity);return function(n){return t.r=r(n),t.g=i(n),t.b=o(n),t.opacity=a(n),t+""}}return r.gamma=t,r}(1);function lr(t){return function(n){var e,r,i=n.length,o=new Array(i),a=new Array(i),u=new Array(i);for(e=0;eo&&(i=n.slice(o,i),u[a]?u[a]+=i:u[++a]=i),(e=e[0])===(r=r[0])?u[a]?u[a]+=r:u[++a]=r:(u[++a]=null,c.push({i:a,x:_r(e,r)})),o=xr.lastIndex;return o180?n+=360:n-t>180&&(t+=360),o.push({i:e.push(i(e)+"rotate(",null,r)-2,x:_r(t,n)})):n&&e.push(i(e)+"rotate("+n+r)}(o.rotate,a.rotate,u,c),function(t,n,e,o){t!==n?o.push({i:e.push(i(e)+"skewX(",null,r)-2,x:_r(t,n)}):n&&e.push(i(e)+"skewX("+n+r)}(o.skewX,a.skewX,u,c),function(t,n,e,r,o,a){if(t!==e||n!==r){var u=o.push(i(o)+"scale(",null,",",null,")");a.push({i:u-4,x:_r(t,e)},{i:u-2,x:_r(n,r)})}else 1===e&&1===r||o.push(i(o)+"scale("+e+","+r+")")}(o.scaleX,o.scaleY,a.scaleX,a.scaleY,u,c),o=a=null,function(t){for(var n,e=-1,r=c.length;++e=0&&n._call.call(null,t),n=n._next;--Gr}function oi(){Zr=(Wr=Qr.now())+Kr,Gr=Vr=0;try{ii()}finally{Gr=0,function(){var t,n,e=Hr,r=1/0;for(;e;)e._call?(r>e._time&&(r=e._time),t=e,e=e._next):(n=e._next,e._next=null,e=t?t._next=n:Hr=n);Xr=t,ui(r)}(),Zr=0}}function ai(){var t=Qr.now(),n=t-Wr;n>1e3&&(Kr-=n,Wr=t)}function ui(t){Gr||(Vr&&(Vr=clearTimeout(Vr)),t-Zr>24?(t<1/0&&(Vr=setTimeout(oi,t-Qr.now()-Kr)),$r&&($r=clearInterval($r))):($r||(Wr=Qr.now(),$r=setInterval(ai,1e3)),Gr=1,Jr(oi)))}function ci(t,n,e){var r=new ei;return n=null==n?0:+n,r.restart((e=>{r.stop(),t(e+n)}),n,e),r}ei.prototype=ri.prototype={constructor:ei,restart:function(t,n,e){if("function"!=typeof t)throw new TypeError("callback is not a function");e=(null==e?ti():+e)+(null==n?0:+n),this._next||Xr===this||(Xr?Xr._next=this:Hr=this,Xr=this),this._call=t,this._time=e,ui()},stop:function(){this._call&&(this._call=null,this._time=1/0,ui())}};var fi=pt("start","end","cancel","interrupt"),si=[];function li(t,n,e,r,i,o){var a=t.__transition;if(a){if(e in a)return}else t.__transition={};!function(t,n,e){var r,i=t.__transition;function o(t){e.state=1,e.timer.restart(a,e.delay,e.time),e.delay<=t&&a(t-e.delay)}function a(o){var f,s,l,h;if(1!==e.state)return c();for(f in i)if((h=i[f]).name===e.name){if(3===h.state)return ci(a);4===h.state?(h.state=6,h.timer.stop(),h.on.call("interrupt",t,t.__data__,h.index,h.group),delete i[f]):+f0)throw new Error("too late; already scheduled");return e}function di(t,n){var e=pi(t,n);if(e.state>3)throw new Error("too late; already running");return e}function pi(t,n){var e=t.__transition;if(!e||!(e=e[n]))throw new Error("transition not found");return e}function gi(t,n){var e,r,i,o=t.__transition,a=!0;if(o){for(i in n=null==n?null:n+"",o)(e=o[i]).name===n?(r=e.state>2&&e.state<5,e.state=6,e.timer.stop(),e.on.call(r?"interrupt":"cancel",t,t.__data__,e.index,e.group),delete o[i]):a=!1;a&&delete t.__transition}}function yi(t,n){var e,r;return function(){var i=di(this,t),o=i.tween;if(o!==e)for(var a=0,u=(r=e=o).length;a=0&&(t=t.slice(0,n)),!t||"start"===t}))}(n)?hi:di;return function(){var a=o(this,t),u=a.on;u!==r&&(i=(r=u).copy()).on(n,e),a.on=i}}var Fi=zn.prototype.constructor;function Oi(t){return function(){this.style.removeProperty(t)}}function Ii(t,n,e){return function(r){this.style.setProperty(t,n.call(this,r),e)}}function Ui(t,n,e){var r,i;function o(){var o=n.apply(this,arguments);return o!==i&&(r=(i=o)&&Ii(t,o,e)),r}return o._value=n,o}function Bi(t){return function(n){this.textContent=t.call(this,n)}}function Yi(t){var n,e;function r(){var r=t.apply(this,arguments);return r!==e&&(n=(e=r)&&Bi(r)),n}return r._value=t,r}var Li=0;function ji(t,n,e,r){this._groups=t,this._parents=n,this._name=e,this._id=r}function Hi(t){return zn().transition(t)}function Xi(){return++Li}var Gi=zn.prototype;ji.prototype=Hi.prototype={constructor:ji,select:function(t){var n=this._name,e=this._id;"function"!=typeof t&&(t=St(t));for(var r=this._groups,i=r.length,o=new Array(i),a=0;a()=>t;function mo(t,{sourceEvent:n,target:e,selection:r,mode:i,dispatch:o}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},target:{value:e,enumerable:!0,configurable:!0},selection:{value:r,enumerable:!0,configurable:!0},mode:{value:i,enumerable:!0,configurable:!0},_:{value:o}})}function xo(t){t.stopImmediatePropagation()}function wo(t){t.preventDefault(),t.stopImmediatePropagation()}var Mo={name:"drag"},Ao={name:"space"},To={name:"handle"},So={name:"center"};const{abs:Eo,max:ko,min:No}=Math;function Co(t){return[+t[0],+t[1]]}function Po(t){return[Co(t[0]),Co(t[1])]}var zo={name:"x",handles:["w","e"].map(Bo),input:function(t,n){return null==t?null:[[+t[0],n[0][1]],[+t[1],n[1][1]]]},output:function(t){return t&&[t[0][0],t[1][0]]}},Do={name:"y",handles:["n","s"].map(Bo),input:function(t,n){return null==t?null:[[n[0][0],+t[0]],[n[1][0],+t[1]]]},output:function(t){return t&&[t[0][1],t[1][1]]}},qo={name:"xy",handles:["n","w","e","s","nw","ne","sw","se"].map(Bo),input:function(t){return null==t?null:Po(t)},output:function(t){return t}},Ro={overlay:"crosshair",selection:"move",n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Fo={e:"w",w:"e",nw:"ne",ne:"nw",se:"sw",sw:"se"},Oo={n:"s",s:"n",nw:"sw",ne:"se",se:"ne",sw:"nw"},Io={overlay:1,selection:1,n:null,e:1,s:null,w:-1,nw:-1,ne:1,se:1,sw:-1},Uo={overlay:1,selection:1,n:-1,e:null,s:1,w:null,nw:-1,ne:-1,se:1,sw:1};function Bo(t){return{type:t}}function Yo(t){return!t.ctrlKey&&!t.button}function Lo(){var t=this.ownerSVGElement||this;return t.hasAttribute("viewBox")?[[(t=t.viewBox.baseVal).x,t.y],[t.x+t.width,t.y+t.height]]:[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]}function jo(){return navigator.maxTouchPoints||"ontouchstart"in this}function Ho(t){for(;!t.__brush;)if(!(t=t.parentNode))return;return t.__brush}function Xo(t){return t[0][0]===t[1][0]||t[0][1]===t[1][1]}function Go(t){var n,e=Lo,r=Yo,i=jo,o=!0,a=pt("start","brush","end"),u=6;function c(n){var e=n.property("__brush",g).selectAll(".overlay").data([Bo("overlay")]);e.enter().append("rect").attr("class","overlay").attr("pointer-events","all").attr("cursor",Ro.overlay).merge(e).each((function(){var t=Ho(this).extent;Dn(this).attr("x",t[0][0]).attr("y",t[0][1]).attr("width",t[1][0]-t[0][0]).attr("height",t[1][1]-t[0][1])})),n.selectAll(".selection").data([Bo("selection")]).enter().append("rect").attr("class","selection").attr("cursor",Ro.selection).attr("fill","#777").attr("fill-opacity",.3).attr("stroke","#fff").attr("shape-rendering","crispEdges");var r=n.selectAll(".handle").data(t.handles,(function(t){return t.type}));r.exit().remove(),r.enter().append("rect").attr("class",(function(t){return"handle handle--"+t.type})).attr("cursor",(function(t){return Ro[t.type]})),n.each(f).attr("fill","none").attr("pointer-events","all").on("mousedown.brush",h).filter(i).on("touchstart.brush",h).on("touchmove.brush",d).on("touchend.brush touchcancel.brush",p).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function f(){var t=Dn(this),n=Ho(this).selection;n?(t.selectAll(".selection").style("display",null).attr("x",n[0][0]).attr("y",n[0][1]).attr("width",n[1][0]-n[0][0]).attr("height",n[1][1]-n[0][1]),t.selectAll(".handle").style("display",null).attr("x",(function(t){return"e"===t.type[t.type.length-1]?n[1][0]-u/2:n[0][0]-u/2})).attr("y",(function(t){return"s"===t.type[0]?n[1][1]-u/2:n[0][1]-u/2})).attr("width",(function(t){return"n"===t.type||"s"===t.type?n[1][0]-n[0][0]+u:u})).attr("height",(function(t){return"e"===t.type||"w"===t.type?n[1][1]-n[0][1]+u:u}))):t.selectAll(".selection,.handle").style("display","none").attr("x",null).attr("y",null).attr("width",null).attr("height",null)}function s(t,n,e){var r=t.__brush.emitter;return!r||e&&r.clean?new l(t,n,e):r}function l(t,n,e){this.that=t,this.args=n,this.state=t.__brush,this.active=0,this.clean=e}function h(e){if((!n||e.touches)&&r.apply(this,arguments)){var i,a,u,c,l,h,d,p,g,y,v,_=this,b=e.target.__data__.type,m="selection"===(o&&e.metaKey?b="overlay":b)?Mo:o&&e.altKey?So:To,x=t===Do?null:Io[b],w=t===zo?null:Uo[b],M=Ho(_),A=M.extent,T=M.selection,S=A[0][0],E=A[0][1],k=A[1][0],N=A[1][1],C=0,P=0,z=x&&w&&o&&e.shiftKey,D=Array.from(e.touches||[e],(t=>{const n=t.identifier;return(t=In(t,_)).point0=t.slice(),t.identifier=n,t}));if("overlay"===b){T&&(g=!0);const n=[D[0],D[1]||D[0]];M.selection=T=[[i=t===Do?S:No(n[0][0],n[1][0]),u=t===zo?E:No(n[0][1],n[1][1])],[l=t===Do?k:ko(n[0][0],n[1][0]),d=t===zo?N:ko(n[0][1],n[1][1])]],D.length>1&&U()}else i=T[0][0],u=T[0][1],l=T[1][0],d=T[1][1];a=i,c=u,h=l,p=d;var q=Dn(_).attr("pointer-events","none"),R=q.selectAll(".overlay").attr("cursor",Ro[b]);gi(_);var F=s(_,arguments,!0).beforestart();if(e.touches)F.moved=I,F.ended=B;else{var O=Dn(e.view).on("mousemove.brush",I,!0).on("mouseup.brush",B,!0);o&&O.on("keydown.brush",Y,!0).on("keyup.brush",L,!0),Yn(e.view)}f.call(_),F.start(e,m.name)}function I(t){for(const n of t.changedTouches||[t])for(const t of D)t.identifier===n.identifier&&(t.cur=In(n,_));if(z&&!y&&!v&&1===D.length){const t=D[0];Eo(t.cur[0]-t[0])>Eo(t.cur[1]-t[1])?v=!0:y=!0}for(const t of D)t.cur&&(t[0]=t.cur[0],t[1]=t.cur[1]);g=!0,wo(t),U(t)}function U(t){const n=D[0],e=n.point0;var r;switch(C=n[0]-e[0],P=n[1]-e[1],m){case Ao:case Mo:x&&(C=ko(S-i,No(k-l,C)),a=i+C,h=l+C),w&&(P=ko(E-u,No(N-d,P)),c=u+P,p=d+P);break;case To:D[1]?(x&&(a=ko(S,No(k,D[0][0])),h=ko(S,No(k,D[1][0])),x=1),w&&(c=ko(E,No(N,D[0][1])),p=ko(E,No(N,D[1][1])),w=1)):(x<0?(C=ko(S-i,No(k-i,C)),a=i+C,h=l):x>0&&(C=ko(S-l,No(k-l,C)),a=i,h=l+C),w<0?(P=ko(E-u,No(N-u,P)),c=u+P,p=d):w>0&&(P=ko(E-d,No(N-d,P)),c=u,p=d+P));break;case So:x&&(a=ko(S,No(k,i-C*x)),h=ko(S,No(k,l+C*x))),w&&(c=ko(E,No(N,u-P*w)),p=ko(E,No(N,d+P*w)))}h0&&(i=a-C),w<0?d=p-P:w>0&&(u=c-P),m=Ao,R.attr("cursor",Ro.selection),U());break;default:return}wo(t)}function L(t){switch(t.keyCode){case 16:z&&(y=v=z=!1,U());break;case 18:m===So&&(x<0?l=h:x>0&&(i=a),w<0?d=p:w>0&&(u=c),m=To,U());break;case 32:m===Ao&&(t.altKey?(x&&(l=h-C*x,i=a+C*x),w&&(d=p-P*w,u=c+P*w),m=So):(x<0?l=h:x>0&&(i=a),w<0?d=p:w>0&&(u=c),m=To),R.attr("cursor",Ro[b]),U());break;default:return}wo(t)}}function d(t){s(this,arguments).moved(t)}function p(t){s(this,arguments).ended(t)}function g(){var n=this.__brush||{selection:null};return n.extent=Po(e.apply(this,arguments)),n.dim=t,n}return c.move=function(n,e){n.tween?n.on("start.brush",(function(t){s(this,arguments).beforestart().start(t)})).on("interrupt.brush end.brush",(function(t){s(this,arguments).end(t)})).tween("brush",(function(){var n=this,r=n.__brush,i=s(n,arguments),o=r.selection,a=t.input("function"==typeof e?e.apply(this,arguments):e,r.extent),u=Mr(o,a);function c(t){r.selection=1===t&&null===a?null:u(t),f.call(n),i.brush()}return null!==o&&null!==a?c:c(1)})):n.each((function(){var n=this,r=arguments,i=n.__brush,o=t.input("function"==typeof e?e.apply(n,r):e,i.extent),a=s(n,r).beforestart();gi(n),i.selection=null===o?null:o,f.call(n),a.start().brush().end()}))},c.clear=function(t){c.move(t,null)},l.prototype={beforestart:function(){return 1==++this.active&&(this.state.emitter=this,this.starting=!0),this},start:function(t,n){return this.starting?(this.starting=!1,this.emit("start",t,n)):this.emit("brush",t),this},brush:function(t,n){return this.emit("brush",t,n),this},end:function(t,n){return 0==--this.active&&(delete this.state.emitter,this.emit("end",t,n)),this},emit:function(n,e,r){var i=Dn(this.that).datum();a.call(n,this.that,new mo(n,{sourceEvent:e,target:c,selection:t.output(this.state.selection),mode:r,dispatch:a}),i)}},c.extent=function(t){return arguments.length?(e="function"==typeof t?t:bo(Po(t)),c):e},c.filter=function(t){return arguments.length?(r="function"==typeof t?t:bo(!!t),c):r},c.touchable=function(t){return arguments.length?(i="function"==typeof t?t:bo(!!t),c):i},c.handleSize=function(t){return arguments.length?(u=+t,c):u},c.keyModifiers=function(t){return arguments.length?(o=!!t,c):o},c.on=function(){var t=a.on.apply(a,arguments);return t===a?c:t},c}var Vo=Math.abs,$o=Math.cos,Wo=Math.sin,Zo=Math.PI,Ko=Zo/2,Qo=2*Zo,Jo=Math.max,ta=1e-12;function na(t,n){return Array.from({length:n-t},((n,e)=>t+e))}function ea(t){return function(n,e){return t(n.source.value+n.target.value,e.source.value+e.target.value)}}function ra(t,n){var e=0,r=null,i=null,o=null;function a(a){var u,c=a.length,f=new Array(c),s=na(0,c),l=new Array(c*c),h=new Array(c),d=0;a=Float64Array.from({length:c*c},n?(t,n)=>a[n%c][n/c|0]:(t,n)=>a[n/c|0][n%c]);for(let n=0;nr(f[t],f[n])));for(const e of s){const r=n;if(t){const t=na(1+~c,c).filter((t=>t<0?a[~t*c+e]:a[e*c+t]));i&&t.sort(((t,n)=>i(t<0?-a[~t*c+e]:a[e*c+t],n<0?-a[~n*c+e]:a[e*c+n])));for(const r of t)if(r<0){(l[~r*c+e]||(l[~r*c+e]={source:null,target:null})).target={index:e,startAngle:n,endAngle:n+=a[~r*c+e]*d,value:a[~r*c+e]}}else{(l[e*c+r]||(l[e*c+r]={source:null,target:null})).source={index:e,startAngle:n,endAngle:n+=a[e*c+r]*d,value:a[e*c+r]}}h[e]={index:e,startAngle:r,endAngle:n,value:f[e]}}else{const t=na(0,c).filter((t=>a[e*c+t]||a[t*c+e]));i&&t.sort(((t,n)=>i(a[e*c+t],a[e*c+n])));for(const r of t){let t;if(eaa)if(Math.abs(s*u-c*f)>aa&&i){var h=e-o,d=r-a,p=u*u+c*c,g=h*h+d*d,y=Math.sqrt(p),v=Math.sqrt(l),_=i*Math.tan((ia-Math.acos((p+l-g)/(2*y*v)))/2),b=_/v,m=_/y;Math.abs(b-1)>aa&&(this._+="L"+(t+b*f)+","+(n+b*s)),this._+="A"+i+","+i+",0,0,"+ +(s*h>f*d)+","+(this._x1=t+m*u)+","+(this._y1=n+m*c)}else this._+="L"+(this._x1=t)+","+(this._y1=n);else;},arc:function(t,n,e,r,i,o){t=+t,n=+n,o=!!o;var a=(e=+e)*Math.cos(r),u=e*Math.sin(r),c=t+a,f=n+u,s=1^o,l=o?r-i:i-r;if(e<0)throw new Error("negative radius: "+e);null===this._x1?this._+="M"+c+","+f:(Math.abs(this._x1-c)>aa||Math.abs(this._y1-f)>aa)&&(this._+="L"+c+","+f),e&&(l<0&&(l=l%oa+oa),l>ua?this._+="A"+e+","+e+",0,1,"+s+","+(t-a)+","+(n-u)+"A"+e+","+e+",0,1,"+s+","+(this._x1=c)+","+(this._y1=f):l>aa&&(this._+="A"+e+","+e+",0,"+ +(l>=ia)+","+s+","+(this._x1=t+e*Math.cos(i))+","+(this._y1=n+e*Math.sin(i))))},rect:function(t,n,e,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+n)+"h"+ +e+"v"+ +r+"h"+-e+"Z"},toString:function(){return this._}};var sa=Array.prototype.slice;function la(t){return function(){return t}}function ha(t){return t.source}function da(t){return t.target}function pa(t){return t.radius}function ga(t){return t.startAngle}function ya(t){return t.endAngle}function va(){return 0}function _a(){return 10}function ba(t){var n=ha,e=da,r=pa,i=pa,o=ga,a=ya,u=va,c=null;function f(){var f,s=n.apply(this,arguments),l=e.apply(this,arguments),h=u.apply(this,arguments)/2,d=sa.call(arguments),p=+r.apply(this,(d[0]=s,d)),g=o.apply(this,d)-Ko,y=a.apply(this,d)-Ko,v=+i.apply(this,(d[0]=l,d)),_=o.apply(this,d)-Ko,b=a.apply(this,d)-Ko;if(c||(c=f=fa()),h>ta&&(Vo(y-g)>2*h+ta?y>g?(g+=h,y-=h):(g-=h,y+=h):g=y=(g+y)/2,Vo(b-_)>2*h+ta?b>_?(_+=h,b-=h):(_-=h,b+=h):_=b=(_+b)/2),c.moveTo(p*$o(g),p*Wo(g)),c.arc(0,0,p,g,y),g!==_||y!==b)if(t){var m=+t.apply(this,arguments),x=v-m,w=(_+b)/2;c.quadraticCurveTo(0,0,x*$o(_),x*Wo(_)),c.lineTo(v*$o(w),v*Wo(w)),c.lineTo(x*$o(b),x*Wo(b))}else c.quadraticCurveTo(0,0,v*$o(_),v*Wo(_)),c.arc(0,0,v,_,b);if(c.quadraticCurveTo(0,0,p*$o(g),p*Wo(g)),c.closePath(),f)return c=null,f+""||null}return t&&(f.headRadius=function(n){return arguments.length?(t="function"==typeof n?n:la(+n),f):t}),f.radius=function(t){return arguments.length?(r=i="function"==typeof t?t:la(+t),f):r},f.sourceRadius=function(t){return arguments.length?(r="function"==typeof t?t:la(+t),f):r},f.targetRadius=function(t){return arguments.length?(i="function"==typeof t?t:la(+t),f):i},f.startAngle=function(t){return arguments.length?(o="function"==typeof t?t:la(+t),f):o},f.endAngle=function(t){return arguments.length?(a="function"==typeof t?t:la(+t),f):a},f.padAngle=function(t){return arguments.length?(u="function"==typeof t?t:la(+t),f):u},f.source=function(t){return arguments.length?(n=t,f):n},f.target=function(t){return arguments.length?(e=t,f):e},f.context=function(t){return arguments.length?(c=null==t?null:t,f):c},f}var ma=Array.prototype.slice;function xa(t,n){return t-n}var wa=t=>()=>t;function Ma(t,n){for(var e,r=-1,i=n.length;++rr!=d>r&&e<(h-f)*(r-s)/(d-s)+f&&(i=-i)}return i}function Ta(t,n,e){var r,i,o,a;return function(t,n,e){return(n[0]-t[0])*(e[1]-t[1])==(e[0]-t[0])*(n[1]-t[1])}(t,n,e)&&(i=t[r=+(t[0]===n[0])],o=e[r],a=n[r],i<=o&&o<=a||a<=o&&o<=i)}function Sa(){}var Ea=[[],[[[1,1.5],[.5,1]]],[[[1.5,1],[1,1.5]]],[[[1.5,1],[.5,1]]],[[[1,.5],[1.5,1]]],[[[1,1.5],[.5,1]],[[1,.5],[1.5,1]]],[[[1,.5],[1,1.5]]],[[[1,.5],[.5,1]]],[[[.5,1],[1,.5]]],[[[1,1.5],[1,.5]]],[[[.5,1],[1,.5]],[[1.5,1],[1,1.5]]],[[[1.5,1],[1,.5]]],[[[.5,1],[1.5,1]]],[[[1,1.5],[1.5,1]]],[[[.5,1],[1,1.5]]],[]];function ka(){var t=1,n=1,e=I,r=u;function i(t){var n=e(t);if(Array.isArray(n))n=n.slice().sort(xa);else{var r=p(t),i=r[0],a=r[1];n=F(i,a,n),n=Z(Math.floor(i/n)*n,Math.floor(a/n)*n,n)}return n.map((function(n){return o(t,n)}))}function o(e,i){var o=[],u=[];return function(e,r,i){var o,u,c,f,s,l,h=new Array,d=new Array;o=u=-1,f=e[0]>=r,Ea[f<<1].forEach(p);for(;++o=r,Ea[c|f<<1].forEach(p);Ea[f<<0].forEach(p);for(;++u=r,s=e[u*t]>=r,Ea[f<<1|s<<2].forEach(p);++o=r,l=s,s=e[u*t+o+1]>=r,Ea[c|f<<1|s<<2|l<<3].forEach(p);Ea[f|s<<3].forEach(p)}o=-1,s=e[u*t]>=r,Ea[s<<2].forEach(p);for(;++o=r,Ea[s<<2|l<<3].forEach(p);function p(t){var n,e,r=[t[0][0]+o,t[0][1]+u],c=[t[1][0]+o,t[1][1]+u],f=a(r),s=a(c);(n=d[f])?(e=h[s])?(delete d[n.end],delete h[e.start],n===e?(n.ring.push(c),i(n.ring)):h[n.start]=d[e.end]={start:n.start,end:e.end,ring:n.ring.concat(e.ring)}):(delete d[n.end],n.ring.push(c),d[n.end=s]=n):(n=h[s])?(e=d[f])?(delete h[n.start],delete d[e.end],n===e?(n.ring.push(c),i(n.ring)):h[e.start]=d[n.end]={start:e.start,end:n.end,ring:e.ring.concat(n.ring)}):(delete h[n.start],n.ring.unshift(r),h[n.start=f]=n):h[f]=d[s]={start:f,end:s,ring:[r,c]}}Ea[s<<3].forEach(p)}(e,i,(function(t){r(t,e,i),function(t){for(var n=0,e=t.length,r=t[e-1][1]*t[0][0]-t[e-1][0]*t[0][1];++n0?o.push([t]):u.push(t)})),u.forEach((function(t){for(var n,e=0,r=o.length;e0&&a0&&u=0&&o>=0))throw new Error("invalid size");return t=r,n=o,i},i.thresholds=function(t){return arguments.length?(e="function"==typeof t?t:Array.isArray(t)?wa(ma.call(t)):wa(t),i):e},i.smooth=function(t){return arguments.length?(r=t?u:Sa,i):r===u},i}function Na(t,n,e){for(var r=t.width,i=t.height,o=1+(e<<1),a=0;a=e&&(u>=o&&(c-=t.data[u-o+a*r]),n.data[u-e+a*r]=c/Math.min(u+1,r-1+o-u,o))}function Ca(t,n,e){for(var r=t.width,i=t.height,o=1+(e<<1),a=0;a=e&&(u>=o&&(c-=t.data[a+(u-o)*r]),n.data[a+(u-e)*r]=c/Math.min(u+1,i-1+o-u,o))}function Pa(t){return t[0]}function za(t){return t[1]}function Da(){return 1}const qa=Math.pow(2,-52),Ra=new Uint32Array(512);class Fa{static from(t,n=Ha,e=Xa){const r=t.length,i=new Float64Array(2*r);for(let o=0;o>1;if(n>0&&"number"!=typeof t[0])throw new Error("Expected coords to contain numbers.");this.coords=t;const e=Math.max(2*n-5,0);this._triangles=new Uint32Array(3*e),this._halfedges=new Int32Array(3*e),this._hashSize=Math.ceil(Math.sqrt(n)),this._hullPrev=new Uint32Array(n),this._hullNext=new Uint32Array(n),this._hullTri=new Uint32Array(n),this._hullHash=new Int32Array(this._hashSize).fill(-1),this._ids=new Uint32Array(n),this._dists=new Float64Array(n),this.update()}update(){const{coords:t,_hullPrev:n,_hullNext:e,_hullTri:r,_hullHash:i}=this,o=t.length>>1;let a=1/0,u=1/0,c=-1/0,f=-1/0;for(let n=0;nc&&(c=e),r>f&&(f=r),this._ids[n]=n}const s=(a+c)/2,l=(u+f)/2;let h,d,p,g=1/0;for(let n=0;n0&&(d=n,g=e)}let _=t[2*d],b=t[2*d+1],m=1/0;for(let n=0;nr&&(n[e++]=i,r=this._dists[i])}return this.hull=n.subarray(0,e),this.triangles=new Uint32Array(0),void(this.halfedges=new Uint32Array(0))}if(Ua(y,v,_,b,x,w)){const t=d,n=_,e=b;d=p,_=x,b=w,p=t,x=n,w=e}const M=function(t,n,e,r,i,o){const a=e-t,u=r-n,c=i-t,f=o-n,s=a*a+u*u,l=c*c+f*f,h=.5/(a*f-u*c);return{x:t+(f*s-u*l)*h,y:n+(a*l-c*s)*h}}(y,v,_,b,x,w);this._cx=M.x,this._cy=M.y;for(let n=0;n0&&Math.abs(f-o)<=qa&&Math.abs(s-a)<=qa)continue;if(o=f,a=s,c===h||c===d||c===p)continue;let l=0;for(let t=0,n=this._hashKey(f,s);t0?3-e:1+e)/4}(t-this._cx,n-this._cy)*this._hashSize)%this._hashSize}_legalize(t){const{_triangles:n,_halfedges:e,coords:r}=this;let i=0,o=0;for(;;){const a=e[t],u=t-t%3;if(o=u+(t+2)%3,-1===a){if(0===i)break;t=Ra[--i];continue}const c=a-a%3,f=u+(t+1)%3,s=c+(a+2)%3,l=n[o],h=n[t],d=n[f],p=n[s];if(Ba(r[2*l],r[2*l+1],r[2*h],r[2*h+1],r[2*d],r[2*d+1],r[2*p],r[2*p+1])){n[t]=p,n[a]=l;const r=e[s];if(-1===r){let n=this._hullStart;do{if(this._hullTri[n]===s){this._hullTri[n]=t;break}n=this._hullPrev[n]}while(n!==this._hullStart)}this._link(t,r),this._link(a,e[o]),this._link(o,s);const u=c+(a+1)%3;i=33306690738754716e-32*Math.abs(a+u)?a-u:0}function Ua(t,n,e,r,i,o){return(Ia(i,o,t,n,e,r)||Ia(t,n,e,r,i,o)||Ia(e,r,i,o,t,n))<0}function Ba(t,n,e,r,i,o,a,u){const c=t-a,f=n-u,s=e-a,l=r-u,h=i-a,d=o-u,p=s*s+l*l,g=h*h+d*d;return c*(l*g-p*d)-f*(s*g-p*h)+(c*c+f*f)*(s*d-l*h)<0}function Ya(t,n,e,r,i,o){const a=e-t,u=r-n,c=i-t,f=o-n,s=a*a+u*u,l=c*c+f*f,h=.5/(a*f-u*c),d=(f*s-u*l)*h,p=(a*l-c*s)*h;return d*d+p*p}function La(t,n,e,r){if(r-e<=20)for(let i=e+1;i<=r;i++){const r=t[i],o=n[r];let a=i-1;for(;a>=e&&n[t[a]]>o;)t[a+1]=t[a--];t[a+1]=r}else{let i=e+1,o=r;ja(t,e+r>>1,i),n[t[e]]>n[t[r]]&&ja(t,e,r),n[t[i]]>n[t[r]]&&ja(t,i,r),n[t[e]]>n[t[i]]&&ja(t,e,i);const a=t[i],u=n[a];for(;;){do{i++}while(n[t[i]]u);if(o=o-e?(La(t,n,i,r),La(t,n,e,o-1)):(La(t,n,e,o-1),La(t,n,i,r))}}function ja(t,n,e){const r=t[n];t[n]=t[e],t[e]=r}function Ha(t){return t[0]}function Xa(t){return t[1]}const Ga=1e-6;class Va{constructor(){this._x0=this._y0=this._x1=this._y1=null,this._=""}moveTo(t,n){this._+=`M${this._x0=this._x1=+t},${this._y0=this._y1=+n}`}closePath(){null!==this._x1&&(this._x1=this._x0,this._y1=this._y0,this._+="Z")}lineTo(t,n){this._+=`L${this._x1=+t},${this._y1=+n}`}arc(t,n,e){const r=(t=+t)+(e=+e),i=n=+n;if(e<0)throw new Error("negative radius");null===this._x1?this._+=`M${r},${i}`:(Math.abs(this._x1-r)>Ga||Math.abs(this._y1-i)>Ga)&&(this._+="L"+r+","+i),e&&(this._+=`A${e},${e},0,1,1,${t-e},${n}A${e},${e},0,1,1,${this._x1=r},${this._y1=i}`)}rect(t,n,e,r){this._+=`M${this._x0=this._x1=+t},${this._y0=this._y1=+n}h${+e}v${+r}h${-e}Z`}value(){return this._||null}}class $a{constructor(){this._=[]}moveTo(t,n){this._.push([t,n])}closePath(){this._.push(this._[0].slice())}lineTo(t,n){this._.push([t,n])}value(){return this._.length?this._:null}}class Wa{constructor(t,[n,e,r,i]=[0,0,960,500]){if(!((r=+r)>=(n=+n)&&(i=+i)>=(e=+e)))throw new Error("invalid bounds");this.delaunay=t,this._circumcenters=new Float64Array(2*t.points.length),this.vectors=new Float64Array(2*t.points.length),this.xmax=r,this.xmin=n,this.ymax=i,this.ymin=e,this._init()}update(){return this.delaunay.update(),this._init(),this}_init(){const{delaunay:{points:t,hull:n,triangles:e},vectors:r}=this,i=this.circumcenters=this._circumcenters.subarray(0,e.length/3*2);for(let n,r,o=0,a=0,u=e.length;o1;)i-=2;for(let t=2;t4)for(let t=0;t0){if(n>=this.ymax)return null;(i=(this.ymax-n)/r)0){if(t>=this.xmax)return null;(i=(this.xmax-t)/e)this.xmax?2:0)|(nthis.ymax?8:0)}}const Za=2*Math.PI,Ka=Math.pow;function Qa(t){return t[0]}function Ja(t){return t[1]}function tu(t,n,e){return[t+Math.sin(t+n)*e,n+Math.cos(t-n)*e]}class nu{static from(t,n=Qa,e=Ja,r){return new nu("length"in t?function(t,n,e,r){const i=t.length,o=new Float64Array(2*i);for(let a=0;a2&&function(t){const{triangles:n,coords:e}=t;for(let t=0;t1e-10)return!1}return!0}(t)){this.collinear=Int32Array.from({length:n.length/2},((t,n)=>n)).sort(((t,e)=>n[2*t]-n[2*e]||n[2*t+1]-n[2*e+1]));const t=this.collinear[0],e=this.collinear[this.collinear.length-1],r=[n[2*t],n[2*t+1],n[2*e],n[2*e+1]],i=1e-8*Math.hypot(r[3]-r[1],r[2]-r[0]);for(let t=0,e=n.length/2;t0&&(this.triangles=new Int32Array(3).fill(-1),this.halfedges=new Int32Array(3).fill(-1),this.triangles[0]=r[0],this.triangles[1]=r[1],this.triangles[2]=r[1],o[r[0]]=1,2===r.length&&(o[r[1]]=0))}voronoi(t){return new Wa(this,t)}*neighbors(t){const{inedges:n,hull:e,_hullIndex:r,halfedges:i,triangles:o,collinear:a}=this;if(a){const n=a.indexOf(t);return n>0&&(yield a[n-1]),void(n=0&&i!==e&&i!==r;)e=i;return i}_step(t,n,e){const{inedges:r,hull:i,_hullIndex:o,halfedges:a,triangles:u,points:c}=this;if(-1===r[t]||!c.length)return(t+1)%(c.length>>1);let f=t,s=Ka(n-c[2*t],2)+Ka(e-c[2*t+1],2);const l=r[t];let h=l;do{let r=u[h];const l=Ka(n-c[2*r],2)+Ka(e-c[2*r+1],2);if(l9999?"+"+au(t,6):au(t,4)}(t.getUTCFullYear())+"-"+au(t.getUTCMonth()+1,2)+"-"+au(t.getUTCDate(),2)+(i?"T"+au(n,2)+":"+au(e,2)+":"+au(r,2)+"."+au(i,3)+"Z":r?"T"+au(n,2)+":"+au(e,2)+":"+au(r,2)+"Z":e||n?"T"+au(n,2)+":"+au(e,2)+"Z":"")}function cu(t){var n=new RegExp('["'+t+"\n\r]"),e=t.charCodeAt(0);function r(t,n){var r,i=[],o=t.length,a=0,u=0,c=o<=0,f=!1;function s(){if(c)return ru;if(f)return f=!1,eu;var n,r,i=a;if(34===t.charCodeAt(i)){for(;a++=o?c=!0:10===(r=t.charCodeAt(a++))?f=!0:13===r&&(f=!0,10===t.charCodeAt(a)&&++a),t.slice(i+1,n-1).replace(/""/g,'"')}for(;aNu(n,e).then((n=>(new DOMParser).parseFromString(n,t)))}var Ru=qu("application/xml"),Fu=qu("text/html"),Ou=qu("image/svg+xml");function Iu(t,n,e,r){if(isNaN(n)||isNaN(e))return t;var i,o,a,u,c,f,s,l,h,d=t._root,p={data:r},g=t._x0,y=t._y0,v=t._x1,_=t._y1;if(!d)return t._root=p,t;for(;d.length;)if((f=n>=(o=(g+v)/2))?g=o:v=o,(s=e>=(a=(y+_)/2))?y=a:_=a,i=d,!(d=d[l=s<<1|f]))return i[l]=p,t;if(u=+t._x.call(null,d.data),c=+t._y.call(null,d.data),n===u&&e===c)return p.next=d,i?i[l]=p:t._root=p,t;do{i=i?i[l]=new Array(4):t._root=new Array(4),(f=n>=(o=(g+v)/2))?g=o:v=o,(s=e>=(a=(y+_)/2))?y=a:_=a}while((l=s<<1|f)==(h=(c>=a)<<1|u>=o));return i[h]=d,i[l]=p,t}function Uu(t,n,e,r,i){this.node=t,this.x0=n,this.y0=e,this.x1=r,this.y1=i}function Bu(t){return t[0]}function Yu(t){return t[1]}function Lu(t,n,e){var r=new ju(null==n?Bu:n,null==e?Yu:e,NaN,NaN,NaN,NaN);return null==t?r:r.addAll(t)}function ju(t,n,e,r,i,o){this._x=t,this._y=n,this._x0=e,this._y0=r,this._x1=i,this._y1=o,this._root=void 0}function Hu(t){for(var n={data:t.data},e=n;t=t.next;)e=e.next={data:t.data};return n}var Xu=Lu.prototype=ju.prototype;function Gu(t){return function(){return t}}function Vu(t){return 1e-6*(t()-.5)}function $u(t){return t.x+t.vx}function Wu(t){return t.y+t.vy}function Zu(t){return t.index}function Ku(t,n){var e=t.get(n);if(!e)throw new Error("node not found: "+n);return e}Xu.copy=function(){var t,n,e=new ju(this._x,this._y,this._x0,this._y0,this._x1,this._y1),r=this._root;if(!r)return e;if(!r.length)return e._root=Hu(r),e;for(t=[{source:r,target:e._root=new Array(4)}];r=t.pop();)for(var i=0;i<4;++i)(n=r.source[i])&&(n.length?t.push({source:n,target:r.target[i]=new Array(4)}):r.target[i]=Hu(n));return e},Xu.add=function(t){const n=+this._x.call(null,t),e=+this._y.call(null,t);return Iu(this.cover(n,e),n,e,t)},Xu.addAll=function(t){var n,e,r,i,o=t.length,a=new Array(o),u=new Array(o),c=1/0,f=1/0,s=-1/0,l=-1/0;for(e=0;es&&(s=r),il&&(l=i));if(c>s||f>l)return this;for(this.cover(c,f).cover(s,l),e=0;et||t>=i||r>n||n>=o;)switch(u=(nh||(o=c.y0)>d||(a=c.x1)=v)<<1|t>=y)&&(c=p[p.length-1],p[p.length-1]=p[p.length-1-f],p[p.length-1-f]=c)}else{var _=t-+this._x.call(null,g.data),b=n-+this._y.call(null,g.data),m=_*_+b*b;if(m=(u=(p+y)/2))?p=u:y=u,(s=a>=(c=(g+v)/2))?g=c:v=c,n=d,!(d=d[l=s<<1|f]))return this;if(!d.length)break;(n[l+1&3]||n[l+2&3]||n[l+3&3])&&(e=n,h=l)}for(;d.data!==t;)if(r=d,!(d=d.next))return this;return(i=d.next)&&delete d.next,r?(i?r.next=i:delete r.next,this):n?(i?n[l]=i:delete n[l],(d=n[0]||n[1]||n[2]||n[3])&&d===(n[3]||n[2]||n[1]||n[0])&&!d.length&&(e?e[h]=d:this._root=d),this):(this._root=i,this)},Xu.removeAll=function(t){for(var n=0,e=t.length;n1?r[0]+r.slice(2):r,+t.slice(e+1)]}function rc(t){return(t=ec(Math.abs(t)))?t[1]:NaN}var ic,oc=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function ac(t){if(!(n=oc.exec(t)))throw new Error("invalid format: "+t);var n;return new uc({fill:n[1],align:n[2],sign:n[3],symbol:n[4],zero:n[5],width:n[6],comma:n[7],precision:n[8]&&n[8].slice(1),trim:n[9],type:n[10]})}function uc(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}function cc(t,n){var e=ec(t,n);if(!e)return t+"";var r=e[0],i=e[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")}ac.prototype=uc.prototype,uc.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var fc={"%":(t,n)=>(100*t).toFixed(n),b:t=>Math.round(t).toString(2),c:t=>t+"",d:function(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)},e:(t,n)=>t.toExponential(n),f:(t,n)=>t.toFixed(n),g:(t,n)=>t.toPrecision(n),o:t=>Math.round(t).toString(8),p:(t,n)=>cc(100*t,n),r:cc,s:function(t,n){var e=ec(t,n);if(!e)return t+"";var r=e[0],i=e[1],o=i-(ic=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,a=r.length;return o===a?r:o>a?r+new Array(o-a+1).join("0"):o>0?r.slice(0,o)+"."+r.slice(o):"0."+new Array(1-o).join("0")+ec(t,Math.max(0,n+o-1))[0]},X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function sc(t){return t}var lc,hc=Array.prototype.map,dc=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];function pc(t){var n,e,r=void 0===t.grouping||void 0===t.thousands?sc:(n=hc.call(t.grouping,Number),e=t.thousands+"",function(t,r){for(var i=t.length,o=[],a=0,u=n[0],c=0;i>0&&u>0&&(c+u+1>r&&(u=Math.max(1,r-c)),o.push(t.substring(i-=u,i+u)),!((c+=u+1)>r));)u=n[a=(a+1)%n.length];return o.reverse().join(e)}),i=void 0===t.currency?"":t.currency[0]+"",o=void 0===t.currency?"":t.currency[1]+"",a=void 0===t.decimal?".":t.decimal+"",u=void 0===t.numerals?sc:function(t){return function(n){return n.replace(/[0-9]/g,(function(n){return t[+n]}))}}(hc.call(t.numerals,String)),c=void 0===t.percent?"%":t.percent+"",f=void 0===t.minus?"−":t.minus+"",s=void 0===t.nan?"NaN":t.nan+"";function l(t){var n=(t=ac(t)).fill,e=t.align,l=t.sign,h=t.symbol,d=t.zero,p=t.width,g=t.comma,y=t.precision,v=t.trim,_=t.type;"n"===_?(g=!0,_="g"):fc[_]||(void 0===y&&(y=12),v=!0,_="g"),(d||"0"===n&&"="===e)&&(d=!0,n="0",e="=");var b="$"===h?i:"#"===h&&/[boxX]/.test(_)?"0"+_.toLowerCase():"",m="$"===h?o:/[%p]/.test(_)?c:"",x=fc[_],w=/[defgprs%]/.test(_);function M(t){var i,o,c,h=b,M=m;if("c"===_)M=x(t)+M,t="";else{var A=(t=+t)<0||1/t<0;if(t=isNaN(t)?s:x(Math.abs(t),y),v&&(t=function(t){t:for(var n,e=t.length,r=1,i=-1;r0&&(i=0)}return i>0?t.slice(0,i)+t.slice(n+1):t}(t)),A&&0==+t&&"+"!==l&&(A=!1),h=(A?"("===l?l:f:"-"===l||"("===l?"":l)+h,M=("s"===_?dc[8+ic/3]:"")+M+(A&&"("===l?")":""),w)for(i=-1,o=t.length;++i(c=t.charCodeAt(i))||c>57){M=(46===c?a+t.slice(i+1):t.slice(i))+M,t=t.slice(0,i);break}}g&&!d&&(t=r(t,1/0));var T=h.length+t.length+M.length,S=T>1)+h+t+M+S.slice(T);break;default:t=S+h+t+M}return u(t)}return y=void 0===y?6:/[gprs]/.test(_)?Math.max(1,Math.min(21,y)):Math.max(0,Math.min(20,y)),M.toString=function(){return t+""},M}return{format:l,formatPrefix:function(t,n){var e=l(((t=ac(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor(rc(n)/3))),i=Math.pow(10,-r),o=dc[8+r/3];return function(t){return e(i*t)+o}}}}function gc(n){return lc=pc(n),t.format=lc.format,t.formatPrefix=lc.formatPrefix,lc}function yc(t){return Math.max(0,-rc(Math.abs(t)))}function vc(t,n){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(rc(n)/3)))-rc(Math.abs(t)))}function _c(t,n){return t=Math.abs(t),n=Math.abs(n)-t,Math.max(0,rc(n)-rc(t))+1}t.format=void 0,t.formatPrefix=void 0,gc({thousands:",",grouping:[3],currency:["$",""]});var bc=1e-6,mc=1e-12,xc=Math.PI,wc=xc/2,Mc=xc/4,Ac=2*xc,Tc=180/xc,Sc=xc/180,Ec=Math.abs,kc=Math.atan,Nc=Math.atan2,Cc=Math.cos,Pc=Math.ceil,zc=Math.exp,Dc=Math.hypot,qc=Math.log,Rc=Math.pow,Fc=Math.sin,Oc=Math.sign||function(t){return t>0?1:t<0?-1:0},Ic=Math.sqrt,Uc=Math.tan;function Bc(t){return t>1?0:t<-1?xc:Math.acos(t)}function Yc(t){return t>1?wc:t<-1?-wc:Math.asin(t)}function Lc(t){return(t=Fc(t/2))*t}function jc(){}function Hc(t,n){t&&Gc.hasOwnProperty(t.type)&&Gc[t.type](t,n)}var Xc={Feature:function(t,n){Hc(t.geometry,n)},FeatureCollection:function(t,n){for(var e=t.features,r=-1,i=e.length;++r=0?1:-1,i=r*e,o=Cc(n=(n*=Sc)/2+Mc),a=Fc(n),u=tf*a,c=Jc*o+u*Cc(i),f=u*r*Fc(i);df.add(Nc(f,c)),Qc=t,Jc=o,tf=a}function mf(t){return[Nc(t[1],t[0]),Yc(t[2])]}function xf(t){var n=t[0],e=t[1],r=Cc(e);return[r*Cc(n),r*Fc(n),Fc(e)]}function wf(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]}function Mf(t,n){return[t[1]*n[2]-t[2]*n[1],t[2]*n[0]-t[0]*n[2],t[0]*n[1]-t[1]*n[0]]}function Af(t,n){t[0]+=n[0],t[1]+=n[1],t[2]+=n[2]}function Tf(t,n){return[t[0]*n,t[1]*n,t[2]*n]}function Sf(t){var n=Ic(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=n,t[1]/=n,t[2]/=n}var Ef,kf,Nf,Cf,Pf,zf,Df,qf,Rf,Ff,Of,If,Uf,Bf,Yf,Lf,jf={point:Hf,lineStart:Gf,lineEnd:Vf,polygonStart:function(){jf.point=$f,jf.lineStart=Wf,jf.lineEnd=Zf,sf=new g,gf.polygonStart()},polygonEnd:function(){gf.polygonEnd(),jf.point=Hf,jf.lineStart=Gf,jf.lineEnd=Vf,df<0?(nf=-(rf=180),ef=-(of=90)):sf>bc?of=90:sf<-1e-6&&(ef=-90),hf[0]=nf,hf[1]=rf},sphere:function(){nf=-(rf=180),ef=-(of=90)}};function Hf(t,n){lf.push(hf=[nf=t,rf=t]),nof&&(of=n)}function Xf(t,n){var e=xf([t*Sc,n*Sc]);if(ff){var r=Mf(ff,e),i=Mf([r[1],-r[0],0],r);Sf(i),i=mf(i);var o,a=t-af,u=a>0?1:-1,c=i[0]*Tc*u,f=Ec(a)>180;f^(u*afof&&(of=o):f^(u*af<(c=(c+360)%360-180)&&cof&&(of=n)),f?tKf(nf,rf)&&(rf=t):Kf(t,rf)>Kf(nf,rf)&&(nf=t):rf>=nf?(trf&&(rf=t)):t>af?Kf(nf,t)>Kf(nf,rf)&&(rf=t):Kf(t,rf)>Kf(nf,rf)&&(nf=t)}else lf.push(hf=[nf=t,rf=t]);nof&&(of=n),ff=e,af=t}function Gf(){jf.point=Xf}function Vf(){hf[0]=nf,hf[1]=rf,jf.point=Hf,ff=null}function $f(t,n){if(ff){var e=t-af;sf.add(Ec(e)>180?e+(e>0?360:-360):e)}else uf=t,cf=n;gf.point(t,n),Xf(t,n)}function Wf(){gf.lineStart()}function Zf(){$f(uf,cf),gf.lineEnd(),Ec(sf)>bc&&(nf=-(rf=180)),hf[0]=nf,hf[1]=rf,ff=null}function Kf(t,n){return(n-=t)<0?n+360:n}function Qf(t,n){return t[0]-n[0]}function Jf(t,n){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nxc?t+Math.round(-t/Ac)*Ac:t,n]}function ps(t,n,e){return(t%=Ac)?n||e?hs(ys(t),vs(n,e)):ys(t):n||e?vs(n,e):ds}function gs(t){return function(n,e){return[(n+=t)>xc?n-Ac:n<-xc?n+Ac:n,e]}}function ys(t){var n=gs(t);return n.invert=gs(-t),n}function vs(t,n){var e=Cc(t),r=Fc(t),i=Cc(n),o=Fc(n);function a(t,n){var a=Cc(n),u=Cc(t)*a,c=Fc(t)*a,f=Fc(n),s=f*e+u*r;return[Nc(c*i-s*o,u*e-f*r),Yc(s*i+c*o)]}return a.invert=function(t,n){var a=Cc(n),u=Cc(t)*a,c=Fc(t)*a,f=Fc(n),s=f*i-c*o;return[Nc(c*i+f*o,u*e+s*r),Yc(s*e-u*r)]},a}function _s(t){function n(n){return(n=t(n[0]*Sc,n[1]*Sc))[0]*=Tc,n[1]*=Tc,n}return t=ps(t[0]*Sc,t[1]*Sc,t.length>2?t[2]*Sc:0),n.invert=function(n){return(n=t.invert(n[0]*Sc,n[1]*Sc))[0]*=Tc,n[1]*=Tc,n},n}function bs(t,n,e,r,i,o){if(e){var a=Cc(n),u=Fc(n),c=r*e;null==i?(i=n+r*Ac,o=n-c/2):(i=ms(a,i),o=ms(a,o),(r>0?io)&&(i+=r*Ac));for(var f,s=i;r>0?s>o:s1&&n.push(n.pop().concat(n.shift()))},result:function(){var e=n;return n=[],t=null,e}}}function ws(t,n){return Ec(t[0]-n[0])=0;--o)i.point((s=f[o])[0],s[1]);else r(h.x,h.p.x,-1,i);h=h.p}f=(h=h.o).z,d=!d}while(!h.v);i.lineEnd()}}}function Ts(t){if(n=t.length){for(var n,e,r=0,i=t[0];++r=0?1:-1,E=S*T,k=E>xc,N=v*M;if(c.add(Nc(N*S*Fc(E),_*A+N*Cc(E))),a+=k?T+S*Ac:T,k^p>=e^x>=e){var C=Mf(xf(d),xf(m));Sf(C);var P=Mf(o,C);Sf(P);var z=(k^T>=0?-1:1)*Yc(P[2]);(r>z||r===z&&(C[0]||C[1]))&&(u+=k^T>=0?1:-1)}}return(a<-1e-6||a0){for(l||(i.polygonStart(),l=!0),i.lineStart(),t=0;t1&&2&c&&h.push(h.pop().concat(h.shift())),a.push(h.filter(Ns))}return h}}function Ns(t){return t.length>1}function Cs(t,n){return((t=t.x)[0]<0?t[1]-wc-bc:wc-t[1])-((n=n.x)[0]<0?n[1]-wc-bc:wc-n[1])}ds.invert=ds;var Ps=ks((function(){return!0}),(function(t){var n,e=NaN,r=NaN,i=NaN;return{lineStart:function(){t.lineStart(),n=1},point:function(o,a){var u=o>0?xc:-xc,c=Ec(o-e);Ec(c-xc)0?wc:-wc),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(u,r),t.point(o,r),n=0):i!==u&&c>=xc&&(Ec(e-i)bc?kc((Fc(n)*(o=Cc(r))*Fc(e)-Fc(r)*(i=Cc(n))*Fc(t))/(i*o*a)):(n+r)/2}(e,r,o,a),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(u,r),n=0),t.point(e=o,r=a),i=u},lineEnd:function(){t.lineEnd(),e=r=NaN},clean:function(){return 2-n}}}),(function(t,n,e,r){var i;if(null==t)i=e*wc,r.point(-xc,i),r.point(0,i),r.point(xc,i),r.point(xc,0),r.point(xc,-i),r.point(0,-i),r.point(-xc,-i),r.point(-xc,0),r.point(-xc,i);else if(Ec(t[0]-n[0])>bc){var o=t[0]0,i=Ec(n)>bc;function o(t,e){return Cc(t)*Cc(e)>n}function a(t,e,r){var i=[1,0,0],o=Mf(xf(t),xf(e)),a=wf(o,o),u=o[0],c=a-u*u;if(!c)return!r&&t;var f=n*a/c,s=-n*u/c,l=Mf(i,o),h=Tf(i,f);Af(h,Tf(o,s));var d=l,p=wf(h,d),g=wf(d,d),y=p*p-g*(wf(h,h)-1);if(!(y<0)){var v=Ic(y),_=Tf(d,(-p-v)/g);if(Af(_,h),_=mf(_),!r)return _;var b,m=t[0],x=e[0],w=t[1],M=e[1];x0^_[1]<(Ec(_[0]-m)xc^(m<=_[0]&&_[0]<=x)){var S=Tf(d,(-p+v)/g);return Af(S,h),[_,mf(S)]}}}function u(n,e){var i=r?t:xc-t,o=0;return n<-i?o|=1:n>i&&(o|=2),e<-i?o|=4:e>i&&(o|=8),o}return ks(o,(function(t){var n,e,c,f,s;return{lineStart:function(){f=c=!1,s=1},point:function(l,h){var d,p=[l,h],g=o(l,h),y=r?g?0:u(l,h):g?u(l+(l<0?xc:-xc),h):0;if(!n&&(f=c=g)&&t.lineStart(),g!==c&&(!(d=a(n,p))||ws(n,d)||ws(p,d))&&(p[2]=1),g!==c)s=0,g?(t.lineStart(),d=a(p,n),t.point(d[0],d[1])):(d=a(n,p),t.point(d[0],d[1],2),t.lineEnd()),n=d;else if(i&&n&&r^g){var v;y&e||!(v=a(p,n,!0))||(s=0,r?(t.lineStart(),t.point(v[0][0],v[0][1]),t.point(v[1][0],v[1][1]),t.lineEnd()):(t.point(v[1][0],v[1][1]),t.lineEnd(),t.lineStart(),t.point(v[0][0],v[0][1],3)))}!g||n&&ws(n,p)||t.point(p[0],p[1]),n=p,c=g,e=y},lineEnd:function(){c&&t.lineEnd(),n=null},clean:function(){return s|(f&&c)<<1}}}),(function(n,r,i,o){bs(o,t,e,i,n,r)}),r?[0,-t]:[-xc,t-xc])}var Ds,qs,Rs,Fs,Os=1e9,Is=-Os;function Us(t,n,e,r){function i(i,o){return t<=i&&i<=e&&n<=o&&o<=r}function o(i,o,u,f){var s=0,l=0;if(null==i||(s=a(i,u))!==(l=a(o,u))||c(i,o)<0^u>0)do{f.point(0===s||3===s?t:e,s>1?r:n)}while((s=(s+u+4)%4)!==l);else f.point(o[0],o[1])}function a(r,i){return Ec(r[0]-t)0?0:3:Ec(r[0]-e)0?2:1:Ec(r[1]-n)0?1:0:i>0?3:2}function u(t,n){return c(t.x,n.x)}function c(t,n){var e=a(t,1),r=a(n,1);return e!==r?e-r:0===e?n[1]-t[1]:1===e?t[0]-n[0]:2===e?t[1]-n[1]:n[0]-t[0]}return function(a){var c,f,s,l,h,d,p,g,y,v,_,b=a,m=xs(),x={point:w,lineStart:function(){x.point=M,f&&f.push(s=[]);v=!0,y=!1,p=g=NaN},lineEnd:function(){c&&(M(l,h),d&&y&&m.rejoin(),c.push(m.result()));x.point=w,y&&b.lineEnd()},polygonStart:function(){b=m,c=[],f=[],_=!0},polygonEnd:function(){var n=function(){for(var n=0,e=0,i=f.length;er&&(h-o)*(r-a)>(d-a)*(t-o)&&++n:d<=r&&(h-o)*(r-a)<(d-a)*(t-o)&&--n;return n}(),e=_&&n,i=(c=V(c)).length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),o(null,null,1,a),a.lineEnd()),i&&As(c,u,n,o,a),a.polygonEnd());b=a,c=f=s=null}};function w(t,n){i(t,n)&&b.point(t,n)}function M(o,a){var u=i(o,a);if(f&&s.push([o,a]),v)l=o,h=a,d=u,v=!1,u&&(b.lineStart(),b.point(o,a));else if(u&&y)b.point(o,a);else{var c=[p=Math.max(Is,Math.min(Os,p)),g=Math.max(Is,Math.min(Os,g))],m=[o=Math.max(Is,Math.min(Os,o)),a=Math.max(Is,Math.min(Os,a))];!function(t,n,e,r,i,o){var a,u=t[0],c=t[1],f=0,s=1,l=n[0]-u,h=n[1]-c;if(a=e-u,l||!(a>0)){if(a/=l,l<0){if(a0){if(a>s)return;a>f&&(f=a)}if(a=i-u,l||!(a<0)){if(a/=l,l<0){if(a>s)return;a>f&&(f=a)}else if(l>0){if(a0)){if(a/=h,h<0){if(a0){if(a>s)return;a>f&&(f=a)}if(a=o-c,h||!(a<0)){if(a/=h,h<0){if(a>s)return;a>f&&(f=a)}else if(h>0){if(a0&&(t[0]=u+f*l,t[1]=c+f*h),s<1&&(n[0]=u+s*l,n[1]=c+s*h),!0}}}}}(c,m,t,n,e,r)?u&&(b.lineStart(),b.point(o,a),_=!1):(y||(b.lineStart(),b.point(c[0],c[1])),b.point(m[0],m[1]),u||b.lineEnd(),_=!1)}p=o,g=a,y=u}return x}}var Bs={sphere:jc,point:jc,lineStart:function(){Bs.point=Ls,Bs.lineEnd=Ys},lineEnd:jc,polygonStart:jc,polygonEnd:jc};function Ys(){Bs.point=Bs.lineEnd=jc}function Ls(t,n){qs=t*=Sc,Rs=Fc(n*=Sc),Fs=Cc(n),Bs.point=js}function js(t,n){t*=Sc;var e=Fc(n*=Sc),r=Cc(n),i=Ec(t-qs),o=Cc(i),a=r*Fc(i),u=Fs*e-Rs*r*o,c=Rs*e+Fs*r*o;Ds.add(Nc(Ic(a*a+u*u),c)),qs=t,Rs=e,Fs=r}function Hs(t){return Ds=new g,Wc(t,Bs),+Ds}var Xs=[null,null],Gs={type:"LineString",coordinates:Xs};function Vs(t,n){return Xs[0]=t,Xs[1]=n,Hs(Gs)}var $s={Feature:function(t,n){return Zs(t.geometry,n)},FeatureCollection:function(t,n){for(var e=t.features,r=-1,i=e.length;++r0&&(i=Vs(t[o],t[o-1]))>0&&e<=i&&r<=i&&(e+r-i)*(1-Math.pow((e-r)/i,2))bc})).map(c)).concat(Z(Pc(o/d)*d,i,d).filter((function(t){return Ec(t%g)>bc})).map(f))}return v.lines=function(){return _().map((function(t){return{type:"LineString",coordinates:t}}))},v.outline=function(){return{type:"Polygon",coordinates:[s(r).concat(l(a).slice(1),s(e).reverse().slice(1),l(u).reverse().slice(1))]}},v.extent=function(t){return arguments.length?v.extentMajor(t).extentMinor(t):v.extentMinor()},v.extentMajor=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],u=+t[0][1],a=+t[1][1],r>e&&(t=r,r=e,e=t),u>a&&(t=u,u=a,a=t),v.precision(y)):[[r,u],[e,a]]},v.extentMinor=function(e){return arguments.length?(n=+e[0][0],t=+e[1][0],o=+e[0][1],i=+e[1][1],n>t&&(e=n,n=t,t=e),o>i&&(e=o,o=i,i=e),v.precision(y)):[[n,o],[t,i]]},v.step=function(t){return arguments.length?v.stepMajor(t).stepMinor(t):v.stepMinor()},v.stepMajor=function(t){return arguments.length?(p=+t[0],g=+t[1],v):[p,g]},v.stepMinor=function(t){return arguments.length?(h=+t[0],d=+t[1],v):[h,d]},v.precision=function(h){return arguments.length?(y=+h,c=el(o,i,90),f=rl(n,t,y),s=el(u,a,90),l=rl(r,e,y),v):y},v.extentMajor([[-180,-89.999999],[180,89.999999]]).extentMinor([[-180,-80.000001],[180,80.000001]])}var ol,al,ul,cl,fl=t=>t,sl=new g,ll=new g,hl={point:jc,lineStart:jc,lineEnd:jc,polygonStart:function(){hl.lineStart=dl,hl.lineEnd=yl},polygonEnd:function(){hl.lineStart=hl.lineEnd=hl.point=jc,sl.add(Ec(ll)),ll=new g},result:function(){var t=sl/2;return sl=new g,t}};function dl(){hl.point=pl}function pl(t,n){hl.point=gl,ol=ul=t,al=cl=n}function gl(t,n){ll.add(cl*t-ul*n),ul=t,cl=n}function yl(){gl(ol,al)}var vl=1/0,_l=vl,bl=-vl,ml=bl,xl={point:function(t,n){tbl&&(bl=t);n<_l&&(_l=n);n>ml&&(ml=n)},lineStart:jc,lineEnd:jc,polygonStart:jc,polygonEnd:jc,result:function(){var t=[[vl,_l],[bl,ml]];return bl=ml=-(_l=vl=1/0),t}};var wl,Ml,Al,Tl,Sl=0,El=0,kl=0,Nl=0,Cl=0,Pl=0,zl=0,Dl=0,ql=0,Rl={point:Fl,lineStart:Ol,lineEnd:Bl,polygonStart:function(){Rl.lineStart=Yl,Rl.lineEnd=Ll},polygonEnd:function(){Rl.point=Fl,Rl.lineStart=Ol,Rl.lineEnd=Bl},result:function(){var t=ql?[zl/ql,Dl/ql]:Pl?[Nl/Pl,Cl/Pl]:kl?[Sl/kl,El/kl]:[NaN,NaN];return Sl=El=kl=Nl=Cl=Pl=zl=Dl=ql=0,t}};function Fl(t,n){Sl+=t,El+=n,++kl}function Ol(){Rl.point=Il}function Il(t,n){Rl.point=Ul,Fl(Al=t,Tl=n)}function Ul(t,n){var e=t-Al,r=n-Tl,i=Ic(e*e+r*r);Nl+=i*(Al+t)/2,Cl+=i*(Tl+n)/2,Pl+=i,Fl(Al=t,Tl=n)}function Bl(){Rl.point=Fl}function Yl(){Rl.point=jl}function Ll(){Hl(wl,Ml)}function jl(t,n){Rl.point=Hl,Fl(wl=Al=t,Ml=Tl=n)}function Hl(t,n){var e=t-Al,r=n-Tl,i=Ic(e*e+r*r);Nl+=i*(Al+t)/2,Cl+=i*(Tl+n)/2,Pl+=i,zl+=(i=Tl*t-Al*n)*(Al+t),Dl+=i*(Tl+n),ql+=3*i,Fl(Al=t,Tl=n)}function Xl(t){this._context=t}Xl.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._context.moveTo(t,n),this._point=1;break;case 1:this._context.lineTo(t,n);break;default:this._context.moveTo(t+this._radius,n),this._context.arc(t,n,this._radius,0,Ac)}},result:jc};var Gl,Vl,$l,Wl,Zl,Kl=new g,Ql={point:jc,lineStart:function(){Ql.point=Jl},lineEnd:function(){Gl&&th(Vl,$l),Ql.point=jc},polygonStart:function(){Gl=!0},polygonEnd:function(){Gl=null},result:function(){var t=+Kl;return Kl=new g,t}};function Jl(t,n){Ql.point=th,Vl=Wl=t,$l=Zl=n}function th(t,n){Wl-=t,Zl-=n,Kl.add(Ic(Wl*Wl+Zl*Zl)),Wl=t,Zl=n}function nh(){this._string=[]}function eh(t){return"m0,"+t+"a"+t+","+t+" 0 1,1 0,"+-2*t+"a"+t+","+t+" 0 1,1 0,"+2*t+"z"}function rh(t){return function(n){var e=new ih;for(var r in t)e[r]=t[r];return e.stream=n,e}}function ih(){}function oh(t,n,e){var r=t.clipExtent&&t.clipExtent();return t.scale(150).translate([0,0]),null!=r&&t.clipExtent(null),Wc(e,t.stream(xl)),n(xl.result()),null!=r&&t.clipExtent(r),t}function ah(t,n,e){return oh(t,(function(e){var r=n[1][0]-n[0][0],i=n[1][1]-n[0][1],o=Math.min(r/(e[1][0]-e[0][0]),i/(e[1][1]-e[0][1])),a=+n[0][0]+(r-o*(e[1][0]+e[0][0]))/2,u=+n[0][1]+(i-o*(e[1][1]+e[0][1]))/2;t.scale(150*o).translate([a,u])}),e)}function uh(t,n,e){return ah(t,[[0,0],n],e)}function ch(t,n,e){return oh(t,(function(e){var r=+n,i=r/(e[1][0]-e[0][0]),o=(r-i*(e[1][0]+e[0][0]))/2,a=-i*e[0][1];t.scale(150*i).translate([o,a])}),e)}function fh(t,n,e){return oh(t,(function(e){var r=+n,i=r/(e[1][1]-e[0][1]),o=-i*e[0][0],a=(r-i*(e[1][1]+e[0][1]))/2;t.scale(150*i).translate([o,a])}),e)}nh.prototype={_radius:4.5,_circle:eh(4.5),pointRadius:function(t){return(t=+t)!==this._radius&&(this._radius=t,this._circle=null),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._string.push("Z"),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._string.push("M",t,",",n),this._point=1;break;case 1:this._string.push("L",t,",",n);break;default:null==this._circle&&(this._circle=eh(this._radius)),this._string.push("M",t,",",n,this._circle)}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}return null}},ih.prototype={constructor:ih,point:function(t,n){this.stream.point(t,n)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};var sh=Cc(30*Sc);function lh(t,n){return+n?function(t,n){function e(r,i,o,a,u,c,f,s,l,h,d,p,g,y){var v=f-r,_=s-i,b=v*v+_*_;if(b>4*n&&g--){var m=a+h,x=u+d,w=c+p,M=Ic(m*m+x*x+w*w),A=Yc(w/=M),T=Ec(Ec(w)-1)n||Ec((v*N+_*C)/b-.5)>.3||a*h+u*d+c*p2?t[2]%360*Sc:0,N()):[y*Tc,v*Tc,_*Tc]},E.angle=function(t){return arguments.length?(b=t%360*Sc,N()):b*Tc},E.reflectX=function(t){return arguments.length?(m=t?-1:1,N()):m<0},E.reflectY=function(t){return arguments.length?(x=t?-1:1,N()):x<0},E.precision=function(t){return arguments.length?(a=lh(u,S=t*t),C()):Ic(S)},E.fitExtent=function(t,n){return ah(E,t,n)},E.fitSize=function(t,n){return uh(E,t,n)},E.fitWidth=function(t,n){return ch(E,t,n)},E.fitHeight=function(t,n){return fh(E,t,n)},function(){return n=t.apply(this,arguments),E.invert=n.invert&&k,N()}}function yh(t){var n=0,e=xc/3,r=gh(t),i=r(n,e);return i.parallels=function(t){return arguments.length?r(n=t[0]*Sc,e=t[1]*Sc):[n*Tc,e*Tc]},i}function vh(t,n){var e=Fc(t),r=(e+Fc(n))/2;if(Ec(r)0?n<-wc+bc&&(n=-wc+bc):n>wc-bc&&(n=wc-bc);var e=i/Rc(Sh(n),r);return[e*Fc(r*t),i-e*Cc(r*t)]}return o.invert=function(t,n){var e=i-n,o=Oc(r)*Ic(t*t+e*e),a=Nc(t,Ec(e))*Oc(e);return e*r<0&&(a-=xc*Oc(t)*Oc(e)),[a/r,2*kc(Rc(i/o,1/r))-wc]},o}function kh(t,n){return[t,n]}function Nh(t,n){var e=Cc(t),r=t===n?Fc(t):(e-Cc(n))/(n-t),i=e/r+t;if(Ec(r)=0;)n+=e[r].value;else n=1;t.value=n}function Xh(t,n){t instanceof Map?(t=[void 0,t],void 0===n&&(n=Vh)):void 0===n&&(n=Gh);for(var e,r,i,o,a,u=new Zh(t),c=[u];e=c.pop();)if((i=n(e.data))&&(a=(i=Array.from(i)).length))for(e.children=i,o=a-1;o>=0;--o)c.push(r=i[o]=new Zh(i[o])),r.parent=e,r.depth=e.depth+1;return u.eachBefore(Wh)}function Gh(t){return t.children}function Vh(t){return Array.isArray(t)?t[1]:null}function $h(t){void 0!==t.data.value&&(t.value=t.data.value),t.data=t.data.data}function Wh(t){var n=0;do{t.height=n}while((t=t.parent)&&t.height<++n)}function Zh(t){this.data=t,this.depth=this.height=0,this.parent=null}function Kh(t){for(var n,e,r=0,i=(t=function(t){for(var n,e,r=t.length;r;)e=Math.random()*r--|0,n=t[r],t[r]=t[e],t[e]=n;return t}(Array.from(t))).length,o=[];r0&&e*e>r*r+i*i}function nd(t,n){for(var e=0;e(a*=a)?(r=(f+a-i)/(2*f),o=Math.sqrt(Math.max(0,a/f-r*r)),e.x=t.x-r*u-o*c,e.y=t.y-r*c+o*u):(r=(f+i-a)/(2*f),o=Math.sqrt(Math.max(0,i/f-r*r)),e.x=n.x+r*u-o*c,e.y=n.y+r*c+o*u)):(e.x=n.x+e.r,e.y=n.y)}function ad(t,n){var e=t.r+n.r-1e-6,r=n.x-t.x,i=n.y-t.y;return e>0&&e*e>r*r+i*i}function ud(t){var n=t._,e=t.next._,r=n.r+e.r,i=(n.x*e.r+e.x*n.r)/r,o=(n.y*e.r+e.y*n.r)/r;return i*i+o*o}function cd(t){this._=t,this.next=null,this.previous=null}function fd(t){if(!(i=(t=function(t){return"object"==typeof t&&"length"in t?t:Array.from(t)}(t)).length))return 0;var n,e,r,i,o,a,u,c,f,s,l;if((n=t[0]).x=0,n.y=0,!(i>1))return n.r;if(e=t[1],n.x=-e.r,e.x=n.r,e.y=0,!(i>2))return n.r+e.r;od(e,n,r=t[2]),n=new cd(n),e=new cd(e),r=new cd(r),n.next=r.previous=e,e.next=n.previous=r,r.next=e.previous=n;t:for(u=3;ubc&&--i>0);return[t/(.8707+(o=r*r)*(o*(o*o*o*(.003971-.001529*o)-.013791)-.131979)),r]},Ih.invert=xh(Yc),Uh.invert=xh((function(t){return 2*kc(t)})),Bh.invert=function(t,n){return[-n,2*kc(zc(t))-wc]},Zh.prototype=Xh.prototype={constructor:Zh,count:function(){return this.eachAfter(Hh)},each:function(t,n){let e=-1;for(const r of this)t.call(n,r,++e,this);return this},eachAfter:function(t,n){for(var e,r,i,o=this,a=[o],u=[],c=-1;o=a.pop();)if(u.push(o),e=o.children)for(r=0,i=e.length;r=0;--r)o.push(e[r]);return this},find:function(t,n){let e=-1;for(const r of this)if(t.call(n,r,++e,this))return r},sum:function(t){return this.eachAfter((function(n){for(var e=+t(n.data)||0,r=n.children,i=r&&r.length;--i>=0;)e+=r[i].value;n.value=e}))},sort:function(t){return this.eachBefore((function(n){n.children&&n.children.sort(t)}))},path:function(t){for(var n=this,e=function(t,n){if(t===n)return t;var e=t.ancestors(),r=n.ancestors(),i=null;t=e.pop(),n=r.pop();for(;t===n;)i=t,t=e.pop(),n=r.pop();return i}(n,t),r=[n];n!==e;)n=n.parent,r.push(n);for(var i=r.length;t!==e;)r.splice(i,0,t),t=t.parent;return r},ancestors:function(){for(var t=this,n=[t];t=t.parent;)n.push(t);return n},descendants:function(){return Array.from(this)},leaves:function(){var t=[];return this.eachBefore((function(n){n.children||t.push(n)})),t},links:function(){var t=this,n=[];return t.each((function(e){e!==t&&n.push({source:e.parent,target:e})})),n},copy:function(){return Xh(this).eachBefore($h)},[Symbol.iterator]:function*(){var t,n,e,r,i=this,o=[i];do{for(t=o.reverse(),o=[];i=t.pop();)if(yield i,n=i.children)for(e=0,r=n.length;eh&&(h=u),y=s*s*g,(d=Math.max(h/y,y/l))>p){s-=u;break}p=d}v.push(a={value:s,dice:c1?n:1)},e}(Pd);var qd=function t(n){function e(t,e,r,i,o){if((a=t._squarify)&&a.ratio===n)for(var a,u,c,f,s,l=-1,h=a.length,d=t.value;++l1?n:1)},e}(Pd);function Rd(t,n,e){return(n[0]-t[0])*(e[1]-t[1])-(n[1]-t[1])*(e[0]-t[0])}function Fd(t,n){return t[0]-n[0]||t[1]-n[1]}function Od(t){const n=t.length,e=[0,1];let r,i=2;for(r=2;r1&&Rd(t[e[i-2]],t[e[i-1]],t[r])<=0;)--i;e[i++]=r}return e.slice(0,i)}var Id=Math.random,Ud=function t(n){function e(t,e){return t=null==t?0:+t,e=null==e?1:+e,1===arguments.length?(e=t,t=0):e-=t,function(){return n()*e+t}}return e.source=t,e}(Id),Bd=function t(n){function e(t,e){return arguments.length<2&&(e=t,t=0),t=Math.floor(t),e=Math.floor(e)-t,function(){return Math.floor(n()*e+t)}}return e.source=t,e}(Id),Yd=function t(n){function e(t,e){var r,i;return t=null==t?0:+t,e=null==e?1:+e,function(){var o;if(null!=r)o=r,r=null;else do{r=2*n()-1,o=2*n()-1,i=r*r+o*o}while(!i||i>1);return t+e*o*Math.sqrt(-2*Math.log(i)/i)}}return e.source=t,e}(Id),Ld=function t(n){var e=Yd.source(n);function r(){var t=e.apply(this,arguments);return function(){return Math.exp(t())}}return r.source=t,r}(Id),jd=function t(n){function e(t){return(t=+t)<=0?()=>0:function(){for(var e=0,r=t;r>1;--r)e+=n();return e+r*n()}}return e.source=t,e}(Id),Hd=function t(n){var e=jd.source(n);function r(t){if(0==(t=+t))return n;var r=e(t);return function(){return r()/t}}return r.source=t,r}(Id),Xd=function t(n){function e(t){return function(){return-Math.log1p(-n())/t}}return e.source=t,e}(Id),Gd=function t(n){function e(t){if((t=+t)<0)throw new RangeError("invalid alpha");return t=1/-t,function(){return Math.pow(1-n(),t)}}return e.source=t,e}(Id),Vd=function t(n){function e(t){if((t=+t)<0||t>1)throw new RangeError("invalid p");return function(){return Math.floor(n()+t)}}return e.source=t,e}(Id),$d=function t(n){function e(t){if((t=+t)<0||t>1)throw new RangeError("invalid p");return 0===t?()=>1/0:1===t?()=>1:(t=Math.log1p(-t),function(){return 1+Math.floor(Math.log1p(-n())/t)})}return e.source=t,e}(Id),Wd=function t(n){var e=Yd.source(n)();function r(t,r){if((t=+t)<0)throw new RangeError("invalid k");if(0===t)return()=>0;if(r=null==r?1:+r,1===t)return()=>-Math.log1p(-n())*r;var i=(t<1?t+1:t)-1/3,o=1/(3*Math.sqrt(i)),a=t<1?()=>Math.pow(n(),1/t):()=>1;return function(){do{do{var t=e(),u=1+o*t}while(u<=0);u*=u*u;var c=1-n()}while(c>=1-.0331*t*t*t*t&&Math.log(c)>=.5*t*t+i*(1-u+Math.log(u)));return i*u*a()*r}}return r.source=t,r}(Id),Zd=function t(n){var e=Wd.source(n);function r(t,n){var r=e(t),i=e(n);return function(){var t=r();return 0===t?0:t/(t+i())}}return r.source=t,r}(Id),Kd=function t(n){var e=$d.source(n),r=Zd.source(n);function i(t,n){return t=+t,(n=+n)>=1?()=>t:n<=0?()=>0:function(){for(var i=0,o=t,a=n;o*a>16&&o*(1-a)>16;){var u=Math.floor((o+1)*a),c=r(u,o-u+1)();c<=a?(i+=u,o-=u,a=(a-c)/(1-c)):(o=u-1,a/=c)}for(var f=a<.5,s=e(f?a:1-a),l=s(),h=0;l<=o;++h)l+=s();return i+(f?h:o-h)}}return i.source=t,i}(Id),Qd=function t(n){function e(t,e,r){var i;return 0==(t=+t)?i=t=>-Math.log(t):(t=1/t,i=n=>Math.pow(n,t)),e=null==e?0:+e,r=null==r?1:+r,function(){return e+r*i(-Math.log1p(-n()))}}return e.source=t,e}(Id),Jd=function t(n){function e(t,e){return t=null==t?0:+t,e=null==e?1:+e,function(){return t+e*Math.tan(Math.PI*n())}}return e.source=t,e}(Id),tp=function t(n){function e(t,e){return t=null==t?0:+t,e=null==e?1:+e,function(){var r=n();return t+e*Math.log(r/(1-r))}}return e.source=t,e}(Id),np=function t(n){var e=Wd.source(n),r=Kd.source(n);function i(t){return function(){for(var i=0,o=t;o>16;){var a=Math.floor(.875*o),u=e(a)();if(u>o)return i+r(a-1,o/u)();i+=a,o-=u}for(var c=-Math.log1p(-n()),f=0;c<=o;++f)c-=Math.log1p(-n());return i+f}}return i.source=t,i}(Id);const ep=1/4294967296;function rp(t,n){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(n).domain(t)}return this}function ip(t,n){switch(arguments.length){case 0:break;case 1:"function"==typeof t?this.interpolator(t):this.range(t);break;default:this.domain(t),"function"==typeof n?this.interpolator(n):this.range(n)}return this}const op=Symbol("implicit");function ap(){var t=new Map,n=[],e=[],r=op;function i(i){var o=i+"",a=t.get(o);if(!a){if(r!==op)return r;t.set(o,a=n.push(i))}return e[(a-1)%e.length]}return i.domain=function(e){if(!arguments.length)return n.slice();n=[],t=new Map;for(const r of e){const e=r+"";t.has(e)||t.set(e,n.push(r))}return i},i.range=function(t){return arguments.length?(e=Array.from(t),i):e.slice()},i.unknown=function(t){return arguments.length?(r=t,i):r},i.copy=function(){return ap(n,e).unknown(r)},rp.apply(i,arguments),i}function up(){var t,n,e=ap().unknown(void 0),r=e.domain,i=e.range,o=0,a=1,u=!1,c=0,f=0,s=.5;function l(){var e=r().length,l=an&&(e=t,t=n,n=e),function(e){return Math.max(t,Math.min(n,e))}}(a[0],a[t-1])),r=t>2?pp:dp,i=o=null,l}function l(n){return null==n||isNaN(n=+n)?e:(i||(i=r(a.map(t),u,c)))(t(f(n)))}return l.invert=function(e){return f(n((o||(o=r(u,a.map(t),_r)))(e)))},l.domain=function(t){return arguments.length?(a=Array.from(t,fp),s()):a.slice()},l.range=function(t){return arguments.length?(u=Array.from(t),s()):u.slice()},l.rangeRound=function(t){return u=Array.from(t),c=Ar,s()},l.clamp=function(t){return arguments.length?(f=!!t||lp,s()):f!==lp},l.interpolate=function(t){return arguments.length?(c=t,s()):c},l.unknown=function(t){return arguments.length?(e=t,l):e},function(e,r){return t=e,n=r,s()}}function vp(){return yp()(lp,lp)}function _p(n,e,r,i){var o,a=F(n,e,r);switch((i=ac(null==i?",f":i)).type){case"s":var u=Math.max(Math.abs(n),Math.abs(e));return null!=i.precision||isNaN(o=vc(a,u))||(i.precision=o),t.formatPrefix(i,u);case"":case"e":case"g":case"p":case"r":null!=i.precision||isNaN(o=_c(a,Math.max(Math.abs(n),Math.abs(e))))||(i.precision=o-("e"===i.type));break;case"f":case"%":null!=i.precision||isNaN(o=yc(a))||(i.precision=o-2*("%"===i.type))}return t.format(i)}function bp(t){var n=t.domain;return t.ticks=function(t){var e=n();return q(e[0],e[e.length-1],null==t?10:t)},t.tickFormat=function(t,e){var r=n();return _p(r[0],r[r.length-1],null==t?10:t,e)},t.nice=function(e){null==e&&(e=10);var r,i,o=n(),a=0,u=o.length-1,c=o[a],f=o[u],s=10;for(f0;){if((i=R(c,f,e))===r)return o[a]=c,o[u]=f,n(o);if(i>0)c=Math.floor(c/i)*i,f=Math.ceil(f/i)*i;else{if(!(i<0))break;c=Math.ceil(c*i)/i,f=Math.floor(f*i)/i}r=i}return t},t}function mp(t,n){var e,r=0,i=(t=t.slice()).length-1,o=t[r],a=t[i];return a0){for(;h<=d;++h)for(s=1,f=r(h);sc)break;g.push(l)}}else for(;h<=d;++h)for(s=a-1,f=r(h);s>=1;--s)if(!((l=f*s)c)break;g.push(l)}2*g.length0))return u;do{u.push(a=new Date(+e)),n(e,o),t(e)}while(a=n)for(;t(n),!e(n);)n.setTime(n-1)}),(function(t,r){if(t>=t)if(r<0)for(;++r<=0;)for(;n(t,-1),!e(t););else for(;--r>=0;)for(;n(t,1),!e(t););}))},e&&(i.count=function(n,r){return Ip.setTime(+n),Up.setTime(+r),t(Ip),t(Up),Math.floor(e(Ip,Up))},i.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?function(n){return r(n)%t==0}:function(n){return i.count(0,n)%t==0}):i:null}),i}var Yp=Bp((function(){}),(function(t,n){t.setTime(+t+n)}),(function(t,n){return n-t}));Yp.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?Bp((function(n){n.setTime(Math.floor(n/t)*t)}),(function(n,e){n.setTime(+n+e*t)}),(function(n,e){return(e-n)/t})):Yp:null};var Lp=Yp.range;const jp=1e3,Hp=6e4,Xp=36e5,Gp=864e5,Vp=6048e5,$p=2592e6,Wp=31536e6;var Zp=Bp((function(t){t.setTime(t-t.getMilliseconds())}),(function(t,n){t.setTime(+t+n*jp)}),(function(t,n){return(n-t)/jp}),(function(t){return t.getUTCSeconds()})),Kp=Zp.range,Qp=Bp((function(t){t.setTime(t-t.getMilliseconds()-t.getSeconds()*jp)}),(function(t,n){t.setTime(+t+n*Hp)}),(function(t,n){return(n-t)/Hp}),(function(t){return t.getMinutes()})),Jp=Qp.range,tg=Bp((function(t){t.setTime(t-t.getMilliseconds()-t.getSeconds()*jp-t.getMinutes()*Hp)}),(function(t,n){t.setTime(+t+n*Xp)}),(function(t,n){return(n-t)/Xp}),(function(t){return t.getHours()})),ng=tg.range,eg=Bp((t=>t.setHours(0,0,0,0)),((t,n)=>t.setDate(t.getDate()+n)),((t,n)=>(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*Hp)/Gp),(t=>t.getDate()-1)),rg=eg.range;function ig(t){return Bp((function(n){n.setDate(n.getDate()-(n.getDay()+7-t)%7),n.setHours(0,0,0,0)}),(function(t,n){t.setDate(t.getDate()+7*n)}),(function(t,n){return(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*Hp)/Vp}))}var og=ig(0),ag=ig(1),ug=ig(2),cg=ig(3),fg=ig(4),sg=ig(5),lg=ig(6),hg=og.range,dg=ag.range,pg=ug.range,gg=cg.range,yg=fg.range,vg=sg.range,_g=lg.range,bg=Bp((function(t){t.setDate(1),t.setHours(0,0,0,0)}),(function(t,n){t.setMonth(t.getMonth()+n)}),(function(t,n){return n.getMonth()-t.getMonth()+12*(n.getFullYear()-t.getFullYear())}),(function(t){return t.getMonth()})),mg=bg.range,xg=Bp((function(t){t.setMonth(0,1),t.setHours(0,0,0,0)}),(function(t,n){t.setFullYear(t.getFullYear()+n)}),(function(t,n){return n.getFullYear()-t.getFullYear()}),(function(t){return t.getFullYear()}));xg.every=function(t){return isFinite(t=Math.floor(t))&&t>0?Bp((function(n){n.setFullYear(Math.floor(n.getFullYear()/t)*t),n.setMonth(0,1),n.setHours(0,0,0,0)}),(function(n,e){n.setFullYear(n.getFullYear()+e*t)})):null};var wg=xg.range,Mg=Bp((function(t){t.setUTCSeconds(0,0)}),(function(t,n){t.setTime(+t+n*Hp)}),(function(t,n){return(n-t)/Hp}),(function(t){return t.getUTCMinutes()})),Ag=Mg.range,Tg=Bp((function(t){t.setUTCMinutes(0,0,0)}),(function(t,n){t.setTime(+t+n*Xp)}),(function(t,n){return(n-t)/Xp}),(function(t){return t.getUTCHours()})),Sg=Tg.range,Eg=Bp((function(t){t.setUTCHours(0,0,0,0)}),(function(t,n){t.setUTCDate(t.getUTCDate()+n)}),(function(t,n){return(n-t)/Gp}),(function(t){return t.getUTCDate()-1})),kg=Eg.range;function Ng(t){return Bp((function(n){n.setUTCDate(n.getUTCDate()-(n.getUTCDay()+7-t)%7),n.setUTCHours(0,0,0,0)}),(function(t,n){t.setUTCDate(t.getUTCDate()+7*n)}),(function(t,n){return(n-t)/Vp}))}var Cg=Ng(0),Pg=Ng(1),zg=Ng(2),Dg=Ng(3),qg=Ng(4),Rg=Ng(5),Fg=Ng(6),Og=Cg.range,Ig=Pg.range,Ug=zg.range,Bg=Dg.range,Yg=qg.range,Lg=Rg.range,jg=Fg.range,Hg=Bp((function(t){t.setUTCDate(1),t.setUTCHours(0,0,0,0)}),(function(t,n){t.setUTCMonth(t.getUTCMonth()+n)}),(function(t,n){return n.getUTCMonth()-t.getUTCMonth()+12*(n.getUTCFullYear()-t.getUTCFullYear())}),(function(t){return t.getUTCMonth()})),Xg=Hg.range,Gg=Bp((function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),(function(t,n){t.setUTCFullYear(t.getUTCFullYear()+n)}),(function(t,n){return n.getUTCFullYear()-t.getUTCFullYear()}),(function(t){return t.getUTCFullYear()}));Gg.every=function(t){return isFinite(t=Math.floor(t))&&t>0?Bp((function(n){n.setUTCFullYear(Math.floor(n.getUTCFullYear()/t)*t),n.setUTCMonth(0,1),n.setUTCHours(0,0,0,0)}),(function(n,e){n.setUTCFullYear(n.getUTCFullYear()+e*t)})):null};var Vg=Gg.range;function $g(t,n,r,i,o,a){const u=[[Zp,1,jp],[Zp,5,5e3],[Zp,15,15e3],[Zp,30,3e4],[a,1,Hp],[a,5,3e5],[a,15,9e5],[a,30,18e5],[o,1,Xp],[o,3,108e5],[o,6,216e5],[o,12,432e5],[i,1,Gp],[i,2,1728e5],[r,1,Vp],[n,1,$p],[n,3,7776e6],[t,1,Wp]];function c(n,r,i){const o=Math.abs(r-n)/i,a=e((([,,t])=>t)).right(u,o);if(a===u.length)return t.every(F(n/Wp,r/Wp,i));if(0===a)return Yp.every(Math.max(F(n,r,i),1));const[c,f]=u[o/u[a-1][2]=12)]},q:function(t){return 1+~~(t.getMonth()/3)},Q:bv,s:mv,S:By,u:Yy,U:Ly,V:Hy,w:Xy,W:Gy,x:null,X:null,y:Vy,Y:Wy,Z:Ky,"%":_v},m={a:function(t){return a[t.getUTCDay()]},A:function(t){return o[t.getUTCDay()]},b:function(t){return c[t.getUTCMonth()]},B:function(t){return u[t.getUTCMonth()]},c:null,d:Qy,e:Qy,f:rv,g:pv,G:yv,H:Jy,I:tv,j:nv,L:ev,m:iv,M:ov,p:function(t){return i[+(t.getUTCHours()>=12)]},q:function(t){return 1+~~(t.getUTCMonth()/3)},Q:bv,s:mv,S:av,u:uv,U:cv,V:sv,w:lv,W:hv,x:null,X:null,y:dv,Y:gv,Z:vv,"%":_v},x={a:function(t,n,e){var r=d.exec(n.slice(e));return r?(t.w=p.get(r[0].toLowerCase()),e+r[0].length):-1},A:function(t,n,e){var r=l.exec(n.slice(e));return r?(t.w=h.get(r[0].toLowerCase()),e+r[0].length):-1},b:function(t,n,e){var r=v.exec(n.slice(e));return r?(t.m=_.get(r[0].toLowerCase()),e+r[0].length):-1},B:function(t,n,e){var r=g.exec(n.slice(e));return r?(t.m=y.get(r[0].toLowerCase()),e+r[0].length):-1},c:function(t,e,r){return A(t,n,e,r)},d:wy,e:wy,f:ky,g:_y,G:vy,H:Ay,I:Ay,j:My,L:Ey,m:xy,M:Ty,p:function(t,n,e){var r=f.exec(n.slice(e));return r?(t.p=s.get(r[0].toLowerCase()),e+r[0].length):-1},q:my,Q:Cy,s:Py,S:Sy,u:dy,U:py,V:gy,w:hy,W:yy,x:function(t,n,r){return A(t,e,n,r)},X:function(t,n,e){return A(t,r,n,e)},y:_y,Y:vy,Z:by,"%":Ny};function w(t,n){return function(e){var r,i,o,a=[],u=-1,c=0,f=t.length;for(e instanceof Date||(e=new Date(+e));++u53)return null;"w"in o||(o.w=1),"Z"in o?(i=(r=ty(ny(o.y,0,1))).getUTCDay(),r=i>4||0===i?Pg.ceil(r):Pg(r),r=Eg.offset(r,7*(o.V-1)),o.y=r.getUTCFullYear(),o.m=r.getUTCMonth(),o.d=r.getUTCDate()+(o.w+6)%7):(i=(r=Jg(ny(o.y,0,1))).getDay(),r=i>4||0===i?ag.ceil(r):ag(r),r=eg.offset(r,7*(o.V-1)),o.y=r.getFullYear(),o.m=r.getMonth(),o.d=r.getDate()+(o.w+6)%7)}else("W"in o||"U"in o)&&("w"in o||(o.w="u"in o?o.u%7:"W"in o?1:0),i="Z"in o?ty(ny(o.y,0,1)).getUTCDay():Jg(ny(o.y,0,1)).getDay(),o.m=0,o.d="W"in o?(o.w+6)%7+7*o.W-(i+5)%7:o.w+7*o.U-(i+6)%7);return"Z"in o?(o.H+=o.Z/100|0,o.M+=o.Z%100,ty(o)):Jg(o)}}function A(t,n,e,r){for(var i,o,a=0,u=n.length,c=e.length;a=c)return-1;if(37===(i=n.charCodeAt(a++))){if(i=n.charAt(a++),!(o=x[i in iy?n.charAt(a++):i])||(r=o(t,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}return b.x=w(e,b),b.X=w(r,b),b.c=w(n,b),m.x=w(e,m),m.X=w(r,m),m.c=w(n,m),{format:function(t){var n=w(t+="",b);return n.toString=function(){return t},n},parse:function(t){var n=M(t+="",!1);return n.toString=function(){return t},n},utcFormat:function(t){var n=w(t+="",m);return n.toString=function(){return t},n},utcParse:function(t){var n=M(t+="",!0);return n.toString=function(){return t},n}}}var ry,iy={"-":"",_:" ",0:"0"},oy=/^\s*\d+/,ay=/^%/,uy=/[\\^$*+?|[\]().{}]/g;function cy(t,n,e){var r=t<0?"-":"",i=(r?-t:t)+"",o=i.length;return r+(o[t.toLowerCase(),n])))}function hy(t,n,e){var r=oy.exec(n.slice(e,e+1));return r?(t.w=+r[0],e+r[0].length):-1}function dy(t,n,e){var r=oy.exec(n.slice(e,e+1));return r?(t.u=+r[0],e+r[0].length):-1}function py(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.U=+r[0],e+r[0].length):-1}function gy(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.V=+r[0],e+r[0].length):-1}function yy(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.W=+r[0],e+r[0].length):-1}function vy(t,n,e){var r=oy.exec(n.slice(e,e+4));return r?(t.y=+r[0],e+r[0].length):-1}function _y(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.y=+r[0]+(+r[0]>68?1900:2e3),e+r[0].length):-1}function by(t,n,e){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(n.slice(e,e+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),e+r[0].length):-1}function my(t,n,e){var r=oy.exec(n.slice(e,e+1));return r?(t.q=3*r[0]-3,e+r[0].length):-1}function xy(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.m=r[0]-1,e+r[0].length):-1}function wy(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.d=+r[0],e+r[0].length):-1}function My(t,n,e){var r=oy.exec(n.slice(e,e+3));return r?(t.m=0,t.d=+r[0],e+r[0].length):-1}function Ay(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.H=+r[0],e+r[0].length):-1}function Ty(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.M=+r[0],e+r[0].length):-1}function Sy(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.S=+r[0],e+r[0].length):-1}function Ey(t,n,e){var r=oy.exec(n.slice(e,e+3));return r?(t.L=+r[0],e+r[0].length):-1}function ky(t,n,e){var r=oy.exec(n.slice(e,e+6));return r?(t.L=Math.floor(r[0]/1e3),e+r[0].length):-1}function Ny(t,n,e){var r=ay.exec(n.slice(e,e+1));return r?e+r[0].length:-1}function Cy(t,n,e){var r=oy.exec(n.slice(e));return r?(t.Q=+r[0],e+r[0].length):-1}function Py(t,n,e){var r=oy.exec(n.slice(e));return r?(t.s=+r[0],e+r[0].length):-1}function zy(t,n){return cy(t.getDate(),n,2)}function Dy(t,n){return cy(t.getHours(),n,2)}function qy(t,n){return cy(t.getHours()%12||12,n,2)}function Ry(t,n){return cy(1+eg.count(xg(t),t),n,3)}function Fy(t,n){return cy(t.getMilliseconds(),n,3)}function Oy(t,n){return Fy(t,n)+"000"}function Iy(t,n){return cy(t.getMonth()+1,n,2)}function Uy(t,n){return cy(t.getMinutes(),n,2)}function By(t,n){return cy(t.getSeconds(),n,2)}function Yy(t){var n=t.getDay();return 0===n?7:n}function Ly(t,n){return cy(og.count(xg(t)-1,t),n,2)}function jy(t){var n=t.getDay();return n>=4||0===n?fg(t):fg.ceil(t)}function Hy(t,n){return t=jy(t),cy(fg.count(xg(t),t)+(4===xg(t).getDay()),n,2)}function Xy(t){return t.getDay()}function Gy(t,n){return cy(ag.count(xg(t)-1,t),n,2)}function Vy(t,n){return cy(t.getFullYear()%100,n,2)}function $y(t,n){return cy((t=jy(t)).getFullYear()%100,n,2)}function Wy(t,n){return cy(t.getFullYear()%1e4,n,4)}function Zy(t,n){var e=t.getDay();return cy((t=e>=4||0===e?fg(t):fg.ceil(t)).getFullYear()%1e4,n,4)}function Ky(t){var n=t.getTimezoneOffset();return(n>0?"-":(n*=-1,"+"))+cy(n/60|0,"0",2)+cy(n%60,"0",2)}function Qy(t,n){return cy(t.getUTCDate(),n,2)}function Jy(t,n){return cy(t.getUTCHours(),n,2)}function tv(t,n){return cy(t.getUTCHours()%12||12,n,2)}function nv(t,n){return cy(1+Eg.count(Gg(t),t),n,3)}function ev(t,n){return cy(t.getUTCMilliseconds(),n,3)}function rv(t,n){return ev(t,n)+"000"}function iv(t,n){return cy(t.getUTCMonth()+1,n,2)}function ov(t,n){return cy(t.getUTCMinutes(),n,2)}function av(t,n){return cy(t.getUTCSeconds(),n,2)}function uv(t){var n=t.getUTCDay();return 0===n?7:n}function cv(t,n){return cy(Cg.count(Gg(t)-1,t),n,2)}function fv(t){var n=t.getUTCDay();return n>=4||0===n?qg(t):qg.ceil(t)}function sv(t,n){return t=fv(t),cy(qg.count(Gg(t),t)+(4===Gg(t).getUTCDay()),n,2)}function lv(t){return t.getUTCDay()}function hv(t,n){return cy(Pg.count(Gg(t)-1,t),n,2)}function dv(t,n){return cy(t.getUTCFullYear()%100,n,2)}function pv(t,n){return cy((t=fv(t)).getUTCFullYear()%100,n,2)}function gv(t,n){return cy(t.getUTCFullYear()%1e4,n,4)}function yv(t,n){var e=t.getUTCDay();return cy((t=e>=4||0===e?qg(t):qg.ceil(t)).getUTCFullYear()%1e4,n,4)}function vv(){return"+0000"}function _v(){return"%"}function bv(t){return+t}function mv(t){return Math.floor(+t/1e3)}function xv(n){return ry=ey(n),t.timeFormat=ry.format,t.timeParse=ry.parse,t.utcFormat=ry.utcFormat,t.utcParse=ry.utcParse,ry}t.timeFormat=void 0,t.timeParse=void 0,t.utcFormat=void 0,t.utcParse=void 0,xv({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var wv="%Y-%m-%dT%H:%M:%S.%LZ";var Mv=Date.prototype.toISOString?function(t){return t.toISOString()}:t.utcFormat(wv);var Av=+new Date("2000-01-01T00:00:00.000Z")?function(t){var n=new Date(t);return isNaN(n)?null:n}:t.utcParse(wv);function Tv(t){return new Date(t)}function Sv(t){return t instanceof Date?+t:+new Date(+t)}function Ev(t,n,e,r,i,o,a,u,c,f){var s=vp(),l=s.invert,h=s.domain,d=f(".%L"),p=f(":%S"),g=f("%I:%M"),y=f("%I %p"),v=f("%a %d"),_=f("%b %d"),b=f("%B"),m=f("%Y");function x(t){return(c(t)hr(t[t.length-1]),Xv=new Array(3).concat("d8b365f5f5f55ab4ac","a6611adfc27d80cdc1018571","a6611adfc27df5f5f580cdc1018571","8c510ad8b365f6e8c3c7eae55ab4ac01665e","8c510ad8b365f6e8c3f5f5f5c7eae55ab4ac01665e","8c510abf812ddfc27df6e8c3c7eae580cdc135978f01665e","8c510abf812ddfc27df6e8c3f5f5f5c7eae580cdc135978f01665e","5430058c510abf812ddfc27df6e8c3c7eae580cdc135978f01665e003c30","5430058c510abf812ddfc27df6e8c3f5f5f5c7eae580cdc135978f01665e003c30").map(Dv),Gv=Hv(Xv),Vv=new Array(3).concat("af8dc3f7f7f77fbf7b","7b3294c2a5cfa6dba0008837","7b3294c2a5cff7f7f7a6dba0008837","762a83af8dc3e7d4e8d9f0d37fbf7b1b7837","762a83af8dc3e7d4e8f7f7f7d9f0d37fbf7b1b7837","762a839970abc2a5cfe7d4e8d9f0d3a6dba05aae611b7837","762a839970abc2a5cfe7d4e8f7f7f7d9f0d3a6dba05aae611b7837","40004b762a839970abc2a5cfe7d4e8d9f0d3a6dba05aae611b783700441b","40004b762a839970abc2a5cfe7d4e8f7f7f7d9f0d3a6dba05aae611b783700441b").map(Dv),$v=Hv(Vv),Wv=new Array(3).concat("e9a3c9f7f7f7a1d76a","d01c8bf1b6dab8e1864dac26","d01c8bf1b6daf7f7f7b8e1864dac26","c51b7de9a3c9fde0efe6f5d0a1d76a4d9221","c51b7de9a3c9fde0eff7f7f7e6f5d0a1d76a4d9221","c51b7dde77aef1b6dafde0efe6f5d0b8e1867fbc414d9221","c51b7dde77aef1b6dafde0eff7f7f7e6f5d0b8e1867fbc414d9221","8e0152c51b7dde77aef1b6dafde0efe6f5d0b8e1867fbc414d9221276419","8e0152c51b7dde77aef1b6dafde0eff7f7f7e6f5d0b8e1867fbc414d9221276419").map(Dv),Zv=Hv(Wv),Kv=new Array(3).concat("998ec3f7f7f7f1a340","5e3c99b2abd2fdb863e66101","5e3c99b2abd2f7f7f7fdb863e66101","542788998ec3d8daebfee0b6f1a340b35806","542788998ec3d8daebf7f7f7fee0b6f1a340b35806","5427888073acb2abd2d8daebfee0b6fdb863e08214b35806","5427888073acb2abd2d8daebf7f7f7fee0b6fdb863e08214b35806","2d004b5427888073acb2abd2d8daebfee0b6fdb863e08214b358067f3b08","2d004b5427888073acb2abd2d8daebf7f7f7fee0b6fdb863e08214b358067f3b08").map(Dv),Qv=Hv(Kv),Jv=new Array(3).concat("ef8a62f7f7f767a9cf","ca0020f4a58292c5de0571b0","ca0020f4a582f7f7f792c5de0571b0","b2182bef8a62fddbc7d1e5f067a9cf2166ac","b2182bef8a62fddbc7f7f7f7d1e5f067a9cf2166ac","b2182bd6604df4a582fddbc7d1e5f092c5de4393c32166ac","b2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac","67001fb2182bd6604df4a582fddbc7d1e5f092c5de4393c32166ac053061","67001fb2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac053061").map(Dv),t_=Hv(Jv),n_=new Array(3).concat("ef8a62ffffff999999","ca0020f4a582bababa404040","ca0020f4a582ffffffbababa404040","b2182bef8a62fddbc7e0e0e09999994d4d4d","b2182bef8a62fddbc7ffffffe0e0e09999994d4d4d","b2182bd6604df4a582fddbc7e0e0e0bababa8787874d4d4d","b2182bd6604df4a582fddbc7ffffffe0e0e0bababa8787874d4d4d","67001fb2182bd6604df4a582fddbc7e0e0e0bababa8787874d4d4d1a1a1a","67001fb2182bd6604df4a582fddbc7ffffffe0e0e0bababa8787874d4d4d1a1a1a").map(Dv),e_=Hv(n_),r_=new Array(3).concat("fc8d59ffffbf91bfdb","d7191cfdae61abd9e92c7bb6","d7191cfdae61ffffbfabd9e92c7bb6","d73027fc8d59fee090e0f3f891bfdb4575b4","d73027fc8d59fee090ffffbfe0f3f891bfdb4575b4","d73027f46d43fdae61fee090e0f3f8abd9e974add14575b4","d73027f46d43fdae61fee090ffffbfe0f3f8abd9e974add14575b4","a50026d73027f46d43fdae61fee090e0f3f8abd9e974add14575b4313695","a50026d73027f46d43fdae61fee090ffffbfe0f3f8abd9e974add14575b4313695").map(Dv),i_=Hv(r_),o_=new Array(3).concat("fc8d59ffffbf91cf60","d7191cfdae61a6d96a1a9641","d7191cfdae61ffffbfa6d96a1a9641","d73027fc8d59fee08bd9ef8b91cf601a9850","d73027fc8d59fee08bffffbfd9ef8b91cf601a9850","d73027f46d43fdae61fee08bd9ef8ba6d96a66bd631a9850","d73027f46d43fdae61fee08bffffbfd9ef8ba6d96a66bd631a9850","a50026d73027f46d43fdae61fee08bd9ef8ba6d96a66bd631a9850006837","a50026d73027f46d43fdae61fee08bffffbfd9ef8ba6d96a66bd631a9850006837").map(Dv),a_=Hv(o_),u_=new Array(3).concat("fc8d59ffffbf99d594","d7191cfdae61abdda42b83ba","d7191cfdae61ffffbfabdda42b83ba","d53e4ffc8d59fee08be6f59899d5943288bd","d53e4ffc8d59fee08bffffbfe6f59899d5943288bd","d53e4ff46d43fdae61fee08be6f598abdda466c2a53288bd","d53e4ff46d43fdae61fee08bffffbfe6f598abdda466c2a53288bd","9e0142d53e4ff46d43fdae61fee08be6f598abdda466c2a53288bd5e4fa2","9e0142d53e4ff46d43fdae61fee08bffffbfe6f598abdda466c2a53288bd5e4fa2").map(Dv),c_=Hv(u_),f_=new Array(3).concat("e5f5f999d8c92ca25f","edf8fbb2e2e266c2a4238b45","edf8fbb2e2e266c2a42ca25f006d2c","edf8fbccece699d8c966c2a42ca25f006d2c","edf8fbccece699d8c966c2a441ae76238b45005824","f7fcfde5f5f9ccece699d8c966c2a441ae76238b45005824","f7fcfde5f5f9ccece699d8c966c2a441ae76238b45006d2c00441b").map(Dv),s_=Hv(f_),l_=new Array(3).concat("e0ecf49ebcda8856a7","edf8fbb3cde38c96c688419d","edf8fbb3cde38c96c68856a7810f7c","edf8fbbfd3e69ebcda8c96c68856a7810f7c","edf8fbbfd3e69ebcda8c96c68c6bb188419d6e016b","f7fcfde0ecf4bfd3e69ebcda8c96c68c6bb188419d6e016b","f7fcfde0ecf4bfd3e69ebcda8c96c68c6bb188419d810f7c4d004b").map(Dv),h_=Hv(l_),d_=new Array(3).concat("e0f3dba8ddb543a2ca","f0f9e8bae4bc7bccc42b8cbe","f0f9e8bae4bc7bccc443a2ca0868ac","f0f9e8ccebc5a8ddb57bccc443a2ca0868ac","f0f9e8ccebc5a8ddb57bccc44eb3d32b8cbe08589e","f7fcf0e0f3dbccebc5a8ddb57bccc44eb3d32b8cbe08589e","f7fcf0e0f3dbccebc5a8ddb57bccc44eb3d32b8cbe0868ac084081").map(Dv),p_=Hv(d_),g_=new Array(3).concat("fee8c8fdbb84e34a33","fef0d9fdcc8afc8d59d7301f","fef0d9fdcc8afc8d59e34a33b30000","fef0d9fdd49efdbb84fc8d59e34a33b30000","fef0d9fdd49efdbb84fc8d59ef6548d7301f990000","fff7ecfee8c8fdd49efdbb84fc8d59ef6548d7301f990000","fff7ecfee8c8fdd49efdbb84fc8d59ef6548d7301fb300007f0000").map(Dv),y_=Hv(g_),v_=new Array(3).concat("ece2f0a6bddb1c9099","f6eff7bdc9e167a9cf02818a","f6eff7bdc9e167a9cf1c9099016c59","f6eff7d0d1e6a6bddb67a9cf1c9099016c59","f6eff7d0d1e6a6bddb67a9cf3690c002818a016450","fff7fbece2f0d0d1e6a6bddb67a9cf3690c002818a016450","fff7fbece2f0d0d1e6a6bddb67a9cf3690c002818a016c59014636").map(Dv),__=Hv(v_),b_=new Array(3).concat("ece7f2a6bddb2b8cbe","f1eef6bdc9e174a9cf0570b0","f1eef6bdc9e174a9cf2b8cbe045a8d","f1eef6d0d1e6a6bddb74a9cf2b8cbe045a8d","f1eef6d0d1e6a6bddb74a9cf3690c00570b0034e7b","fff7fbece7f2d0d1e6a6bddb74a9cf3690c00570b0034e7b","fff7fbece7f2d0d1e6a6bddb74a9cf3690c00570b0045a8d023858").map(Dv),m_=Hv(b_),x_=new Array(3).concat("e7e1efc994c7dd1c77","f1eef6d7b5d8df65b0ce1256","f1eef6d7b5d8df65b0dd1c77980043","f1eef6d4b9dac994c7df65b0dd1c77980043","f1eef6d4b9dac994c7df65b0e7298ace125691003f","f7f4f9e7e1efd4b9dac994c7df65b0e7298ace125691003f","f7f4f9e7e1efd4b9dac994c7df65b0e7298ace125698004367001f").map(Dv),w_=Hv(x_),M_=new Array(3).concat("fde0ddfa9fb5c51b8a","feebe2fbb4b9f768a1ae017e","feebe2fbb4b9f768a1c51b8a7a0177","feebe2fcc5c0fa9fb5f768a1c51b8a7a0177","feebe2fcc5c0fa9fb5f768a1dd3497ae017e7a0177","fff7f3fde0ddfcc5c0fa9fb5f768a1dd3497ae017e7a0177","fff7f3fde0ddfcc5c0fa9fb5f768a1dd3497ae017e7a017749006a").map(Dv),A_=Hv(M_),T_=new Array(3).concat("edf8b17fcdbb2c7fb8","ffffcca1dab441b6c4225ea8","ffffcca1dab441b6c42c7fb8253494","ffffccc7e9b47fcdbb41b6c42c7fb8253494","ffffccc7e9b47fcdbb41b6c41d91c0225ea80c2c84","ffffd9edf8b1c7e9b47fcdbb41b6c41d91c0225ea80c2c84","ffffd9edf8b1c7e9b47fcdbb41b6c41d91c0225ea8253494081d58").map(Dv),S_=Hv(T_),E_=new Array(3).concat("f7fcb9addd8e31a354","ffffccc2e69978c679238443","ffffccc2e69978c67931a354006837","ffffccd9f0a3addd8e78c67931a354006837","ffffccd9f0a3addd8e78c67941ab5d238443005a32","ffffe5f7fcb9d9f0a3addd8e78c67941ab5d238443005a32","ffffe5f7fcb9d9f0a3addd8e78c67941ab5d238443006837004529").map(Dv),k_=Hv(E_),N_=new Array(3).concat("fff7bcfec44fd95f0e","ffffd4fed98efe9929cc4c02","ffffd4fed98efe9929d95f0e993404","ffffd4fee391fec44ffe9929d95f0e993404","ffffd4fee391fec44ffe9929ec7014cc4c028c2d04","ffffe5fff7bcfee391fec44ffe9929ec7014cc4c028c2d04","ffffe5fff7bcfee391fec44ffe9929ec7014cc4c02993404662506").map(Dv),C_=Hv(N_),P_=new Array(3).concat("ffeda0feb24cf03b20","ffffb2fecc5cfd8d3ce31a1c","ffffb2fecc5cfd8d3cf03b20bd0026","ffffb2fed976feb24cfd8d3cf03b20bd0026","ffffb2fed976feb24cfd8d3cfc4e2ae31a1cb10026","ffffccffeda0fed976feb24cfd8d3cfc4e2ae31a1cb10026","ffffccffeda0fed976feb24cfd8d3cfc4e2ae31a1cbd0026800026").map(Dv),z_=Hv(P_),D_=new Array(3).concat("deebf79ecae13182bd","eff3ffbdd7e76baed62171b5","eff3ffbdd7e76baed63182bd08519c","eff3ffc6dbef9ecae16baed63182bd08519c","eff3ffc6dbef9ecae16baed64292c62171b5084594","f7fbffdeebf7c6dbef9ecae16baed64292c62171b5084594","f7fbffdeebf7c6dbef9ecae16baed64292c62171b508519c08306b").map(Dv),q_=Hv(D_),R_=new Array(3).concat("e5f5e0a1d99b31a354","edf8e9bae4b374c476238b45","edf8e9bae4b374c47631a354006d2c","edf8e9c7e9c0a1d99b74c47631a354006d2c","edf8e9c7e9c0a1d99b74c47641ab5d238b45005a32","f7fcf5e5f5e0c7e9c0a1d99b74c47641ab5d238b45005a32","f7fcf5e5f5e0c7e9c0a1d99b74c47641ab5d238b45006d2c00441b").map(Dv),F_=Hv(R_),O_=new Array(3).concat("f0f0f0bdbdbd636363","f7f7f7cccccc969696525252","f7f7f7cccccc969696636363252525","f7f7f7d9d9d9bdbdbd969696636363252525","f7f7f7d9d9d9bdbdbd969696737373525252252525","fffffff0f0f0d9d9d9bdbdbd969696737373525252252525","fffffff0f0f0d9d9d9bdbdbd969696737373525252252525000000").map(Dv),I_=Hv(O_),U_=new Array(3).concat("efedf5bcbddc756bb1","f2f0f7cbc9e29e9ac86a51a3","f2f0f7cbc9e29e9ac8756bb154278f","f2f0f7dadaebbcbddc9e9ac8756bb154278f","f2f0f7dadaebbcbddc9e9ac8807dba6a51a34a1486","fcfbfdefedf5dadaebbcbddc9e9ac8807dba6a51a34a1486","fcfbfdefedf5dadaebbcbddc9e9ac8807dba6a51a354278f3f007d").map(Dv),B_=Hv(U_),Y_=new Array(3).concat("fee0d2fc9272de2d26","fee5d9fcae91fb6a4acb181d","fee5d9fcae91fb6a4ade2d26a50f15","fee5d9fcbba1fc9272fb6a4ade2d26a50f15","fee5d9fcbba1fc9272fb6a4aef3b2ccb181d99000d","fff5f0fee0d2fcbba1fc9272fb6a4aef3b2ccb181d99000d","fff5f0fee0d2fcbba1fc9272fb6a4aef3b2ccb181da50f1567000d").map(Dv),L_=Hv(Y_),j_=new Array(3).concat("fee6cefdae6be6550d","feeddefdbe85fd8d3cd94701","feeddefdbe85fd8d3ce6550da63603","feeddefdd0a2fdae6bfd8d3ce6550da63603","feeddefdd0a2fdae6bfd8d3cf16913d948018c2d04","fff5ebfee6cefdd0a2fdae6bfd8d3cf16913d948018c2d04","fff5ebfee6cefdd0a2fdae6bfd8d3cf16913d94801a636037f2704").map(Dv),H_=Hv(j_);var X_=Lr(tr(300,.5,0),tr(-240,.5,1)),G_=Lr(tr(-100,.75,.35),tr(80,1.5,.8)),V_=Lr(tr(260,.75,.35),tr(80,1.5,.8)),$_=tr();var W_=ve(),Z_=Math.PI/3,K_=2*Math.PI/3;function Q_(t){var n=t.length;return function(e){return t[Math.max(0,Math.min(n-1,Math.floor(e*n)))]}}var J_=Q_(Dv("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725")),tb=Q_(Dv("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")),nb=Q_(Dv("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")),eb=Q_(Dv("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921"));function rb(t){return function(){return t}}var ib=Math.abs,ob=Math.atan2,ab=Math.cos,ub=Math.max,cb=Math.min,fb=Math.sin,sb=Math.sqrt,lb=1e-12,hb=Math.PI,db=hb/2,pb=2*hb;function gb(t){return t>1?0:t<-1?hb:Math.acos(t)}function yb(t){return t>=1?db:t<=-1?-db:Math.asin(t)}function vb(t){return t.innerRadius}function _b(t){return t.outerRadius}function bb(t){return t.startAngle}function mb(t){return t.endAngle}function xb(t){return t&&t.padAngle}function wb(t,n,e,r,i,o,a,u){var c=e-t,f=r-n,s=a-i,l=u-o,h=l*c-s*f;if(!(h*hC*C+P*P&&(A=S,T=E),{cx:A,cy:T,x01:-s,y01:-l,x11:A*(i/x-1),y11:T*(i/x-1)}}var Ab=Array.prototype.slice;function Tb(t){return"object"==typeof t&&"length"in t?t:Array.from(t)}function Sb(t){this._context=t}function Eb(t){return new Sb(t)}function kb(t){return t[0]}function Nb(t){return t[1]}function Cb(t,n){var e=rb(!0),r=null,i=Eb,o=null;function a(a){var u,c,f,s=(a=Tb(a)).length,l=!1;for(null==r&&(o=i(f=fa())),u=0;u<=s;++u)!(u=s;--l)u.point(y[l],v[l]);u.lineEnd(),u.areaEnd()}g&&(y[f]=+t(h,f,c),v[f]=+n(h,f,c),u.point(r?+r(h,f,c):y[f],e?+e(h,f,c):v[f]))}if(d)return u=null,d+""||null}function f(){return Cb().defined(i).curve(a).context(o)}return t="function"==typeof t?t:void 0===t?kb:rb(+t),n="function"==typeof n?n:rb(void 0===n?0:+n),e="function"==typeof e?e:void 0===e?Nb:rb(+e),c.x=function(n){return arguments.length?(t="function"==typeof n?n:rb(+n),r=null,c):t},c.x0=function(n){return arguments.length?(t="function"==typeof n?n:rb(+n),c):t},c.x1=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:rb(+t),c):r},c.y=function(t){return arguments.length?(n="function"==typeof t?t:rb(+t),e=null,c):n},c.y0=function(t){return arguments.length?(n="function"==typeof t?t:rb(+t),c):n},c.y1=function(t){return arguments.length?(e=null==t?null:"function"==typeof t?t:rb(+t),c):e},c.lineX0=c.lineY0=function(){return f().x(t).y(n)},c.lineY1=function(){return f().x(t).y(e)},c.lineX1=function(){return f().x(r).y(n)},c.defined=function(t){return arguments.length?(i="function"==typeof t?t:rb(!!t),c):i},c.curve=function(t){return arguments.length?(a=t,null!=o&&(u=a(o)),c):a},c.context=function(t){return arguments.length?(null==t?o=u=null:u=a(o=t),c):o},c}function zb(t,n){return nt?1:n>=t?0:NaN}function Db(t){return t}Sb.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:this._context.lineTo(t,n)}}};var qb=Fb(Eb);function Rb(t){this._curve=t}function Fb(t){function n(n){return new Rb(t(n))}return n._curve=t,n}function Ob(t){var n=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(t){return arguments.length?n(Fb(t)):n()._curve},t}function Ib(){return Ob(Cb().curve(qb))}function Ub(){var t=Pb().curve(qb),n=t.curve,e=t.lineX0,r=t.lineX1,i=t.lineY0,o=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return Ob(e())},delete t.lineX0,t.lineEndAngle=function(){return Ob(r())},delete t.lineX1,t.lineInnerRadius=function(){return Ob(i())},delete t.lineY0,t.lineOuterRadius=function(){return Ob(o())},delete t.lineY1,t.curve=function(t){return arguments.length?n(Fb(t)):n()._curve},t}function Bb(t,n){return[(n=+n)*Math.cos(t-=Math.PI/2),n*Math.sin(t)]}function Yb(t){return t.source}function Lb(t){return t.target}function jb(t){var n=Yb,e=Lb,r=kb,i=Nb,o=null;function a(){var a,u=Ab.call(arguments),c=n.apply(this,u),f=e.apply(this,u);if(o||(o=a=fa()),t(o,+r.apply(this,(u[0]=c,u)),+i.apply(this,u),+r.apply(this,(u[0]=f,u)),+i.apply(this,u)),a)return o=null,a+""||null}return a.source=function(t){return arguments.length?(n=t,a):n},a.target=function(t){return arguments.length?(e=t,a):e},a.x=function(t){return arguments.length?(r="function"==typeof t?t:rb(+t),a):r},a.y=function(t){return arguments.length?(i="function"==typeof t?t:rb(+t),a):i},a.context=function(t){return arguments.length?(o=null==t?null:t,a):o},a}function Hb(t,n,e,r,i){t.moveTo(n,e),t.bezierCurveTo(n=(n+r)/2,e,n,i,r,i)}function Xb(t,n,e,r,i){t.moveTo(n,e),t.bezierCurveTo(n,e=(e+i)/2,r,e,r,i)}function Gb(t,n,e,r,i){var o=Bb(n,e),a=Bb(n,e=(e+i)/2),u=Bb(r,e),c=Bb(r,i);t.moveTo(o[0],o[1]),t.bezierCurveTo(a[0],a[1],u[0],u[1],c[0],c[1])}Rb.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,n){this._curve.point(n*Math.sin(t),n*-Math.cos(t))}};var Vb={draw:function(t,n){var e=Math.sqrt(n/hb);t.moveTo(e,0),t.arc(0,0,e,0,pb)}},$b={draw:function(t,n){var e=Math.sqrt(n/5)/2;t.moveTo(-3*e,-e),t.lineTo(-e,-e),t.lineTo(-e,-3*e),t.lineTo(e,-3*e),t.lineTo(e,-e),t.lineTo(3*e,-e),t.lineTo(3*e,e),t.lineTo(e,e),t.lineTo(e,3*e),t.lineTo(-e,3*e),t.lineTo(-e,e),t.lineTo(-3*e,e),t.closePath()}},Wb=Math.sqrt(1/3),Zb=2*Wb,Kb={draw:function(t,n){var e=Math.sqrt(n/Zb),r=e*Wb;t.moveTo(0,-e),t.lineTo(r,0),t.lineTo(0,e),t.lineTo(-r,0),t.closePath()}},Qb=Math.sin(hb/10)/Math.sin(7*hb/10),Jb=Math.sin(pb/10)*Qb,tm=-Math.cos(pb/10)*Qb,nm={draw:function(t,n){var e=Math.sqrt(.8908130915292852*n),r=Jb*e,i=tm*e;t.moveTo(0,-e),t.lineTo(r,i);for(var o=1;o<5;++o){var a=pb*o/5,u=Math.cos(a),c=Math.sin(a);t.lineTo(c*e,-u*e),t.lineTo(u*r-c*i,c*r+u*i)}t.closePath()}},em={draw:function(t,n){var e=Math.sqrt(n),r=-e/2;t.rect(r,r,e,e)}},rm=Math.sqrt(3),im={draw:function(t,n){var e=-Math.sqrt(n/(3*rm));t.moveTo(0,2*e),t.lineTo(-rm*e,-e),t.lineTo(rm*e,-e),t.closePath()}},om=-.5,am=Math.sqrt(3)/2,um=1/Math.sqrt(12),cm=3*(um/2+1),fm={draw:function(t,n){var e=Math.sqrt(n/cm),r=e/2,i=e*um,o=r,a=e*um+e,u=-o,c=a;t.moveTo(r,i),t.lineTo(o,a),t.lineTo(u,c),t.lineTo(om*r-am*i,am*r+om*i),t.lineTo(om*o-am*a,am*o+om*a),t.lineTo(om*u-am*c,am*u+om*c),t.lineTo(om*r+am*i,om*i-am*r),t.lineTo(om*o+am*a,om*a-am*o),t.lineTo(om*u+am*c,om*c-am*u),t.closePath()}},sm=[Vb,$b,Kb,em,nm,im,fm];function lm(){}function hm(t,n,e){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+n)/6,(t._y0+4*t._y1+e)/6)}function dm(t){this._context=t}function pm(t){this._context=t}function gm(t){this._context=t}dm.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:hm(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:hm(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}},pm.prototype={areaStart:lm,areaEnd:lm,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x2=t,this._y2=n;break;case 1:this._point=2,this._x3=t,this._y3=n;break;case 2:this._point=3,this._x4=t,this._y4=n,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+n)/6);break;default:hm(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}},gm.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var e=(this._x0+4*this._x1+t)/6,r=(this._y0+4*this._y1+n)/6;this._line?this._context.lineTo(e,r):this._context.moveTo(e,r);break;case 3:this._point=4;default:hm(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}};class ym{constructor(t,n){this._context=t,this._x=n}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line}point(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:this._x?this._context.bezierCurveTo(this._x0=(this._x0+t)/2,this._y0,this._x0,n,t,n):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+n)/2,t,this._y0,t,n)}this._x0=t,this._y0=n}}function vm(t,n){this._basis=new dm(t),this._beta=n}vm.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,n=this._y,e=t.length-1;if(e>0)for(var r,i=t[0],o=n[0],a=t[e]-i,u=n[e]-o,c=-1;++c<=e;)r=c/e,this._basis.point(this._beta*t[c]+(1-this._beta)*(i+r*a),this._beta*n[c]+(1-this._beta)*(o+r*u));this._x=this._y=null,this._basis.lineEnd()},point:function(t,n){this._x.push(+t),this._y.push(+n)}};var _m=function t(n){function e(t){return 1===n?new dm(t):new vm(t,n)}return e.beta=function(n){return t(+n)},e}(.85);function bm(t,n,e){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-n),t._y2+t._k*(t._y1-e),t._x2,t._y2)}function mm(t,n){this._context=t,this._k=(1-n)/6}mm.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:bm(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2,this._x1=t,this._y1=n;break;case 2:this._point=3;default:bm(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var xm=function t(n){function e(t){return new mm(t,n)}return e.tension=function(n){return t(+n)},e}(0);function wm(t,n){this._context=t,this._k=(1-n)/6}wm.prototype={areaStart:lm,areaEnd:lm,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:bm(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Mm=function t(n){function e(t){return new wm(t,n)}return e.tension=function(n){return t(+n)},e}(0);function Am(t,n){this._context=t,this._k=(1-n)/6}Am.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:bm(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Tm=function t(n){function e(t){return new Am(t,n)}return e.tension=function(n){return t(+n)},e}(0);function Sm(t,n,e){var r=t._x1,i=t._y1,o=t._x2,a=t._y2;if(t._l01_a>lb){var u=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,c=3*t._l01_a*(t._l01_a+t._l12_a);r=(r*u-t._x0*t._l12_2a+t._x2*t._l01_2a)/c,i=(i*u-t._y0*t._l12_2a+t._y2*t._l01_2a)/c}if(t._l23_a>lb){var f=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,s=3*t._l23_a*(t._l23_a+t._l12_a);o=(o*f+t._x1*t._l23_2a-n*t._l12_2a)/s,a=(a*f+t._y1*t._l23_2a-e*t._l12_2a)/s}t._context.bezierCurveTo(r,i,o,a,t._x2,t._y2)}function Em(t,n){this._context=t,this._alpha=n}Em.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3;default:Sm(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var km=function t(n){function e(t){return n?new Em(t,n):new mm(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function Nm(t,n){this._context=t,this._alpha=n}Nm.prototype={areaStart:lm,areaEnd:lm,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:Sm(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Cm=function t(n){function e(t){return n?new Nm(t,n):new wm(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function Pm(t,n){this._context=t,this._alpha=n}Pm.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Sm(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var zm=function t(n){function e(t){return n?new Pm(t,n):new Am(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function Dm(t){this._context=t}function qm(t){return t<0?-1:1}function Rm(t,n,e){var r=t._x1-t._x0,i=n-t._x1,o=(t._y1-t._y0)/(r||i<0&&-0),a=(e-t._y1)/(i||r<0&&-0),u=(o*i+a*r)/(r+i);return(qm(o)+qm(a))*Math.min(Math.abs(o),Math.abs(a),.5*Math.abs(u))||0}function Fm(t,n){var e=t._x1-t._x0;return e?(3*(t._y1-t._y0)/e-n)/2:n}function Om(t,n,e){var r=t._x0,i=t._y0,o=t._x1,a=t._y1,u=(o-r)/3;t._context.bezierCurveTo(r+u,i+u*n,o-u,a-u*e,o,a)}function Im(t){this._context=t}function Um(t){this._context=new Bm(t)}function Bm(t){this._context=t}function Ym(t){this._context=t}function Lm(t){var n,e,r=t.length-1,i=new Array(r),o=new Array(r),a=new Array(r);for(i[0]=0,o[0]=2,a[0]=t[0]+2*t[1],n=1;n=0;--n)i[n]=(a[n]-i[n+1])/o[n];for(o[r-1]=(t[r]+i[r-1])/2,n=0;n1)for(var e,r,i,o=1,a=t[n[0]],u=a.length;o=0;)e[n]=n;return e}function Gm(t,n){return t[n]}function Vm(t){const n=[];return n.key=t,n}function $m(t){var n=t.map(Wm);return Xm(t).sort((function(t,e){return n[t]-n[e]}))}function Wm(t){for(var n,e=-1,r=0,i=t.length,o=-1/0;++eo&&(o=n,r=e);return r}function Zm(t){var n=t.map(Km);return Xm(t).sort((function(t,e){return n[t]-n[e]}))}function Km(t){for(var n,e=0,r=-1,i=t.length;++r=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,n),this._context.lineTo(t,n);else{var e=this._x*(1-this._t)+t*this._t;this._context.lineTo(e,this._y),this._context.lineTo(e,n)}}this._x=t,this._y=n}};var Qm=t=>()=>t;function Jm(t,{sourceEvent:n,target:e,transform:r,dispatch:i}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},target:{value:e,enumerable:!0,configurable:!0},transform:{value:r,enumerable:!0,configurable:!0},_:{value:i}})}function tx(t,n,e){this.k=t,this.x=n,this.y=e}tx.prototype={constructor:tx,scale:function(t){return 1===t?this:new tx(this.k*t,this.x,this.y)},translate:function(t,n){return 0===t&0===n?this:new tx(this.k,this.x+this.k*t,this.y+this.k*n)},apply:function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},applyX:function(t){return t*this.k+this.x},applyY:function(t){return t*this.k+this.y},invert:function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},invertX:function(t){return(t-this.x)/this.k},invertY:function(t){return(t-this.y)/this.k},rescaleX:function(t){return t.copy().domain(t.range().map(this.invertX,this).map(t.invert,t))},rescaleY:function(t){return t.copy().domain(t.range().map(this.invertY,this).map(t.invert,t))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var nx=new tx(1,0,0);function ex(t){for(;!t.__zoom;)if(!(t=t.parentNode))return nx;return t.__zoom}function rx(t){t.stopImmediatePropagation()}function ix(t){t.preventDefault(),t.stopImmediatePropagation()}function ox(t){return!(t.ctrlKey&&"wheel"!==t.type||t.button)}function ax(){var t=this;return t instanceof SVGElement?(t=t.ownerSVGElement||t).hasAttribute("viewBox")?[[(t=t.viewBox.baseVal).x,t.y],[t.x+t.width,t.y+t.height]]:[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]:[[0,0],[t.clientWidth,t.clientHeight]]}function ux(){return this.__zoom||nx}function cx(t){return-t.deltaY*(1===t.deltaMode?.05:t.deltaMode?1:.002)*(t.ctrlKey?10:1)}function fx(){return navigator.maxTouchPoints||"ontouchstart"in this}function sx(t,n,e){var r=t.invertX(n[0][0])-e[0][0],i=t.invertX(n[1][0])-e[1][0],o=t.invertY(n[0][1])-e[0][1],a=t.invertY(n[1][1])-e[1][1];return t.translate(i>r?(r+i)/2:Math.min(0,r)||Math.max(0,i),a>o?(o+a)/2:Math.min(0,o)||Math.max(0,a))}ex.prototype=tx.prototype,t.Adder=g,t.Delaunay=nu,t.FormatSpecifier=uc,t.InternMap=y,t.InternSet=v,t.Voronoi=Wa,t.active=function(t,n){var e,r,i=t.__transition;if(i)for(r in n=null==n?null:n+"",i)if((e=i[r]).state>1&&e.name===n)return new ji([[t]],_o,n,+r);return null},t.arc=function(){var t=vb,n=_b,e=rb(0),r=null,i=bb,o=mb,a=xb,u=null;function c(){var c,f,s=+t.apply(this,arguments),l=+n.apply(this,arguments),h=i.apply(this,arguments)-db,d=o.apply(this,arguments)-db,p=ib(d-h),g=d>h;if(u||(u=c=fa()),llb)if(p>pb-lb)u.moveTo(l*ab(h),l*fb(h)),u.arc(0,0,l,h,d,!g),s>lb&&(u.moveTo(s*ab(d),s*fb(d)),u.arc(0,0,s,d,h,g));else{var y,v,_=h,b=d,m=h,x=d,w=p,M=p,A=a.apply(this,arguments)/2,T=A>lb&&(r?+r.apply(this,arguments):sb(s*s+l*l)),S=cb(ib(l-s)/2,+e.apply(this,arguments)),E=S,k=S;if(T>lb){var N=yb(T/s*fb(A)),C=yb(T/l*fb(A));(w-=2*N)>lb?(m+=N*=g?1:-1,x-=N):(w=0,m=x=(h+d)/2),(M-=2*C)>lb?(_+=C*=g?1:-1,b-=C):(M=0,_=b=(h+d)/2)}var P=l*ab(_),z=l*fb(_),D=s*ab(x),q=s*fb(x);if(S>lb){var R,F=l*ab(b),O=l*fb(b),I=s*ab(m),U=s*fb(m);if(plb?k>lb?(y=Mb(I,U,P,z,l,k,g),v=Mb(F,O,D,q,l,k,g),u.moveTo(y.cx+y.x01,y.cy+y.y01),klb&&w>lb?E>lb?(y=Mb(D,q,F,O,s,-E,g),v=Mb(P,z,I,U,s,-E,g),u.lineTo(y.cx+y.x01,y.cy+y.y01),E>a,f=i+2*u>>a,s=wa(20);function l(r){var i=new Float32Array(c*f),l=new Float32Array(c*f);r.forEach((function(r,o,s){var l=+t(r,o,s)+u>>a,h=+n(r,o,s)+u>>a,d=+e(r,o,s);l>=0&&l=0&&h>a),Ca({width:c,height:f,data:l},{width:c,height:f,data:i},o>>a),Na({width:c,height:f,data:i},{width:c,height:f,data:l},o>>a),Ca({width:c,height:f,data:l},{width:c,height:f,data:i},o>>a),Na({width:c,height:f,data:i},{width:c,height:f,data:l},o>>a),Ca({width:c,height:f,data:l},{width:c,height:f,data:i},o>>a);var d=s(i);if(!Array.isArray(d)){var p=B(i);d=F(0,p,d),(d=Z(0,Math.floor(p/d)*d,d)).shift()}return ka().thresholds(d).size([c,f])(i).map(h)}function h(t){return t.value*=Math.pow(2,-2*a),t.coordinates.forEach(d),t}function d(t){t.forEach(p)}function p(t){t.forEach(g)}function g(t){t[0]=t[0]*Math.pow(2,a)-u,t[1]=t[1]*Math.pow(2,a)-u}function y(){return c=r+2*(u=3*o)>>a,f=i+2*u>>a,l}return l.x=function(n){return arguments.length?(t="function"==typeof n?n:wa(+n),l):t},l.y=function(t){return arguments.length?(n="function"==typeof t?t:wa(+t),l):n},l.weight=function(t){return arguments.length?(e="function"==typeof t?t:wa(+t),l):e},l.size=function(t){if(!arguments.length)return[r,i];var n=+t[0],e=+t[1];if(!(n>=0&&e>=0))throw new Error("invalid size");return r=n,i=e,y()},l.cellSize=function(t){if(!arguments.length)return 1<=1))throw new Error("invalid cell size");return a=Math.floor(Math.log(t)/Math.LN2),y()},l.thresholds=function(t){return arguments.length?(s="function"==typeof t?t:Array.isArray(t)?wa(ma.call(t)):wa(t),l):s},l.bandwidth=function(t){if(!arguments.length)return Math.sqrt(o*(o+1));if(!((t=+t)>=0))throw new Error("invalid bandwidth");return o=Math.round((Math.sqrt(4*t*t+1)-1)/2),y()},l},t.contours=ka,t.count=c,t.create=function(t){return Dn(At(t).call(document.documentElement))},t.creator=At,t.cross=function(...t){const n="function"==typeof t[t.length-1]&&function(t){return n=>t(...n)}(t.pop()),e=(t=t.map(l)).map(f),r=t.length-1,i=new Array(r+1).fill(0),o=[];if(r<0||e.some(s))return o;for(;;){o.push(i.map(((n,e)=>t[e][n])));let a=r;for(;++i[a]===e[a];){if(0===a)return n?o.map(n):o;i[a--]=0}}},t.csv=Pu,t.csvFormat=hu,t.csvFormatBody=du,t.csvFormatRow=gu,t.csvFormatRows=pu,t.csvFormatValue=yu,t.csvParse=su,t.csvParseRows=lu,t.cubehelix=tr,t.cumsum=function(t,n){var e=0,r=0;return Float64Array.from(t,void 0===n?t=>e+=+t||0:i=>e+=+n(i,r++,t)||0)},t.curveBasis=function(t){return new dm(t)},t.curveBasisClosed=function(t){return new pm(t)},t.curveBasisOpen=function(t){return new gm(t)},t.curveBumpX=function(t){return new ym(t,!0)},t.curveBumpY=function(t){return new ym(t,!1)},t.curveBundle=_m,t.curveCardinal=xm,t.curveCardinalClosed=Mm,t.curveCardinalOpen=Tm,t.curveCatmullRom=km,t.curveCatmullRomClosed=Cm,t.curveCatmullRomOpen=zm,t.curveLinear=Eb,t.curveLinearClosed=function(t){return new Dm(t)},t.curveMonotoneX=function(t){return new Im(t)},t.curveMonotoneY=function(t){return new Um(t)},t.curveNatural=function(t){return new Ym(t)},t.curveStep=function(t){return new jm(t,.5)},t.curveStepAfter=function(t){return new jm(t,1)},t.curveStepBefore=function(t){return new jm(t,0)},t.descending=function(t,n){return nt?1:n>=t?0:NaN},t.deviation=d,t.difference=function(t,...n){t=new Set(t);for(const e of n)for(const n of e)t.delete(n);return t},t.disjoint=function(t,n){const e=n[Symbol.iterator](),r=new Set;for(const n of t){if(r.has(n))return!1;let t,i;for(;({value:t,done:i}=e.next())&&!i;){if(Object.is(n,t))return!1;r.add(t)}}return!0},t.dispatch=pt,t.drag=function(){var t,n,e,r,i=Xn,o=Gn,a=Vn,u=$n,c={},f=pt("start","drag","end"),s=0,l=0;function h(t){t.on("mousedown.drag",d).filter(u).on("touchstart.drag",y).on("touchmove.drag",v).on("touchend.drag touchcancel.drag",_).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function d(a,u){if(!r&&i.call(this,a,u)){var c=b(this,o.call(this,a,u),a,u,"mouse");c&&(Dn(a.view).on("mousemove.drag",p,!0).on("mouseup.drag",g,!0),Yn(a.view),Un(a),e=!1,t=a.clientX,n=a.clientY,c("start",a))}}function p(r){if(Bn(r),!e){var i=r.clientX-t,o=r.clientY-n;e=i*i+o*o>l}c.mouse("drag",r)}function g(t){Dn(t.view).on("mousemove.drag mouseup.drag",null),Ln(t.view,e),Bn(t),c.mouse("end",t)}function y(t,n){if(i.call(this,t,n)){var e,r,a=t.changedTouches,u=o.call(this,t,n),c=a.length;for(e=0;e+t,t.easePoly=Ki,t.easePolyIn=Wi,t.easePolyInOut=Ki,t.easePolyOut=Zi,t.easeQuad=Vi,t.easeQuadIn=function(t){return t*t},t.easeQuadInOut=Vi,t.easeQuadOut=function(t){return t*(2-t)},t.easeSin=to,t.easeSinIn=function(t){return 1==+t?1:1-Math.cos(t*Ji)},t.easeSinInOut=to,t.easeSinOut=function(t){return Math.sin(t*Ji)},t.every=function(t,n){if("function"!=typeof n)throw new TypeError("test is not a function");let e=-1;for(const r of t)if(!n(r,++e,t))return!1;return!0},t.extent=p,t.fcumsum=function(t,n){const e=new g;let r=-1;return Float64Array.from(t,void 0===n?t=>e.add(+t||0):i=>e.add(+n(i,++r,t)||0))},t.filter=function(t,n){if("function"!=typeof n)throw new TypeError("test is not a function");const e=[];let r=-1;for(const i of t)n(i,++r,t)&&e.push(i);return e},t.forceCenter=function(t,n){var e,r=1;function i(){var i,o,a=e.length,u=0,c=0;for(i=0;if+p||os+p||ac.index){var g=f-u.x-u.vx,y=s-u.y-u.vy,v=g*g+y*y;vt.r&&(t.r=t[n].r)}function c(){if(n){var r,i,o=n.length;for(e=new Array(o),r=0;r[u(t,n,r),t])));for(a=0,i=new Array(f);a=u)){(t.data!==n||t.next)&&(0===l&&(p+=(l=Vu(e))*l),0===h&&(p+=(h=Vu(e))*h),p(t=(1664525*t+1013904223)%Qu)/Qu}();function l(){h(),f.call("tick",n),e1?(null==e?u.delete(t):u.set(t,p(e)),n):u.get(t)},find:function(n,e,r){var i,o,a,u,c,f=0,s=t.length;for(null==r?r=1/0:r*=r,f=0;f1?(f.on(t,e),n):f.on(t)}}},t.forceX=function(t){var n,e,r,i=Gu(.1);function o(t){for(var i,o=0,a=n.length;o=.12&&i<.234&&r>=-.425&&r<-.214?u:i>=.166&&i<.234&&r>=-.214&&r<-.115?c:a).invert(t)},s.stream=function(e){return t&&n===e?t:(r=[a.stream(n=e),u.stream(e),c.stream(e)],i=r.length,t={point:function(t,n){for(var e=-1;++eKf(r[0],r[1])&&(r[1]=i[1]),Kf(i[0],r[1])>Kf(r[0],r[1])&&(r[0]=i[0])):o.push(r=i);for(a=-1/0,n=0,r=o[e=o.length-1];n<=e;r=i,++n)i=o[n],(u=Kf(r[1],i[0]))>a&&(a=u,nf=i[0],rf=r[1])}return lf=hf=null,nf===1/0||ef===1/0?[[NaN,NaN],[NaN,NaN]]:[[nf,ef],[rf,of]]},t.geoCentroid=function(t){Ef=kf=Nf=Cf=Pf=zf=Df=qf=0,Rf=new g,Ff=new g,Of=new g,Wc(t,ts);var n=+Rf,e=+Ff,r=+Of,i=Dc(n,e,r);return i2?t[2]+90:90]):[(t=e())[0],t[1],t[2]-90]},e([0,0,90]).scale(159.155)},t.geoTransverseMercatorRaw=Bh,t.gray=function(t,n){return new Fe(t,0,0,null==n?1:n)},t.greatest=function(t,e=n){let r,i=!1;if(1===e.length){let o;for(const a of t){const t=e(a);(i?n(t,o)>0:0===n(t,t))&&(r=a,o=t,i=!0)}}else for(const n of t)(i?e(n,r)>0:0===e(n,n))&&(r=n,i=!0);return r},t.greatestIndex=function(t,e=n){if(1===e.length)return G(t,e);let r,i=-1,o=-1;for(const n of t)++o,(i<0?0===e(n,n):e(n,r)>0)&&(r=n,i=o);return i},t.group=M,t.groupSort=function(t,e,r){return(1===e.length?k(A(t,e,r),(([t,e],[r,i])=>n(e,i)||n(t,r))):k(M(t,r),(([t,r],[i,o])=>e(r,o)||n(t,i)))).map((([t])=>t))},t.groups=function(t,...n){return S(t,Array.from,w,n)},t.hcl=Le,t.hierarchy=Xh,t.histogram=U,t.hsl=Ae,t.html=Fu,t.image=function(t,n){return new Promise((function(e,r){var i=new Image;for(var o in n)i[o]=n[o];i.onerror=r,i.onload=function(){e(i)},i.src=t}))},t.index=function(t,...n){return S(t,w,T,n)},t.indexes=function(t,...n){return S(t,Array.from,T,n)},t.interpolate=Mr,t.interpolateArray=function(t,n){return(gr(n)?pr:yr)(t,n)},t.interpolateBasis=rr,t.interpolateBasisClosed=ir,t.interpolateBlues=q_,t.interpolateBrBG=Gv,t.interpolateBuGn=s_,t.interpolateBuPu=h_,t.interpolateCividis=function(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(-4.54-t*(35.34-t*(2381.73-t*(6402.7-t*(7024.72-2710.57*t)))))))+", "+Math.max(0,Math.min(255,Math.round(32.49+t*(170.73+t*(52.82-t*(131.46-t*(176.58-67.37*t)))))))+", "+Math.max(0,Math.min(255,Math.round(81.24+t*(442.36-t*(2482.43-t*(6167.24-t*(6614.94-2475.67*t)))))))+")"},t.interpolateCool=V_,t.interpolateCubehelix=Yr,t.interpolateCubehelixDefault=X_,t.interpolateCubehelixLong=Lr,t.interpolateDate=vr,t.interpolateDiscrete=function(t){var n=t.length;return function(e){return t[Math.max(0,Math.min(n-1,Math.floor(e*n)))]}},t.interpolateGnBu=p_,t.interpolateGreens=F_,t.interpolateGreys=I_,t.interpolateHcl=Ir,t.interpolateHclLong=Ur,t.interpolateHsl=Rr,t.interpolateHslLong=Fr,t.interpolateHue=function(t,n){var e=ur(+t,+n);return function(t){var n=e(t);return n-360*Math.floor(n/360)}},t.interpolateInferno=nb,t.interpolateLab=function(t,n){var e=fr((t=Re(t)).l,(n=Re(n)).l),r=fr(t.a,n.a),i=fr(t.b,n.b),o=fr(t.opacity,n.opacity);return function(n){return t.l=e(n),t.a=r(n),t.b=i(n),t.opacity=o(n),t+""}},t.interpolateMagma=tb,t.interpolateNumber=_r,t.interpolateNumberArray=pr,t.interpolateObject=br,t.interpolateOrRd=y_,t.interpolateOranges=H_,t.interpolatePRGn=$v,t.interpolatePiYG=Zv,t.interpolatePlasma=eb,t.interpolatePuBu=m_,t.interpolatePuBuGn=__,t.interpolatePuOr=Qv,t.interpolatePuRd=w_,t.interpolatePurples=B_,t.interpolateRainbow=function(t){(t<0||t>1)&&(t-=Math.floor(t));var n=Math.abs(t-.5);return $_.h=360*t-100,$_.s=1.5-1.5*n,$_.l=.8-.9*n,$_+""},t.interpolateRdBu=t_,t.interpolateRdGy=e_,t.interpolateRdPu=A_,t.interpolateRdYlBu=i_,t.interpolateRdYlGn=a_,t.interpolateReds=L_,t.interpolateRgb=sr,t.interpolateRgbBasis=hr,t.interpolateRgbBasisClosed=dr,t.interpolateRound=Ar,t.interpolateSinebow=function(t){var n;return t=(.5-t)*Math.PI,W_.r=255*(n=Math.sin(t))*n,W_.g=255*(n=Math.sin(t+Z_))*n,W_.b=255*(n=Math.sin(t+K_))*n,W_+""},t.interpolateSpectral=c_,t.interpolateString=wr,t.interpolateTransformCss=Cr,t.interpolateTransformSvg=Pr,t.interpolateTurbo=function(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(34.61+t*(1172.33-t*(10793.56-t*(33300.12-t*(38394.49-14825.05*t)))))))+", "+Math.max(0,Math.min(255,Math.round(23.31+t*(557.33+t*(1225.33-t*(3574.96-t*(1073.77+707.56*t)))))))+", "+Math.max(0,Math.min(255,Math.round(27.2+t*(3211.1-t*(15327.97-t*(27814-t*(22569.18-6838.66*t)))))))+")"},t.interpolateViridis=J_,t.interpolateWarm=G_,t.interpolateYlGn=k_,t.interpolateYlGnBu=S_,t.interpolateYlOrBr=C_,t.interpolateYlOrRd=z_,t.interpolateZoom=Dr,t.interrupt=gi,t.intersection=function(t,...n){t=new Set(t),n=n.map(et);t:for(const e of t)for(const r of n)if(!r.has(e)){t.delete(e);continue t}return t},t.interval=function(t,n,e){var r=new ei,i=n;return null==n?(r.restart(t,n,e),r):(r._restart=r.restart,r.restart=function(t,n,e){n=+n,e=null==e?ti():+e,r._restart((function o(a){a+=i,r._restart(o,i+=n,e),t(a)}),n,e)},r.restart(t,n,e),r)},t.isoFormat=Mv,t.isoParse=Av,t.json=function(t,n){return fetch(t,n).then(Du)},t.lab=Re,t.lch=function(t,n,e,r){return 1===arguments.length?Ye(t):new je(e,n,t,null==r?1:r)},t.least=function(t,e=n){let r,i=!1;if(1===e.length){let o;for(const a of t){const t=e(a);(i?n(t,o)<0:0===n(t,t))&&(r=a,o=t,i=!0)}}else for(const n of t)(i?e(n,r)<0:0===e(n,n))&&(r=n,i=!0);return r},t.leastIndex=K,t.line=Cb,t.lineRadial=Ib,t.linkHorizontal=function(){return jb(Hb)},t.linkRadial=function(){var t=jb(Gb);return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t},t.linkVertical=function(){return jb(Xb)},t.local=Rn,t.map=function(t,n){if("function"!=typeof t[Symbol.iterator])throw new TypeError("values is not iterable");if("function"!=typeof n)throw new TypeError("mapper is not a function");return Array.from(t,((e,r)=>n(e,r,t)))},t.matcher=Ct,t.max=B,t.maxIndex=G,t.mean=function(t,n){let e=0,r=0;if(void 0===n)for(let n of t)null!=n&&(n=+n)>=n&&(++e,r+=n);else{let i=-1;for(let o of t)null!=(o=n(o,++i,t))&&(o=+o)>=o&&(++e,r+=o)}if(e)return r/e},t.median=function(t,n){return H(t,.5,n)},t.merge=V,t.min=Y,t.minIndex=$,t.namespace=xt,t.namespaces=mt,t.nice=O,t.now=ti,t.pack=function(){var t=null,n=1,e=1,r=hd;function i(i){return i.x=n/2,i.y=e/2,t?i.eachBefore(gd(t)).eachAfter(yd(r,.5)).eachBefore(vd(1)):i.eachBefore(gd(pd)).eachAfter(yd(hd,1)).eachAfter(yd(r,i.r/Math.min(n,e))).eachBefore(vd(Math.min(n,e)/(2*i.r))),i}return i.radius=function(n){return arguments.length?(t=sd(n),i):t},i.size=function(t){return arguments.length?(n=+t[0],e=+t[1],i):[n,e]},i.padding=function(t){return arguments.length?(r="function"==typeof t?t:dd(+t),i):r},i},t.packEnclose=Kh,t.packSiblings=function(t){return fd(t),t},t.pairs=function(t,n=W){const e=[];let r,i=!1;for(const o of t)i&&e.push(n(r,o)),r=o,i=!0;return e},t.partition=function(){var t=1,n=1,e=0,r=!1;function i(i){var o=i.height+1;return i.x0=i.y0=e,i.x1=t,i.y1=n/o,i.eachBefore(function(t,n){return function(r){r.children&&bd(r,r.x0,t*(r.depth+1)/n,r.x1,t*(r.depth+2)/n);var i=r.x0,o=r.y0,a=r.x1-e,u=r.y1-e;a0&&(d+=l);for(null!=n?p.sort((function(t,e){return n(g[t],g[e])})):null!=e&&p.sort((function(t,n){return e(a[t],a[n])})),u=0,f=d?(v-h*b)/d:0;u0?l*f:0)+b,g[c]={data:a[c],index:u,value:l,startAngle:y,endAngle:s,padAngle:_};return g}return a.value=function(n){return arguments.length?(t="function"==typeof n?n:rb(+n),a):t},a.sortValues=function(t){return arguments.length?(n=t,e=null,a):n},a.sort=function(t){return arguments.length?(e=t,n=null,a):e},a.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:rb(+t),a):r},a.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:rb(+t),a):i},a.padAngle=function(t){return arguments.length?(o="function"==typeof t?t:rb(+t),a):o},a},t.piecewise=jr,t.pointRadial=Bb,t.pointer=In,t.pointers=function(t,n){return t.target&&(t=On(t),void 0===n&&(n=t.currentTarget),t=t.touches||[t]),Array.from(t,(t=>In(t,n)))},t.polygonArea=function(t){for(var n,e=-1,r=t.length,i=t[r-1],o=0;++eu!=f>u&&a<(c-e)*(u-r)/(f-r)+e&&(s=!s),c=e,f=r;return s},t.polygonHull=function(t){if((e=t.length)<3)return null;var n,e,r=new Array(e),i=new Array(e);for(n=0;n=0;--n)f.push(t[r[o[n]][2]]);for(n=+u;n(n=1664525*n+1013904223|0,ep*(n>>>0))},t.randomLogNormal=Ld,t.randomLogistic=tp,t.randomNormal=Yd,t.randomPareto=Gd,t.randomPoisson=np,t.randomUniform=Ud,t.randomWeibull=Qd,t.range=Z,t.reduce=function(t,n,e){if("function"!=typeof n)throw new TypeError("reducer is not a function");const r=t[Symbol.iterator]();let i,o,a=-1;if(arguments.length<3){if(({done:i,value:e}=r.next()),i)return;++a}for(;({done:i,value:o}=r.next()),!i;)e=n(e,o,++a,t);return e},t.reverse=function(t){if("function"!=typeof t[Symbol.iterator])throw new TypeError("values is not iterable");return Array.from(t).reverse()},t.rgb=ve,t.ribbon=function(){return ba()},t.ribbonArrow=function(){return ba(_a)},t.rollup=A,t.rollups=function(t,n,...e){return S(t,Array.from,n,e)},t.scaleBand=up,t.scaleDiverging=function t(){var n=bp(Pv()(lp));return n.copy=function(){return Nv(n,t())},ip.apply(n,arguments)},t.scaleDivergingLog=function t(){var n=Ep(Pv()).domain([.1,1,10]);return n.copy=function(){return Nv(n,t()).base(n.base())},ip.apply(n,arguments)},t.scaleDivergingPow=zv,t.scaleDivergingSqrt=function(){return zv.apply(null,arguments).exponent(.5)},t.scaleDivergingSymlog=function t(){var n=Cp(Pv());return n.copy=function(){return Nv(n,t()).constant(n.constant())},ip.apply(n,arguments)},t.scaleIdentity=function t(n){var e;function r(t){return null==t||isNaN(t=+t)?e:t}return r.invert=r,r.domain=r.range=function(t){return arguments.length?(n=Array.from(t,fp),r):n.slice()},r.unknown=function(t){return arguments.length?(e=t,r):e},r.copy=function(){return t(n).unknown(e)},n=arguments.length?Array.from(n,fp):[0,1],bp(r)},t.scaleImplicit=op,t.scaleLinear=function t(){var n=vp();return n.copy=function(){return gp(n,t())},rp.apply(n,arguments),bp(n)},t.scaleLog=function t(){var n=Ep(yp()).domain([1,10]);return n.copy=function(){return gp(n,t()).base(n.base())},rp.apply(n,arguments),n},t.scaleOrdinal=ap,t.scalePoint=function(){return cp(up.apply(null,arguments).paddingInner(1))},t.scalePow=Rp,t.scaleQuantile=function t(){var e,r=[],i=[],a=[];function u(){var t=0,n=Math.max(1,i.length);for(a=new Array(n-1);++t0?a[n-1]:r[0],n=i?[a[i-1],r]:[a[n-1],a[n]]},c.unknown=function(t){return arguments.length?(n=t,c):c},c.thresholds=function(){return a.slice()},c.copy=function(){return t().domain([e,r]).range(u).unknown(n)},rp.apply(bp(c),arguments)},t.scaleRadial=function t(){var n,e=vp(),r=[0,1],i=!1;function o(t){var r=Op(e(t));return isNaN(r)?n:i?Math.round(r):r}return o.invert=function(t){return e.invert(Fp(t))},o.domain=function(t){return arguments.length?(e.domain(t),o):e.domain()},o.range=function(t){return arguments.length?(e.range((r=Array.from(t,fp)).map(Fp)),o):r.slice()},o.rangeRound=function(t){return o.range(t).round(!0)},o.round=function(t){return arguments.length?(i=!!t,o):i},o.clamp=function(t){return arguments.length?(e.clamp(t),o):e.clamp()},o.unknown=function(t){return arguments.length?(n=t,o):n},o.copy=function(){return t(e.domain(),r).round(i).clamp(e.clamp()).unknown(n)},rp.apply(o,arguments),bp(o)},t.scaleSequential=function t(){var n=bp(kv()(lp));return n.copy=function(){return Nv(n,t())},ip.apply(n,arguments)},t.scaleSequentialLog=function t(){var n=Ep(kv()).domain([1,10]);return n.copy=function(){return Nv(n,t()).base(n.base())},ip.apply(n,arguments)},t.scaleSequentialPow=Cv,t.scaleSequentialQuantile=function t(){var e=[],r=lp;function i(t){if(null!=t&&!isNaN(t=+t))return r((o(e,t,1)-1)/(e.length-1))}return i.domain=function(t){if(!arguments.length)return e.slice();e=[];for(let n of t)null==n||isNaN(n=+n)||e.push(n);return e.sort(n),i},i.interpolator=function(t){return arguments.length?(r=t,i):r},i.range=function(){return e.map(((t,n)=>r(n/(e.length-1))))},i.quantiles=function(t){return Array.from({length:t+1},((n,r)=>H(e,r/t)))},i.copy=function(){return t(r).domain(e)},ip.apply(i,arguments)},t.scaleSequentialSqrt=function(){return Cv.apply(null,arguments).exponent(.5)},t.scaleSequentialSymlog=function t(){var n=Cp(kv());return n.copy=function(){return Nv(n,t()).constant(n.constant())},ip.apply(n,arguments)},t.scaleSqrt=function(){return Rp.apply(null,arguments).exponent(.5)},t.scaleSymlog=function t(){var n=Cp(yp());return n.copy=function(){return gp(n,t()).constant(n.constant())},rp.apply(n,arguments)},t.scaleThreshold=function t(){var n,e=[.5],r=[0,1],i=1;function a(t){return null!=t&&t<=t?r[o(e,t,0,i)]:n}return a.domain=function(t){return arguments.length?(e=Array.from(t),i=Math.min(e.length,r.length-1),a):e.slice()},a.range=function(t){return arguments.length?(r=Array.from(t),i=Math.min(e.length,r.length-1),a):r.slice()},a.invertExtent=function(t){var n=r.indexOf(t);return[e[n-1],e[n]]},a.unknown=function(t){return arguments.length?(n=t,a):n},a.copy=function(){return t().domain(e).range(r).unknown(n)},rp.apply(a,arguments)},t.scaleTime=function(){return rp.apply(Ev(Kg,Qg,xg,bg,og,eg,tg,Qp,Zp,t.timeFormat).domain([new Date(2e3,0,1),new Date(2e3,0,2)]),arguments)},t.scaleUtc=function(){return rp.apply(Ev(Wg,Zg,Gg,Hg,Cg,Eg,Tg,Mg,Zp,t.utcFormat).domain([Date.UTC(2e3,0,1),Date.UTC(2e3,0,2)]),arguments)},t.scan=function(t,n){const e=K(t,n);return e<0?void 0:e},t.schemeAccent=Rv,t.schemeBlues=D_,t.schemeBrBG=Xv,t.schemeBuGn=f_,t.schemeBuPu=l_,t.schemeCategory10=qv,t.schemeDark2=Fv,t.schemeGnBu=d_,t.schemeGreens=R_,t.schemeGreys=O_,t.schemeOrRd=g_,t.schemeOranges=j_,t.schemePRGn=Vv,t.schemePaired=Ov,t.schemePastel1=Iv,t.schemePastel2=Uv,t.schemePiYG=Wv,t.schemePuBu=b_,t.schemePuBuGn=v_,t.schemePuOr=Kv,t.schemePuRd=x_,t.schemePurples=U_,t.schemeRdBu=Jv,t.schemeRdGy=n_,t.schemeRdPu=M_,t.schemeRdYlBu=r_,t.schemeRdYlGn=o_,t.schemeReds=Y_,t.schemeSet1=Bv,t.schemeSet2=Yv,t.schemeSet3=Lv,t.schemeSpectral=u_,t.schemeTableau10=jv,t.schemeYlGn=E_,t.schemeYlGnBu=T_,t.schemeYlOrBr=N_,t.schemeYlOrRd=P_,t.select=Dn,t.selectAll=function(t){return"string"==typeof t?new Pn([document.querySelectorAll(t)],[document.documentElement]):new Pn([null==t?[]:Et(t)],Cn)},t.selection=zn,t.selector=St,t.selectorAll=Nt,t.shuffle=Q,t.shuffler=J,t.some=function(t,n){if("function"!=typeof n)throw new TypeError("test is not a function");let e=-1;for(const r of t)if(n(r,++e,t))return!0;return!1},t.sort=k,t.stack=function(){var t=rb([]),n=Xm,e=Hm,r=Gm;function i(i){var o,a,u=Array.from(t.apply(this,arguments),Vm),c=u.length,f=-1;for(const t of i)for(o=0,++f;o0)for(var e,r,i,o,a,u,c=0,f=t[n[0]].length;c0?(r[0]=o,r[1]=o+=i):i<0?(r[1]=a,r[0]=a+=i):(r[0]=0,r[1]=i)},t.stackOffsetExpand=function(t,n){if((r=t.length)>0){for(var e,r,i,o=0,a=t[0].length;o0){for(var e,r=0,i=t[n[0]],o=i.length;r0&&(r=(e=t[n[0]]).length)>0){for(var e,r,i,o=0,a=1;a0)throw new Error("cycle");return o}return e.id=function(n){return arguments.length?(t=ld(n),e):t},e.parentId=function(t){return arguments.length?(n=ld(t),e):n},e},t.style=Jt,t.subset=function(t,n){return rt(n,t)},t.sum=function(t,n){let e=0;if(void 0===n)for(let n of t)(n=+n)&&(e+=n);else{let r=-1;for(let i of t)(i=+n(i,++r,t))&&(e+=i)}return e},t.superset=rt,t.svg=Ou,t.symbol=function(t,n){var e=null;function r(){var r;if(e||(e=r=fa()),t.apply(this,arguments).draw(e,+n.apply(this,arguments)),r)return e=null,r+""||null}return t="function"==typeof t?t:rb(t||Vb),n="function"==typeof n?n:rb(void 0===n?64:+n),r.type=function(n){return arguments.length?(t="function"==typeof n?n:rb(n),r):t},r.size=function(t){return arguments.length?(n="function"==typeof t?t:rb(+t),r):n},r.context=function(t){return arguments.length?(e=null==t?null:t,r):e},r},t.symbolCircle=Vb,t.symbolCross=$b,t.symbolDiamond=Kb,t.symbolSquare=em,t.symbolStar=nm,t.symbolTriangle=im,t.symbolWye=fm,t.symbols=sm,t.text=Nu,t.thresholdFreedmanDiaconis=function(t,n,e){return Math.ceil((e-n)/(2*(H(t,.75)-H(t,.25))*Math.pow(c(t),-1/3)))},t.thresholdScott=function(t,n,e){return Math.ceil((e-n)/(3.5*d(t)*Math.pow(c(t),-1/3)))},t.thresholdSturges=I,t.tickFormat=_p,t.tickIncrement=R,t.tickStep=F,t.ticks=q,t.timeDay=eg,t.timeDays=rg,t.timeFormatDefaultLocale=xv,t.timeFormatLocale=ey,t.timeFriday=sg,t.timeFridays=vg,t.timeHour=tg,t.timeHours=ng,t.timeInterval=Bp,t.timeMillisecond=Yp,t.timeMilliseconds=Lp,t.timeMinute=Qp,t.timeMinutes=Jp,t.timeMonday=ag,t.timeMondays=dg,t.timeMonth=bg,t.timeMonths=mg,t.timeSaturday=lg,t.timeSaturdays=_g,t.timeSecond=Zp,t.timeSeconds=Kp,t.timeSunday=og,t.timeSundays=hg,t.timeThursday=fg,t.timeThursdays=yg,t.timeTickInterval=Qg,t.timeTicks=Kg,t.timeTuesday=ug,t.timeTuesdays=pg,t.timeWednesday=cg,t.timeWednesdays=gg,t.timeWeek=og,t.timeWeeks=hg,t.timeYear=xg,t.timeYears=wg,t.timeout=ci,t.timer=ri,t.timerFlush=ii,t.transition=Hi,t.transpose=tt,t.tree=function(){var t=Ad,n=1,e=1,r=null;function i(i){var c=function(t){for(var n,e,r,i,o,a=new Nd(t,0),u=[a];n=u.pop();)if(r=n._.children)for(n.children=new Array(o=r.length),i=o-1;i>=0;--i)u.push(e=n.children[i]=new Nd(r[i],i)),e.parent=n;return(a.parent=new Nd(null,0)).children=[a],a}(i);if(c.eachAfter(o),c.parent.m=-c.z,c.eachBefore(a),r)i.eachBefore(u);else{var f=i,s=i,l=i;i.eachBefore((function(t){t.xs.x&&(s=t),t.depth>l.depth&&(l=t)}));var h=f===s?1:t(f,s)/2,d=h-f.x,p=n/(s.x+h+d),g=e/(l.depth||1);i.eachBefore((function(t){t.x=(t.x+d)*p,t.y=t.depth*g}))}return i}function o(n){var e=n.children,r=n.parent.children,i=n.i?r[n.i-1]:null;if(e){!function(t){for(var n,e=0,r=0,i=t.children,o=i.length;--o>=0;)(n=i[o]).z+=e,n.m+=e,e+=n.s+(r+=n.c)}(n);var o=(e[0].z+e[e.length-1].z)/2;i?(n.z=i.z+t(n._,i._),n.m=n.z-o):n.z=o}else i&&(n.z=i.z+t(n._,i._));n.parent.A=function(n,e,r){if(e){for(var i,o=n,a=n,u=e,c=o.parent.children[0],f=o.m,s=a.m,l=u.m,h=c.m;u=Sd(u),o=Td(o),u&&o;)c=Td(c),(a=Sd(a)).a=n,(i=u.z+l-o.z-f+t(u._,o._))>0&&(Ed(kd(u,n,r),n,i),f+=i,s+=i),l+=u.m,f+=o.m,h+=c.m,s+=a.m;u&&!Sd(a)&&(a.t=u,a.m+=l-s),o&&!Td(c)&&(c.t=o,c.m+=f-h,r=n)}return r}(n,i,n.parent.A||r[0])}function a(t){t._.x=t.z+t.parent.m,t.m+=t.parent.m}function u(t){t.x*=n,t.y=t.depth*e}return i.separation=function(n){return arguments.length?(t=n,i):t},i.size=function(t){return arguments.length?(r=!1,n=+t[0],e=+t[1],i):r?null:[n,e]},i.nodeSize=function(t){return arguments.length?(r=!0,n=+t[0],e=+t[1],i):r?[n,e]:null},i},t.treemap=function(){var t=Dd,n=!1,e=1,r=1,i=[0],o=hd,a=hd,u=hd,c=hd,f=hd;function s(t){return t.x0=t.y0=0,t.x1=e,t.y1=r,t.eachBefore(l),i=[0],n&&t.eachBefore(_d),t}function l(n){var e=i[n.depth],r=n.x0+e,s=n.y0+e,l=n.x1-e,h=n.y1-e;l=e-1){var s=u[n];return s.x0=i,s.y0=o,s.x1=a,void(s.y1=c)}var l=f[n],h=r/2+l,d=n+1,p=e-1;for(;d>>1;f[g]c-o){var _=r?(i*v+a*y)/r:a;t(n,d,y,i,o,_,c),t(d,e,v,_,o,a,c)}else{var b=r?(o*v+c*y)/r:c;t(n,d,y,i,o,a,b),t(d,e,v,i,b,a,c)}}(0,c,t.value,n,e,r,i)},t.treemapDice=bd,t.treemapResquarify=qd,t.treemapSlice=Cd,t.treemapSliceDice=function(t,n,e,r,i){(1&t.depth?Cd:bd)(t,n,e,r,i)},t.treemapSquarify=Dd,t.tsv=zu,t.tsvFormat=mu,t.tsvFormatBody=xu,t.tsvFormatRow=Mu,t.tsvFormatRows=wu,t.tsvFormatValue=Au,t.tsvParse=_u,t.tsvParseRows=bu,t.union=function(...t){const n=new Set;for(const e of t)for(const t of e)n.add(t);return n},t.utcDay=Eg,t.utcDays=kg,t.utcFriday=Rg,t.utcFridays=Lg,t.utcHour=Tg,t.utcHours=Sg,t.utcMillisecond=Yp,t.utcMilliseconds=Lp,t.utcMinute=Mg,t.utcMinutes=Ag,t.utcMonday=Pg,t.utcMondays=Ig,t.utcMonth=Hg,t.utcMonths=Xg,t.utcSaturday=Fg,t.utcSaturdays=jg,t.utcSecond=Zp,t.utcSeconds=Kp,t.utcSunday=Cg,t.utcSundays=Og,t.utcThursday=qg,t.utcThursdays=Yg,t.utcTickInterval=Zg,t.utcTicks=Wg,t.utcTuesday=zg,t.utcTuesdays=Ug,t.utcWednesday=Dg,t.utcWednesdays=Bg,t.utcWeek=Cg,t.utcWeeks=Og,t.utcYear=Gg,t.utcYears=Vg,t.variance=h,t.version="6.7.0",t.window=Wt,t.xml=Ru,t.zip=function(){return tt(arguments)},t.zoom=function(){var t,n,e,r=ox,i=ax,o=sx,a=cx,u=fx,c=[0,1/0],f=[[-1/0,-1/0],[1/0,1/0]],s=250,l=Dr,h=pt("start","zoom","end"),d=500,p=0,g=10;function y(t){t.property("__zoom",ux).on("wheel.zoom",M).on("mousedown.zoom",A).on("dblclick.zoom",T).filter(u).on("touchstart.zoom",S).on("touchmove.zoom",E).on("touchend.zoom touchcancel.zoom",k).style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function v(t,n){return(n=Math.max(c[0],Math.min(c[1],n)))===t.k?t:new tx(n,t.x,t.y)}function _(t,n,e){var r=n[0]-e[0]*t.k,i=n[1]-e[1]*t.k;return r===t.x&&i===t.y?t:new tx(t.k,r,i)}function b(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function m(t,n,e,r){t.on("start.zoom",(function(){x(this,arguments).event(r).start()})).on("interrupt.zoom end.zoom",(function(){x(this,arguments).event(r).end()})).tween("zoom",(function(){var t=this,o=arguments,a=x(t,o).event(r),u=i.apply(t,o),c=null==e?b(u):"function"==typeof e?e.apply(t,o):e,f=Math.max(u[1][0]-u[0][0],u[1][1]-u[0][1]),s=t.__zoom,h="function"==typeof n?n.apply(t,o):n,d=l(s.invert(c).concat(f/s.k),h.invert(c).concat(f/h.k));return function(t){if(1===t)t=h;else{var n=d(t),e=f/n[2];t=new tx(e,c[0]-n[0]*e,c[1]-n[1]*e)}a.zoom(null,t)}}))}function x(t,n,e){return!e&&t.__zooming||new w(t,n)}function w(t,n){this.that=t,this.args=n,this.active=0,this.sourceEvent=null,this.extent=i.apply(t,n),this.taps=0}function M(t,...n){if(r.apply(this,arguments)){var e=x(this,n).event(t),i=this.__zoom,u=Math.max(c[0],Math.min(c[1],i.k*Math.pow(2,a.apply(this,arguments)))),s=In(t);if(e.wheel)e.mouse[0][0]===s[0]&&e.mouse[0][1]===s[1]||(e.mouse[1]=i.invert(e.mouse[0]=s)),clearTimeout(e.wheel);else{if(i.k===u)return;e.mouse=[s,i.invert(s)],gi(this),e.start()}ix(t),e.wheel=setTimeout(l,150),e.zoom("mouse",o(_(v(i,u),e.mouse[0],e.mouse[1]),e.extent,f))}function l(){e.wheel=null,e.end()}}function A(t,...n){if(!e&&r.apply(this,arguments)){var i=x(this,n,!0).event(t),a=Dn(t.view).on("mousemove.zoom",h,!0).on("mouseup.zoom",d,!0),u=In(t,c),c=t.currentTarget,s=t.clientX,l=t.clientY;Yn(t.view),rx(t),i.mouse=[u,this.__zoom.invert(u)],gi(this),i.start()}function h(t){if(ix(t),!i.moved){var n=t.clientX-s,e=t.clientY-l;i.moved=n*n+e*e>p}i.event(t).zoom("mouse",o(_(i.that.__zoom,i.mouse[0]=In(t,c),i.mouse[1]),i.extent,f))}function d(t){a.on("mousemove.zoom mouseup.zoom",null),Ln(t.view,i.moved),ix(t),i.event(t).end()}}function T(t,...n){if(r.apply(this,arguments)){var e=this.__zoom,a=In(t.changedTouches?t.changedTouches[0]:t,this),u=e.invert(a),c=e.k*(t.shiftKey?.5:2),l=o(_(v(e,c),a,u),i.apply(this,n),f);ix(t),s>0?Dn(this).transition().duration(s).call(m,l,a,t):Dn(this).call(y.transform,l,a,t)}}function S(e,...i){if(r.apply(this,arguments)){var o,a,u,c,f=e.touches,s=f.length,l=x(this,i,e.changedTouches.length===s).event(e);for(rx(e),a=0;a Date: Sat, 22 Jun 2024 23:17:34 +0200 Subject: [PATCH 047/118] Run celerity map after rP1fa8c79b1ef86a9edda575b582b18fa24d60ea1f Summary: I obviously failed to run `./bin/celerity map` one more time before running `arc diff`. Sorry for that. Test Plan: Run `./bin/celerity map` and see differences. Reviewers: O1 Blessed Committers, 20after4, chris Reviewed By: O1 Blessed Committers, 20after4, chris Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25697 --- resources/celerity/map.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d384bdb2ac..c6a05974cb 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -396,7 +396,7 @@ return array( 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a', 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b', - 'rsrc/js/application/fact/Chart.js' => '0076c427', + 'rsrc/js/application/fact/Chart.js' => '351abd1c', 'rsrc/js/application/fact/ChartCurtainView.js' => '86954222', 'rsrc/js/application/fact/ChartFunctionLabel.js' => '81de1dab', 'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22', @@ -702,7 +702,7 @@ return array( 'javelin-behavior-user-menu' => '60cd9241', 'javelin-behavior-view-placeholder' => 'a9942052', 'javelin-behavior-workflow' => '9623adc1', - 'javelin-chart' => '0076c427', + 'javelin-chart' => '351abd1c', 'javelin-chart-curtain-view' => '86954222', 'javelin-chart-function-label' => '81de1dab', 'javelin-color' => '78f811c9', @@ -918,12 +918,6 @@ return array( 'unhandled-exception-css' => '9ecfc00d', ), 'requires' => array( - '0076c427' => array( - 'phui-chart-css', - 'd3', - 'javelin-chart-curtain-view', - 'javelin-chart-function-label', - ), '0116d3e8' => array( 'javelin-behavior', 'javelin-dom', @@ -1239,6 +1233,12 @@ return array( 'aphront-typeahead-control-css', 'phui-tag-view-css', ), + '351abd1c' => array( + 'phui-chart-css', + 'd3', + 'javelin-chart-curtain-view', + 'javelin-chart-function-label', + ), '3829a3cf' => array( 'javelin-behavior', 'javelin-uri', From ed9d212013cd0d8747f92bca4e7cb94900bccfd5 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 31 May 2024 16:40:37 +0200 Subject: [PATCH 048/118] Warn in comment field if task is closed as duplicate Summary: Display a placeholder text in the text comment field of a Maniphest task if the task status has been set to Duplicate. This makes it clearer to users (who may have not checked the task status at the top of the page) not to fragment conversations. Closes T15749 Test Plan: * Be logged in and go to a task which is closed as a duplicate and see the placeholder text in the field to add a comment. * Be logged in and go to tasks which are not closed as a duplicate and see no placeholder text in the field to add a comment. * Go to other places whose code calls a `PhabricatorApplicationTransactionCommentView` constructor and check that it still renders correctly, for example Ponder in http://phorge.localhost/Q1, Slowvote in http://phorge.localhost/V1, Differential in http://phorge.localhost/D1 Reviewers: O1 Blessed Committers, valerio.bozzolan, avivey Reviewed By: O1 Blessed Committers, valerio.bozzolan, avivey Subscribers: avivey, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15749 Differential Revision: https://we.phorge.it/D25546 --- .../maniphest/editor/ManiphestEditEngine.php | 8 ++++ .../controller/PholioMockViewController.php | 2 +- .../PonderQuestionViewController.php | 2 +- .../ponder/view/PonderAnswerView.php | 2 +- .../PhabricatorSlowvotePollController.php | 2 +- .../editengine/PhabricatorEditEngine.php | 15 ++++++- ...catorApplicationTransactionCommentView.php | 40 +++++++++++++++---- 7 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index 46877168e7..e11c977436 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -69,6 +69,14 @@ final class ManiphestEditEngine return pht('Set Sail for Adventure'); } + public function getCommentFieldPlaceholderText($object) { + if ($object->getStatus() === ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE) { + return pht('This task is closed as a duplicate. '. + 'Only comment if you think that this task is not a duplicate.'); + } + return ''; + } + protected function getObjectViewURI($object) { return '/'.$object->getMonogram(); } diff --git a/src/applications/pholio/controller/PholioMockViewController.php b/src/applications/pholio/controller/PholioMockViewController.php index f4695d120c..36a8ec8c81 100644 --- a/src/applications/pholio/controller/PholioMockViewController.php +++ b/src/applications/pholio/controller/PholioMockViewController.php @@ -223,7 +223,7 @@ final class PholioMockViewController extends PholioController { $form = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) - ->setObjectPHID($mock->getPHID()) + ->setObject($mock) ->setFormID($comment_form_id) ->setDraft($draft) ->setHeaderText($title) diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 2cd555204c..63fe4e3539 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -60,7 +60,7 @@ final class PonderQuestionViewController extends PonderController { $add_comment = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) - ->setObjectPHID($question->getPHID()) + ->setObject($question) ->setShowPreview(false) ->setAction($this->getApplicationURI("/question/comment/{$id}/")) ->setSubmitButtonName(pht('Comment')); diff --git a/src/applications/ponder/view/PonderAnswerView.php b/src/applications/ponder/view/PonderAnswerView.php index 474661381c..a16e7bfbd4 100644 --- a/src/applications/ponder/view/PonderAnswerView.php +++ b/src/applications/ponder/view/PonderAnswerView.php @@ -121,7 +121,7 @@ final class PonderAnswerView extends AphrontTagView { $comment_view = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) - ->setObjectPHID($answer->getPHID()) + ->setObject($answer) ->setShowPreview(false) ->setHeaderText(pht('Answer Comment')) ->setAction("/ponder/answer/comment/{$id}/") diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php index 7ed83fb13f..1844a5f03c 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php @@ -154,7 +154,7 @@ final class PhabricatorSlowvotePollController return id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) - ->setObjectPHID($poll->getPHID()) + ->setObject($poll) ->setDraft($draft) ->setHeaderText($add_comment_header) ->setAction($this->getApplicationURI('/comment/'.$poll->getID().'/')) diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index f70e172a99..a696fb6cf7 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -339,7 +339,17 @@ abstract class PhabricatorEditEngine return null; } - + /** + * Set default placeholder plain text in the comment textarea of the engine. + * To be overwritten by conditions defined in the child EditEngine class. + * + * @param object Object in which the comment textarea is displayed. + * @return string Placeholder text to display in the comment textarea. + * @task text + */ + public function getCommentFieldPlaceholderText($object) { + return ''; + } /** * Return a human-readable header describing what this engine is used to do, @@ -1664,10 +1674,11 @@ abstract class PhabricatorEditEngine $view = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) - ->setObjectPHID($object_phid) ->setHeaderText($header_text) ->setAction($comment_uri) ->setRequiresMFA($requires_mfa) + ->setObject($object) + ->setEditEngine($this) ->setSubmitButtonName($button_text); $draft = PhabricatorVersionedDraft::loadDraft( diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php index 2e469c5e0a..684c348914 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php @@ -15,11 +15,12 @@ final class PhabricatorApplicationTransactionCommentView private $draft; private $requestURI; private $showPreview = true; - private $objectPHID; + private $object; private $headerText; private $noPermission; private $fullWidth; private $infoView; + private $editEngine; private $editEngineLock; private $noBorder; private $requiresMFA; @@ -30,13 +31,19 @@ final class PhabricatorApplicationTransactionCommentView private $commentActionGroups = array(); private $transactionTimeline; - public function setObjectPHID($object_phid) { - $this->objectPHID = $object_phid; + /** + * Set object in which this comment textarea field is displayed + */ + public function setObject($object) { + $this->object = $object; return $this; } - public function getObjectPHID() { - return $this->objectPHID; + /** + * Get object in which this comment textarea is displayed + */ + public function getObject() { + return $this->object; } public function setShowPreview($show_preview) { @@ -150,6 +157,15 @@ final class PhabricatorApplicationTransactionCommentView return $this->noPermission; } + public function setEditEngine(PhabricatorEditEngine $edit_engine) { + $this->editEngine = $edit_engine; + return $this; + } + + public function getEditEngine() { + return $this->editEngine; + } + public function setEditEngineLock(PhabricatorEditEngineLock $lock) { $this->editEngineLock = $lock; return $this; @@ -295,6 +311,15 @@ final class PhabricatorApplicationTransactionCommentView private function renderCommentPanel() { $viewer = $this->getViewer(); + $engine = $this->getEditEngine(); + // In a few rare cases PhabricatorApplicationTransactionCommentView gets + // initiated in a View or Controller class. Don't crash in that case. + if ($engine) { + $placeholder_text = $engine + ->getCommentFieldPlaceholderText($this->getObject()); + } else { + $placeholder_text = ''; + } $remarkup_control = id(new PhabricatorRemarkupControl()) ->setViewer($viewer) @@ -302,6 +327,7 @@ final class PhabricatorApplicationTransactionCommentView ->addClass('phui-comment-fullwidth-control') ->addClass('phui-comment-textarea-control') ->setCanPin(true) + ->setPlaceholder($placeholder_text) ->setName('comment'); $draft_comment = ''; @@ -331,7 +357,7 @@ final class PhabricatorApplicationTransactionCommentView } $remarkup_control->setRemarkupMetadata($draft_metadata); - if (!$this->getObjectPHID()) { + if (!$this->getObject()->getPHID()) { throw new PhutilInvalidStateException('setObjectPHID', 'render'); } @@ -345,7 +371,7 @@ final class PhabricatorApplicationTransactionCommentView ->setFullWidth($this->fullWidth) ->setMetadata( array( - 'objectPHID' => $this->getObjectPHID(), + 'objectPHID' => $this->getObject()->getPHID(), )) ->setAction($this->getAction()) ->setID($this->getFormID()) From f8b8d8876d56ea7b0f6fd59ad4506d73819ebfec Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 16 May 2024 13:15:29 +0200 Subject: [PATCH 049/118] Fix call to non-existing newEmptyValue() in PhabricatorDashboardPortalProfileMenuEngine Summary: `PhabricatorDashboardPortalProfileMenuEngine` calls `$this->newEmptyValue()` but neither `PhabricatorDashboardPortalProfileMenuEngine` nor its parent `PhabricatorProfileMenuEngine` define a `newEmptyValue()` method. Thus use existing `newNoContentView()` in parent `PhabricatorProfileMenuEngine` (which according to its string output is supposed to cover this case). Test Plan: Go to a Portal with no content. Before applying the patch, Phorge shows the message defined in the parent class `PhabricatorProfileMenuEngine`: `No Content`; `No visible menu items can render content.` After applying this change, it correctly shows the message defined in `PhabricatorDashboardPortalProfileMenuEngine`: `No Portal Content`; `None of the visible menu items in this portal can render any content.` Reviewers: O1 Blessed Committers, speck Reviewed By: O1 Blessed Committers, speck Subscribers: speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25648 --- .../engine/PhabricatorDashboardPortalProfileMenuEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPortalProfileMenuEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPortalProfileMenuEngine.php index 9f8afc74dc..8265b306b1 100644 --- a/src/applications/dashboard/engine/PhabricatorDashboardPortalProfileMenuEngine.php +++ b/src/applications/dashboard/engine/PhabricatorDashboardPortalProfileMenuEngine.php @@ -41,7 +41,7 @@ final class PhabricatorDashboardPortalProfileMenuEngine pht('New Portal'), pht('Use "Edit Menu" to add menu items to this portal.')); } else { - return $this->newEmptyValue( + return $this->newNoContentView( pht('No Portal Content'), pht( 'None of the visible menu items in this portal can render any '. From 68762e820417d4c84c2edd3d46c2734a58c8c906 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Tue, 25 Jun 2024 09:27:54 +0200 Subject: [PATCH 050/118] Drydock and Almanac: add welcome pages Summary: Premising that I still consider Almanac, Drydock and Harbormaster as dark magic, and premising that all my knowledge about these components is here: https://en.wikibooks.org/wiki/Phabricator_Administrator%27s_Handbook/Continuous_integration (Above guide linked from [[Community Resources]]) Hoping to be useful, I've added some welcome pages, precious to newcomers, with the goal to lower a bit the learning barrier to these tricky components. ## Drydock Blueprints Screenshot of the new welcome page in {nav Drydock > Blueprints}: {F2243109} Since Drydock Blueprints are deeply dependent to {nav Almanac}, we added an extra blue button to see its homepage. So, you are then 2-clicks away from... ## Almanac Services Screenshot of the new welcome page in {nav Almanac > Services}: {F2243110} Since Almanac Services are deeply dependent to {nav Almanac > Devices}, we added an extra blue button to see them. So, you are then 1-click away from... ## Almanac Devices Screenshot of the new welcome page in {nav Almanac > Devices}: {F2243112} Since Almanac Devices are deeply dependent to {nav Almanac > Networks}, we added an extra blue button to see them. So, you are then 1-click away from... ## Almanac Networks Screenshot of the new welcome page in {nav Almanac Networks}: {F2243111} And, that's it. ## Notes Ref T15018 ---- After this change, at least 1 newcomer will love Phorge at the maximum level. and they will never again be able to use the proprietary GitHub. bwahaha Test Plan: Visit all the following pages, forcing the welcome "new user interface" mode: http://phorge.localhost/drydock/blueprint/?nux=1 http://phorge.localhost/almanac/service/?nux=1 http://phorge.localhost/almanac/device/?nux=1 http://phorge.localhost/almanac/network/?nux=1 Test all action buttons. Write a comment with "yesyes" or "bwahaha" or "chadyes" in production too. Reviewers: O1 Blessed Committers, aklapper Reviewed By: O1 Blessed Committers, aklapper Subscribers: aklapper, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15018 Differential Revision: https://we.phorge.it/D25675 --- .../query/AlmanacDeviceSearchEngine.php | 26 +++++++++++++++++ .../query/AlmanacNetworkSearchEngine.php | 21 ++++++++++++++ .../query/AlmanacServiceSearchEngine.php | 28 +++++++++++++++++++ .../query/DrydockBlueprintSearchEngine.php | 28 +++++++++++++++++++ 4 files changed, 103 insertions(+) diff --git a/src/applications/almanac/query/AlmanacDeviceSearchEngine.php b/src/applications/almanac/query/AlmanacDeviceSearchEngine.php index 86633ac870..b4f7836720 100644 --- a/src/applications/almanac/query/AlmanacDeviceSearchEngine.php +++ b/src/applications/almanac/query/AlmanacDeviceSearchEngine.php @@ -137,4 +137,30 @@ final class AlmanacDeviceSearchEngine return $result; } + protected function getNewUserBody() { + $see_network = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('See Networks')) + ->setHref('/almanac/network/'); + + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Device')) + ->setHref('/almanac/device/edit/') + ->setIcon('fa-plus') + ->setColor(PHUIButtonView::GREEN); + + $app_name = pht('Devices'); + $view = id(new PHUIBigInfoView()) + ->setIcon('fa-server') + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht( + 'Use Almanac devices to catalogue your build hosts '. + 'and their SSH ports your network, and more.')) + ->addAction($see_network) + ->addAction($create_button); + + return $view; + } } diff --git a/src/applications/almanac/query/AlmanacNetworkSearchEngine.php b/src/applications/almanac/query/AlmanacNetworkSearchEngine.php index 5938fd44c7..7bb8b85116 100644 --- a/src/applications/almanac/query/AlmanacNetworkSearchEngine.php +++ b/src/applications/almanac/query/AlmanacNetworkSearchEngine.php @@ -87,4 +87,25 @@ final class AlmanacNetworkSearchEngine return $result; } + + protected function getNewUserBody() { + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Network')) + ->setHref('/almanac/network/edit/') + ->setIcon('fa-plus') + ->setColor(PHUIButtonView::GREEN); + + $app_name = pht('Networks'); + $view = id(new PHUIBigInfoView()) + ->setIcon('fa-globe') + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht( + 'Use Almanac networks to catalogue private and public '. + 'computer networks.')) + ->addAction($create_button); + + return $view; + } } diff --git a/src/applications/almanac/query/AlmanacServiceSearchEngine.php b/src/applications/almanac/query/AlmanacServiceSearchEngine.php index f9786a87d2..46a9ebfbad 100644 --- a/src/applications/almanac/query/AlmanacServiceSearchEngine.php +++ b/src/applications/almanac/query/AlmanacServiceSearchEngine.php @@ -115,4 +115,32 @@ final class AlmanacServiceSearchEngine return $result; } + + protected function getNewUserBody() { + $see_devices = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('See Devices')) + ->setHref('/almanac/device/'); + + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Service')) + ->setHref('/almanac/service/edit/') + ->setIcon('fa-plus') + ->setColor(PHUIButtonView::GREEN); + + + $app_name = pht('Services'); + $view = id(new PHUIBigInfoView()) + ->setIcon('fa-plug') + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht( + 'Services describe pools of devices, and '. + 'they are available to Drydock for CI/CD, and more.')) + ->addAction($see_devices) + ->addAction($create_button); + + return $view; + } } diff --git a/src/applications/drydock/query/DrydockBlueprintSearchEngine.php b/src/applications/drydock/query/DrydockBlueprintSearchEngine.php index 244afcb986..f556f90146 100644 --- a/src/applications/drydock/query/DrydockBlueprintSearchEngine.php +++ b/src/applications/drydock/query/DrydockBlueprintSearchEngine.php @@ -138,4 +138,32 @@ final class DrydockBlueprintSearchEngine return $result; } + protected function getNewUserBody() { + $see_almanac_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('See Almanac services')) + ->setHref('/almanac/service/'); + + $create_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Create a Blueprint')) + ->setHref('/drydock/blueprint/edit/') + ->setIcon('fa-plus') + ->setColor(PHUIButtonView::GREEN); + + $app_name = pht('Blueprints'); + $view = id(new PHUIBigInfoView()) + ->setIcon('fa-map-o') + ->setTitle(pht('Welcome to %s', $app_name)) + ->setDescription( + pht( + 'Blueprints allow to lease fresh working copies of repositories, '. + 'on your Drydock devices, when needed by CI/CD workflows, and more. '. + 'Blueprints lease services defined in your Almanac.')) + ->addAction($see_almanac_button) + ->addAction($create_button); + + return $view; + } + } From 5a9cf81ae5922433e6ae9f7e6ab688560dbe2010 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Mon, 24 Jun 2024 15:14:27 +0200 Subject: [PATCH 051/118] Fix PHP 8.1 "strlen(null)" exceptions handling empty comment metadata in PhabricatorEditEngine Summary: `strlen()` was used in Phabricator to check if a generic value is a non-empty string. This behavior is deprecated since PHP 8.1. Phorge adopts `phutil_nonempty_string()` as a replacement. Note: this may highlight other absurd input values that might be worth correcting instead of just ignoring. If phutil_nonempty_string() throws an exception in your instance, report it to Phorge to evaluate and fix that specific corner case. ``` ERROR 8192: strlen(): Passing null to parameter #1 ($string) of type string is deprecated at [/var/www/html/phorge/phorge/src/applications/transactions/editengine/PhabricatorEditEngine.php:1923] ``` ``` ERROR 8192: strlen(): Passing null to parameter #1 ($string) of type string is deprecated at [/var/www/html/phorge/phorge/src/applications/transactions/editengine/PhabricatorEditEngine.php:2023] ``` Closes T15864 Test Plan: See steps in https://we.phorge.it/T15864 Reviewers: O1 Blessed Committers, speck Reviewed By: O1 Blessed Committers, speck Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15864 Differential Revision: https://we.phorge.it/D25699 --- .../transactions/editengine/PhabricatorEditEngine.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index a696fb6cf7..d63a66e8f3 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -1920,7 +1920,7 @@ abstract class PhabricatorEditEngine $comment_text = $request->getStr('comment'); $comment_metadata = $request->getStr('comment_metadata'); - if (strlen($comment_metadata)) { + if (phutil_nonempty_string($comment_metadata)) { $comment_metadata = phutil_json_decode($comment_metadata); } @@ -2020,7 +2020,7 @@ abstract class PhabricatorEditEngine $xactions[] = $xaction; } - if (strlen($comment_text) || !$xactions) { + if (phutil_nonempty_string($comment_text) || !$xactions) { $xactions[] = id(clone $template) ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) ->setMetadataValue('remarkup.control', $comment_metadata) From f52bc073216e9b77526561aa676b80c2c0d294de Mon Sep 17 00:00:00 2001 From: Merula Turdus Date: Sun, 23 Jun 2024 23:19:01 +0200 Subject: [PATCH 052/118] Make the Remarkup Help Page Extensible Summary: The Remarkup Help page is currently pointing to a HTML page on we.phorge.it. Because of this it can not be customized/extended by custom extensions. This diff allows you to implement a `RemarkupSyntaxDocumentationProvider` interface to your custom class, which will implement a getDocumentation method. This getDocumentation should return the custom help text in Remarkup format. This diff also contains some extra help for the cowsay and figlet interpreters. The local URLs for the local references are: * /reference/remarkup/ * /reference/cowsay/ * /reference/figlet/ Closes T15401 Test Plan: Open the Phriction wiki editor and click on the Book (Remarkup Reference) icon in the toolbar and validate URL afterwards Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15401 Differential Revision: https://we.phorge.it/D25698 --- src/__phutil_library_map__.php | 21 +- .../src/application/ReferenceApplication.php | 27 + .../controller/CowsayReferenceController.php | 291 +++++++ .../controller/FigletReferenceController.php | 39 + .../src/controller/ReferenceController.php | 75 ++ .../RemarkupReferenceController.php | 762 ++++++++++++++++++ .../RemarkupSyntaxDocumentationProvider.php | 5 + ...bricatorRemarkupCowsayBlockInterpreter.php | 21 +- ...bricatorRemarkupFigletBlockInterpreter.php | 18 +- .../control/PhabricatorRemarkupControl.php | 2 +- 10 files changed, 1256 insertions(+), 5 deletions(-) create mode 100644 src/applications/reference/src/application/ReferenceApplication.php create mode 100644 src/applications/reference/src/controller/CowsayReferenceController.php create mode 100644 src/applications/reference/src/controller/FigletReferenceController.php create mode 100644 src/applications/reference/src/controller/ReferenceController.php create mode 100644 src/applications/reference/src/controller/RemarkupReferenceController.php create mode 100644 src/infrastructure/markup/interface/RemarkupSyntaxDocumentationProvider.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index aa0964c85b..257c69d1a4 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -436,6 +436,7 @@ phutil_register_library_map(array( 'ConpherenceViewController' => 'applications/conpherence/controller/ConpherenceViewController.php', 'CountdownEditConduitAPIMethod' => 'applications/countdown/conduit/CountdownEditConduitAPIMethod.php', 'CountdownSearchConduitAPIMethod' => 'applications/countdown/conduit/CountdownSearchConduitAPIMethod.php', + 'CowsayReferenceController' => 'applications/reference/src/controller/CowsayReferenceController.php', 'DarkConsoleController' => 'applications/console/controller/DarkConsoleController.php', 'DarkConsoleCore' => 'applications/console/core/DarkConsoleCore.php', 'DarkConsoleDataController' => 'applications/console/controller/DarkConsoleDataController.php', @@ -1304,6 +1305,7 @@ phutil_register_library_map(array( 'FeedStoryNotificationGarbageCollector' => 'applications/notification/garbagecollector/FeedStoryNotificationGarbageCollector.php', 'FerretConfigurableSearchFunction' => 'applications/search/ferret/function/FerretConfigurableSearchFunction.php', 'FerretSearchFunction' => 'applications/search/ferret/function/FerretSearchFunction.php', + 'FigletReferenceController' => 'applications/reference/src/controller/FigletReferenceController.php', 'FileAllocateConduitAPIMethod' => 'applications/files/conduit/FileAllocateConduitAPIMethod.php', 'FileConduitAPIMethod' => 'applications/files/conduit/FileConduitAPIMethod.php', 'FileCreateMailReceiver' => 'applications/files/mail/FileCreateMailReceiver.php', @@ -5878,7 +5880,11 @@ phutil_register_library_map(array( 'ProjectSearchConduitAPIMethod' => 'applications/project/conduit/ProjectSearchConduitAPIMethod.php', 'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php', 'QueryFuture' => 'infrastructure/storage/future/QueryFuture.php', + 'ReferenceApplication' => 'applications/reference/src/application/ReferenceApplication.php', + 'ReferenceController' => 'applications/reference/src/controller/ReferenceController.php', 'RemarkupProcessConduitAPIMethod' => 'applications/remarkup/conduit/RemarkupProcessConduitAPIMethod.php', + 'RemarkupReferenceController' => 'applications/reference/src/controller/RemarkupReferenceController.php', + 'RemarkupSyntaxDocumentationProvider' => 'infrastructure/markup/interface/RemarkupSyntaxDocumentationProvider.php', 'RemarkupValue' => 'applications/remarkup/RemarkupValue.php', 'RepositoryConduitAPIMethod' => 'applications/repository/conduit/RepositoryConduitAPIMethod.php', 'RepositoryQueryConduitAPIMethod' => 'applications/repository/conduit/RepositoryQueryConduitAPIMethod.php', @@ -6444,6 +6450,7 @@ phutil_register_library_map(array( 'ConpherenceViewController' => 'ConpherenceController', 'CountdownEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'CountdownSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'CowsayReferenceController' => 'ReferenceController', 'DarkConsoleController' => 'PhabricatorController', 'DarkConsoleCore' => 'Phobject', 'DarkConsoleDataController' => 'PhabricatorController', @@ -7404,6 +7411,7 @@ phutil_register_library_map(array( 'FeedStoryNotificationGarbageCollector' => 'PhabricatorGarbageCollector', 'FerretConfigurableSearchFunction' => 'FerretSearchFunction', 'FerretSearchFunction' => 'Phobject', + 'FigletReferenceController' => 'ReferenceController', 'FileAllocateConduitAPIMethod' => 'FileConduitAPIMethod', 'FileConduitAPIMethod' => 'ConduitAPIMethod', 'FileCreateMailReceiver' => 'PhabricatorApplicationMailReceiver', @@ -11211,12 +11219,18 @@ phutil_register_library_map(array( 'PhabricatorRegistrationProfile' => 'Phobject', 'PhabricatorRemarkupCachePurger' => 'PhabricatorCachePurger', 'PhabricatorRemarkupControl' => 'AphrontFormTextAreaControl', - 'PhabricatorRemarkupCowsayBlockInterpreter' => 'PhutilRemarkupBlockInterpreter', + 'PhabricatorRemarkupCowsayBlockInterpreter' => array( + 'PhutilRemarkupBlockInterpreter', + 'RemarkupSyntaxDocumentationProvider', + ), 'PhabricatorRemarkupCustomBlockRule' => 'PhutilRemarkupBlockRule', 'PhabricatorRemarkupCustomInlineRule' => 'PhutilRemarkupRule', 'PhabricatorRemarkupDocumentEngine' => 'PhabricatorDocumentEngine', 'PhabricatorRemarkupEditField' => 'PhabricatorEditField', - 'PhabricatorRemarkupFigletBlockInterpreter' => 'PhutilRemarkupBlockInterpreter', + 'PhabricatorRemarkupFigletBlockInterpreter' => array( + 'PhutilRemarkupBlockInterpreter', + 'RemarkupSyntaxDocumentationProvider', + ), 'PhabricatorRemarkupHyperlinkEngineExtension' => 'PhutilRemarkupHyperlinkEngineExtension', 'PhabricatorRemarkupUIExample' => 'PhabricatorUIExample', 'PhabricatorRemoveEmailUserLogType' => 'PhabricatorUserLogType', @@ -12793,7 +12807,10 @@ phutil_register_library_map(array( 'ProjectSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'QueryFormattingTestCase' => 'PhabricatorTestCase', 'QueryFuture' => 'Future', + 'ReferenceApplication' => 'PhabricatorApplication', + 'ReferenceController' => 'PhabricatorController', 'RemarkupProcessConduitAPIMethod' => 'ConduitAPIMethod', + 'RemarkupReferenceController' => 'ReferenceController', 'RemarkupValue' => 'Phobject', 'RepositoryConduitAPIMethod' => 'ConduitAPIMethod', 'RepositoryQueryConduitAPIMethod' => 'RepositoryConduitAPIMethod', diff --git a/src/applications/reference/src/application/ReferenceApplication.php b/src/applications/reference/src/application/ReferenceApplication.php new file mode 100644 index 0000000000..5c92d2131c --- /dev/null +++ b/src/applications/reference/src/application/ReferenceApplication.php @@ -0,0 +1,27 @@ + array( + 'remarkup/' => 'RemarkupReferenceController', + 'cowsay/' => 'CowsayReferenceController', + 'figlet/' => 'FigletReferenceController', + ), + ); + } + +} diff --git a/src/applications/reference/src/controller/CowsayReferenceController.php b/src/applications/reference/src/controller/CowsayReferenceController.php new file mode 100644 index 0000000000..140f7fadfa --- /dev/null +++ b/src/applications/reference/src/controller/CowsayReferenceController.php @@ -0,0 +1,291 @@ +getJokes(); + shuffle($jokes); + + $j = 0; + + foreach ($directories as $directory) { + foreach (Filesystem::listDirectory($directory, false) as $cow_file) { + $matches = null; + if (!preg_match('/^(.*)\.cow$/', $cow_file, $matches)) { + continue; + } + $cow_name = $matches[1]; + $cow_name = phutil_utf8_strtolower($cow_name); + + $joke = $jokes[$j % count($jokes)]; + + $content .= "\n=== ".$cow_name; + $content .= "\n```"; + $content .= "\ncowsay(cow='".$cow_name."'){{{{$joke}}}}"; + $content .= "\n```"; + $content .= "\ncowsay(cow='".$cow_name."'){{{{$joke}}}}"; + $content .= "\n"; + + $j++; + } + } + + $content .= "\n"; + $content .= '== Parameters'; + + $joke = $jokes[$j % count($jokes)]; + $j++; + + $content .= "\n=== eyes"; + $content .= "\n```"; + $content .= "\ncowsay(cow='cow', eyes='-x'){{{{$joke}}}}"; + $content .= "\n```"; + $content .= "\ncowsay(cow='cow', eyes='-x'){{{{$joke}}}}"; + $content .= "\n"; + + $joke = $jokes[$j % count($jokes)]; + $j++; + + $content .= "\n=== think"; + $content .= "\n```"; + $content .= "\ncowsay(cow='cow', think=1){{{{$joke}}}}"; + $content .= "\n```"; + $content .= "\ncowsay(cow='cow', think=1){{{{$joke}}}}"; + $content .= "\n"; + + $joke = $jokes[$j % count($jokes)]; + $j++; + + $content .= "\n=== tongue"; + $content .= "\n```"; + $content .= "\ncowsay(cow='cow', tongue=U){{{{$joke}}}}"; + $content .= "\n```"; + $content .= "\ncowsay(cow='cow', tongue=U){{{{$joke}}}}"; + $content .= "\n"; + + return $content; + } +} diff --git a/src/applications/reference/src/controller/FigletReferenceController.php b/src/applications/reference/src/controller/FigletReferenceController.php new file mode 100644 index 0000000000..9913273d79 --- /dev/null +++ b/src/applications/reference/src/controller/FigletReferenceController.php @@ -0,0 +1,39 @@ +getRequest(); + $viewer = $request->getViewer(); + + $content = $this->getContent(); + + $remarkup_view = id(new PHUIRemarkupView($viewer, $content)) + ->setContextObject($this) + ->setRemarkupOption('uri.same-window', true) + ->setRemarkupOption(PHUIRemarkupView::OPTION_GENERATE_TOC, true) + ->setRemarkupOption(PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS, false) + ->setGenerateTableOfContents(true); + + $content = $remarkup_view->render(); + + $toc = $remarkup_view->getTableOfContents(); + $toc = $this->formatToc($toc); + + $document = id(new PHUIDocumentView()) + ->addClass('reference-documentation') + ->setToc($toc) + ->appendChild($remarkup_view); + + $crumbs = $this->buildApplicationCrumbs(); + + return $this->newPage() + ->setTitle($this->getTitle()) + ->setCrumbs($crumbs) + ->appendChild($document); + } + + protected function buildApplicationCrumbs() { + $crumbs = array(); + + $application = $this->getCurrentApplication(); + if ($application) { + $icon = $application->getIcon(); + if (!$icon) { + $icon = 'fa-puzzle'; + } + + $crumbs[] = id(new PHUICrumbView()) + ->setName($this->getTitle()) + ->setIcon($icon); + } + + $view = new PHUICrumbsView(); + foreach ($crumbs as $crumb) { + $view->addCrumb($crumb); + } + + return $view; + } + + protected function formatToc($toc) { + + if ($toc) { + $toc = phutil_tag_div('phui-document-toc-content', array( + phutil_tag_div( + 'phui-document-toc-header', + pht('Contents')), + $toc, + )); + } + + return $toc; + } +} diff --git a/src/applications/reference/src/controller/RemarkupReferenceController.php b/src/applications/reference/src/controller/RemarkupReferenceController.php new file mode 100644 index 0000000000..939845d657 --- /dev/null +++ b/src/applications/reference/src/controller/RemarkupReferenceController.php @@ -0,0 +1,762 @@ + Quoted Text + + Use `- ` or `* ` for bulleted lists, and `# ` for numbered lists. + Use ``` or indent two spaces for code. + Use %%% for a literal block. + Use | ... | ... for tables. + += Basic Styling = + +Format **basic text styles** like this: + + **bold text** + //italic text// + `monospaced text` + ##monospaced text## + ~~deleted text~~ + __underlined text__ + !!highlighted text!! + +Those produce **bold text**, //italic text//, `monospaced text`, ##monospaced +text##, ~~deleted text~~, __underlined text__, and !!highlighted text!! +respectively. + += Layout = + +Make **headers** like this: + + = Large Header = + + == Smaller Header == + + ===== Very Small Header ===== + + Alternate Large Header + ====================== + + Alternate Smaller Header + ------------------------ + +You can optionally omit the trailing `=` signs -- that is, these are the same: + + == Smaller Header == + + == Smaller Header + +This produces headers like the ones in this document. Make sure you have an +empty line before and after the header. + +Lists +===== + +Make **lists** by beginning each item with a `-` or a `*`: + + lang=text + - milk + - eggs + - bread + + * duck + * duck + * goose + +This produces a list like this: + + - milk + - eggs + - bread + +(Note that you need to put a space after the `-` or `*`.) + +You can make numbered lists with a `#` instead of `-` or `*`: + + # Articuno + # Zapdos + # Moltres + +Numbered lists can also be started with `1.` or `1)`. If you use a number other +than `1`, the list will start at that number instead. For example, this: + +``` + 200) OK + 201) Created + 202) Accepted +``` + +...produces this: + + 200) OK + 201) Created + 202) Accepted + +You can also nest lists: + +```- Body + - Head + - Arm + - Elbow + - Hand + # Thumb + # Index + # Middle + # Ring + # Pinkie + - Leg + - Knee + - Foot``` + +...which produces: + + - Body + - Head + - Arm + - Elbow + - Hand + # Thumb + # Index + # Middle + # Ring + # Pinkie + - Leg + - Knee + - Foot + +If you prefer, you can indent lists using multiple characters to show indent +depth, like this: + +```- Tree +-- Branch +--- Twig``` + +As expected, this produces: + +- Tree +-- Branch +--- Twig + +You can add checkboxes to items by prefacing them with `[ ]` or `[X]`, like +this: + +``` + - [X] Preheat oven to 450 degrees. + - [ ] Zest 35 lemons. +``` + +When rendered, this produces: + + - [X] Preheat oven to 450 degrees. + - [ ] Zest 35 lemons. + +Code Blocks +=========== + +Make **code blocks** by indenting two spaces: + + f(x, y); + +You can also use three backticks to enclose the code block: + + ```f(x, y); + g(f);``` + +You can specify a language for syntax highlighting with `lang=xxx`: + + lang=text + lang=html + ... + +When using fenced code blocks (triple backticks) you can simply append the +language right after the backticks, like this: ##```html## + +This will highlight the block using a highlighter for that language, if one is +available (in most cases, this means you need to configure Pygments): + + lang=html + ... + +You can also use a `COUNTEREXAMPLE` header to show that a block of code is +bad and shouldn't be copied: + + lang=text + COUNTEREXAMPLE + function f() { + global \$\$variable_variable; + } + +This produces a block like this: + + COUNTEREXAMPLE + function f() { + global \$\$variable_variable; + } + +You can use `lines=N` to limit the vertical size of a chunk of code, and +`name=some_name.ext` to give it a name. For example, this: + + lang=text + lang=html, name=example.html, lines=12, counterexample + ... + +...produces this: + + lang=html, name=example.html, lines=12, counterexample +

Apple

+

Apricot

+

Avocado

+

Banana

+

Bilberry

+

Blackberry

+

Blackcurrant

+

Blueberry

+

Currant

+

Cherry

+

Cherimoya

+

Clementine

+

Date

+

Damson

+

Durian

+

Eggplant

+

Elderberry

+

Feijoa

+

Gooseberry

+

Grape

+

Grapefruit

+

Guava

+

Huckleberry

+

Jackfruit

+

Jambul

+

Kiwi fruit

+

Kumquat

+

Legume

+

Lemon

+

Lime

+

Lychee

+

Mandarine

+

Mango

+

Mangostine

+

Melon

+ + +You can use the `NOTE:`, `WARNING:` or `IMPORTANT:` elements to call attention +to an important idea. + +For example, write this: + +``` +NOTE: Best practices in proton pack operation include not crossing the streams. +``` + +...to produce this: + +NOTE: Best practices in proton pack operation include not crossing the streams. + +Using `WARNING:` or `IMPORTANT:` at the beginning of the line changes the +color of the callout: + +WARNING: Crossing the streams can result in total protonic reversal! + +IMPORTANT: Don't cross the streams! + +In addition, you can use `(NOTE)`, `(WARNING)`, or `(IMPORTANT)` to get the +same effect but without `(NOTE)`, `(WARNING)`, or `(IMPORTANT)` appearing in +the rendered result. For example, this callout uses `(NOTE)`: + +(NOTE) Dr. Egon Spengler is the best resource for additional proton pack + questions. + + +Dividers +======== + +You can divide sections by putting three or more dashes on a line by +themselves. This creates a divider or horizontal rule similar to an `
` +tag, like this one: + +--- + +The dashes need to appear on their own line and be separated from other +content. For example, like this: + +``` +This section will be visually separated. + +--- + +On an entirely different topic, ... +``` + + += Linking URIs = + +URIs are automatically linked: http://phorge.it/ + +If you have a URI with problematic characters in it, like +"`http://comma.org/,`", you can surround it with angle brackets: + + + +This will force the parser to consume the whole URI: + +You can also use create named links, where you choose the displayed text. These +work within Phorge or on the internet at large: + + [[/herald/transcript/ | Herald Transcripts]] + [[http://www.boring-legal-documents.com/ | exciting legal documents]] + +Markdown-style links are also supported: + + [Toil](http://www.trouble.com) + += Linking to Objects = + +You can link to Phorge objects, such as Differential revisions, Diffusion +commits and Maniphest tasks, by mentioning the name of an object: + + D123 # Link to Differential revision D123 + rX123 # Link to SVN commit 123 from the "X" repository + rXaf3192cd5 # Link to Git commit "af3192cd5..." from the "X" repository. + # You must specify at least 7 characters of the hash. + T123 # Link to Maniphest task T123 + +You can also link directly to a comment in Maniphest and Differential (these +can be found on the date stamp of any transaction/comment): + + T123#412 # Link to comment id #412 of task T123 + +See the Phorge configuration setting `remarkup.ignored-object-names` to +modify this behavior. + += Embedding Objects + +You can also generate full-name references to some objects by using braces: + + {D123} # Link to Differential revision D123 with the full name + {T123} # Link to Maniphest task T123 with the full name + +These references will also show when an object changes state (for instance, a +task or revision is closed). Some types of objects support rich embedding. + +== Linking to Project Tags + +Projects can be linked to with the use of a hashtag `#`. This works by default +using the name of the Project (lowercase, underscored). Additionally you +can set multiple additional hashtags by editing the Project details. + + #qa, #quality_assurance + +== Embedding Mocks (Pholio) + +You can embed a Pholio mock by using braces to refer to it: + + {M123} + +By default the first four images from the mock set are displayed. This behavior +can be overridden with the **image** option. With the **image** option you can +provide one or more image IDs to display. + +You can set the image (or images) to display like this: + + {M123, image=12345} + {M123, image=12345 & 6789} + +== Embedding Pastes + +You can embed a Paste using braces: + + {P123} + +You can adjust the embed height with the `lines` option: + + {P123, lines=15} + +You can highlight specific lines with the `highlight` option: + + {P123, highlight=15} + {P123, highlight="23-25, 31"} + +== Embedding Images + +You can embed an image or other file by using braces to refer to it: + + {F123} + +In most interfaces, you can drag-and-drop an image from your computer into the +text area to upload and reference it. + +Some browsers (e.g. Chrome) support uploading an image data just by pasting them +from clipboard into the text area. + +You can set file display options like this: + + {F123, layout=left, float, size=full, alt="a duckling"} + +Valid options for all files are: + + - **layout** left (default), center, right, inline, link (render a link + instead of a thumbnail for images) + - **name** with `layout=link` or for non-images, use this name for the link + text + - **alt** Provide alternate text for assistive technologies. + +Image files support these options: + + - **float** If layout is set to left or right, the image will be floated so + text wraps around it. + - **size** thumb (default), full + - **width** Scale image to a specific width. + - **height** Scale image to a specific height. + +Audio and video files support these options: + + - **media**: Specify the media type as `audio` or `video`. This allows you + to disambiguate how file format which may contain either audio or video + should be rendered. + - **loop**: Loop this media. + - **autoplay**: Automatically begin playing this media. + +== Embedding Countdowns + +You can embed a countdown by using braces: + + {C123} + += Quoting Text = + +To quote text, preface it with an `>`: + + > This is quoted text. + +This appears like this: + +> This is quoted text. + += Embedding Media = + +If you set a configuration flag, you can embed media directly in text: + + - **remarkup.enable-embedded-youtube**: allows you to paste in YouTube videos + and have them render inline. + +This option is disabled by default because it has security and/or +silliness implications. Carefully read the description before enabling it. + += Image Macros = + +You can upload image macros (More Stuff -> Macro) which will replace text +strings with the image you specify. For instance, you could upload an image of a +dancing banana to create a macro named "peanutbutterjellytime", and then any +time you type that string on a separate line it will be replaced with the image +of a dancing banana. + += Memes = + +You can also use image macros in the context of memes. For example, if you +have an image macro named `grumpy`, you can create a meme by doing the +following: + + {meme, src = grumpy, above = toptextgoeshere, below = bottomtextgoeshere} + +By default, the font used to create the text for the meme is `tuffy.ttf`. For +the more authentic feel of `impact.ttf`, you simply have to place the Impact +TrueType font in the Phorge subfolder `/resources/font/`. If Remarkup +detects the presence of `impact.ttf`, it will automatically use it. + += Mentioning Users = + +In Differential and Maniphest, you can mention another user by writing: + + @username + +When you submit your comment, this will add them as a CC on the revision or task +if they aren't already CC'd. + +Icons +===== + +You can add icons to comments using the `{icon ...}` syntax. For example: + + {icon camera} + +This renders: {icon camera} + +You can select a color for icons: + + {icon camera color=blue} + +This renders: {icon camera color=blue} + +For a list of available icons and colors, check the UIExamples application. +(The icons are sourced from +[[ https://fontawesome.com/v4.7.0/icons/ | FontAwesome ]], so you can also +browse the collection there.) + +You can add `spin` to make the icon spin: + + {icon cog spin} + +This renders: {icon cog spin} + + += Phriction Documents = + +You can link to Phriction documents with a name or path: + + Make sure you sign and date your [[legal/Letter of Marque and Reprisal]]! + +By default, the link will render with the document title as the link name. +With a pipe (`|`), you can retitle the link. Use this to mislead your +opponents: + + Check out these [[legal/boring_documents/ | exciting legal documents]]! + +Links to pages which do not exist are shown in red. Links to pages which exist +but which the viewer does not have permission to see are shown with a lock +icon, and the link will not disclose the page title. + +If you begin a link path with `./` or `../`, the remainder of the path will be +evaluated relative to the current wiki page. For example, if you are writing +content for the document `fruit/` a link to `[[./guava]]` is the same as a link +to `[[fruit/guava]]` from elsewhere. + +Relative links may use `../` to transverse up the document tree. From the +`produce/vegetables/` page, you can use `[[../fruit/guava]]` to link to the +`produce/fruit/guava` page. + +Relative links do not work when used outside of wiki pages. For example, +you can't use a relative link in a comment on a task, because there is no +reasonable place for the link to start resolving from. + +When documents are moved, relative links are not automatically updated: they +are preserved as currently written. After moving a document, you may need to +review and adjust any relative links it contains. + + += Literal Blocks = + +To place text in a literal block use `%%%`: + + %%%Text that won't be processed by remarkup + [[http://www.example.com | example]] + %%% + +Remarkup will not process the text inside of literal blocks (other than to +escape HTML and preserve line breaks). + += Tables = + +Remarkup supports simple table syntax. For example, this: + +``` +| Fruit | Color | Price | Peel? +| ----- | ----- | ----- | ----- +| Apple | red | `\$0.93` | no +| Banana | yellow | `\$0.19` | **YES** +``` + +...produces this: + +| Fruit | Color | Price | Peel? +| ----- | ----- | ----- | ----- +| Apple | red | `\$0.93` | no +| Banana | yellow | `\$0.19` | **YES** + +Remarkup also supports a simplified HTML table syntax. For example, this: + +``` + + + + + + + + + + + + + + + + + + + +
FruitColorPricePeel?
Applered`\$0.93`no
Bananayellow`\$0.19`**YES**
+``` + +...produces this: + + + + + + + + + + + + + + + + + + + + +
FruitColorPricePeel?
Applered`\$0.93`no
Bananayellow`\$0.19`**YES**
+ +Some general notes about this syntax: + + - your tags must all be properly balanced; + - your tags must NOT include attributes (`` is OK, `` is + not); + - you can use other Remarkup rules (like **bold**, //italics//, etc.) inside + table cells. + +Navigation Sequences +==================== + +You can use `{nav ...}` to render a stylized navigation sequence when helping +someone to locate something. This can be useful when writing documentation. +For example, you could give someone directions to purchase lemons: + +{nav icon=home, name=Home > +Grocery Store > +Produce Section > +icon=lemon-o, name=Lemons} + +To render this example, use this markup: + +``` +{nav icon=home, name=Home > +Grocery Store > +Produce Section > +icon=lemon-o, name=Lemons} +``` + +In general: + + - Separate sections with `>`. + - Each section can just have a name to add an element to the navigation + sequence, or a list of key-value pairs. + - Supported keys are `icon`, `name`, `type` and `href`. + - The `type` option can be set to `instructions` to indicate that an element + is asking the user to make a choice or follow specific instructions. + +Keystrokes +========== + +You can use `{key ...}` to render a stylized keystroke. For example, this: + +``` +Press {key M} to view the starmap. +``` + +...renders this: + +> Press {key M} to view the starmap. + +You can also render sequences with modifier keys. This: + +``` +Use {key command option shift 3} to take a screenshot. +Press {key down down-right right LP} to activate the hadoken technique. +``` + +...renders this: + +> Use {key command option shift 3} to take a screenshot. +> Press {key down down-right right LP} to activate the hadoken technique. + + +Anchors +======== + +You can use `{anchor #xyz}` to create a document anchor and later link to +it directly with `#xyz` in the URI. + +Headers also automatically create named anchors. + +If you navigate to `#xyz` in your browser location bar, the page will scroll +to the first anchor with "xyz" as a prefix of the anchor name. + + += Fullscreen Mode = + +Remarkup editors provide a fullscreen composition mode. This can make it easier +to edit large blocks of text, or improve focus by removing distractions. You can +exit **Fullscreen** mode by clicking the button again or by pressing escape. +EOTEXT; + + $remarkup_syntax_documentation_providers = id(new PhutilClassMapQuery()) + ->setAncestorClass('RemarkupSyntaxDocumentationProvider') + ->execute(); + + // add custom Remarkup help + $content .= "\r\n\r\n"; + foreach ($remarkup_syntax_documentation_providers as $doc_provider) { + $content .= $doc_provider->getDocumentation()."\r\n\r\n"; + } + + return $content; + } +} diff --git a/src/infrastructure/markup/interface/RemarkupSyntaxDocumentationProvider.php b/src/infrastructure/markup/interface/RemarkupSyntaxDocumentationProvider.php new file mode 100644 index 0000000000..1a0e849f05 --- /dev/null +++ b/src/infrastructure/markup/interface/RemarkupSyntaxDocumentationProvider.php @@ -0,0 +1,5 @@ + pht('Help'), 'align' => 'right', - 'href' => PhabricatorEnv::getDoclink('Remarkup Reference'), + 'href' => '/reference/remarkup/', ); $mode_actions = array(); From 8825a09f676aaf316fe97d38eb2db41edfa91e2e Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Wed, 26 Jun 2024 23:00:08 +0200 Subject: [PATCH 053/118] Fix legacy crash when visiting Motivator backend as anon Summary: Now anonymous people can visit this page: http://phorge.localhost/home/menu/new/custom/motivator/ And you do not see anymore this crash: Undefined variable $custom_phid phorge applications/search/engine/PhabricatorProfileMenuEngine.php : 61 PhutilErrorHandler::handleError() phorge applications/search/engine/PhabricatorProfileMenuEngine.php : 916 PhabricatorProfileMenuEngine::getEditModeCustomPHID() phorge applications/search/engine/PhabricatorProfileMenuEngine.php : 269 PhabricatorProfileMenuEngine::buildItemNewContent() phorge applications/home/controller/PhabricatorHomeMenuItemController.php : 39 PhabricatorProfileMenuEngine::buildResponse() phorge aphront/configuration/AphrontApplicationConfiguration.php : 284 PhabricatorHomeMenuItemController::handleRequest() phorge aphront/configuration/AphrontApplicationConfiguration.php : 203 AphrontApplicationConfiguration::processRequest() Closes T15867 Test Plan: Visit the mentioned page as logged-in. It still works. Visit the mentioned page as logged-out. No crash anymore. You are redirected to the login page. Reviewers: O1 Blessed Committers, aklapper Reviewed By: O1 Blessed Committers, aklapper Subscribers: aklapper, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15867 Differential Revision: https://we.phorge.it/D25700 --- src/applications/search/engine/PhabricatorProfileMenuEngine.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/search/engine/PhabricatorProfileMenuEngine.php b/src/applications/search/engine/PhabricatorProfileMenuEngine.php index bfa3d11ee5..89cb6cb40a 100644 --- a/src/applications/search/engine/PhabricatorProfileMenuEngine.php +++ b/src/applications/search/engine/PhabricatorProfileMenuEngine.php @@ -54,6 +54,7 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { $custom_phid = $this->getCustomPHID(); break; case self::MODE_GLOBAL: + default: $custom_phid = null; break; } From 290f3d63c5d9c1d1a905164836864c8e29e86bf1 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 27 Jun 2024 09:11:54 +0200 Subject: [PATCH 054/118] Do not link "here" but use a descriptive link text Summary: See https://www.w3.org/TR/WCAG20-TECHS/F84.html and https://www.w3.org/QA/Tips/noClickHere Test Plan: Click click click. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25702 --- .../interpreter/PhabricatorRemarkupCowsayBlockInterpreter.php | 2 +- .../interpreter/PhabricatorRemarkupFigletBlockInterpreter.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/infrastructure/markup/interpreter/PhabricatorRemarkupCowsayBlockInterpreter.php b/src/infrastructure/markup/interpreter/PhabricatorRemarkupCowsayBlockInterpreter.php index 0f4b9818da..c537ded05b 100644 --- a/src/infrastructure/markup/interpreter/PhabricatorRemarkupCowsayBlockInterpreter.php +++ b/src/infrastructure/markup/interpreter/PhabricatorRemarkupCowsayBlockInterpreter.php @@ -86,7 +86,7 @@ which generates: cowsay{{{Why don't they play poker in the jungle? Too many cheetahs}}} -More information about Cowsay can be found [[/reference/cowsay/ | here]] +[[/reference/cowsay/ | More information about Cowsay]] is available. EOT; } diff --git a/src/infrastructure/markup/interpreter/PhabricatorRemarkupFigletBlockInterpreter.php b/src/infrastructure/markup/interpreter/PhabricatorRemarkupFigletBlockInterpreter.php index 87ac14d1fc..d959b3cc42 100644 --- a/src/infrastructure/markup/interpreter/PhabricatorRemarkupFigletBlockInterpreter.php +++ b/src/infrastructure/markup/interpreter/PhabricatorRemarkupFigletBlockInterpreter.php @@ -81,7 +81,7 @@ For example, this: figlet{{{Some big text!}}} -More information about Figlet can be found [[/reference/figlet/ | here]] +[[/reference/figlet/ | More information about Figlet]] is available. EOT; } } From c769125e907048772447b9bf621424ce26ab34eb Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 16 May 2024 12:54:39 +0200 Subject: [PATCH 055/118] Correct rendering of workboard column move feed stories when a single transaction performs moves on multiple boards Summary: If a single transaction performs column moves on multiple different boards (which is permitted in the API), the rendering logic in the feed currently fails. Make it render properly. Same fix as rPa3f4cbd7484b591425e83bbfc7642bccb04d0d57 for `getTitle()` which left out also fixing `getTitleForFeed()`. Test Plan: Carefully read the if/else logic. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25646 --- .../transactions/storage/PhabricatorApplicationTransaction.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index c03cefeed0..607b3713a6 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -1475,6 +1475,8 @@ abstract class PhabricatorApplicationTransaction } else { $fragments = array(); foreach ($moves as $move) { + $to_column = $move['columnPHID']; + $board_phid = $move['boardPHID']; $fragments[] = pht( '%s (%s)', $this->renderHandleLink($board_phid), From 899abf7a2b534131d3d4a34016526558494d1fdc Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Mon, 20 May 2024 18:47:00 +0200 Subject: [PATCH 056/118] Redirect back to object after clicking "Log In to Comment" and login Summary: `PhabricatorApplicationTransactionCommentView` performs `$uri = id(new PhutilURI('/login/'))->replaceQueryParam('next', (string)$this->getRequestURI())` but the constructor call in `PhabricatorEditEngine` does not set any `RequestURI` so the `Log In to Comment` button at the bottom of an object view does not redirect back to the object URI after login. Thus pass a `RequestURI` to `PhabricatorApplicationTransactionCommentView` in the constructor call in `PhabricatorEditEngine`. Closes T15837 Test Plan: As a Phorge admin, set `policy.allow-public` to true and create a Maniphest task with `Public` view policy. As an anonymous user, visit the task and click the `Log In to Comment` button at the bottom of the single task view, log in, and see that you get redirected to the task. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15837 Differential Revision: https://we.phorge.it/D25667 --- .../transactions/editengine/PhabricatorEditEngine.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index d63a66e8f3..4cc9914fe2 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -1676,6 +1676,7 @@ abstract class PhabricatorEditEngine ->setUser($viewer) ->setHeaderText($header_text) ->setAction($comment_uri) + ->setRequestURI(new PhutilURI($this->getObjectViewURI($object))) ->setRequiresMFA($requires_mfa) ->setObject($object) ->setEditEngine($this) From 43d3fd9eac6e8e44d8a60110d8ad6f01bb240979 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Sun, 12 May 2024 10:15:37 +0200 Subject: [PATCH 057/118] Fix exception awarding empty badge to user Summary: When awarding a badge to a user without having actually selected a batch to award, do not fail on running a database query with no parameter. Instead, silently fail without an error and reload the page, similar to the behavior of `Add Recipients` on `/badges/recipients/1/` in this case. Do not throw an Aphront404Response as it would make the `Award Badge` button in the dialog inaccessible and keep the dysfunctional overlay dialog displayed. ``` EXCEPTION: (AphrontParameterQueryException) Array for %Ls conversion is empty. Query: badges.phid IN (%Ls) at [/src/infrastructure/storage/xsprintf/qsprintf.php:383] ``` Closes T15825 Test Plan: Go to a user's badges, click the `Award Badge` button, in the dialog do not select any Badge and click the `Award` button. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: avivey, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15825 Differential Revision: https://we.phorge.it/D25636 --- .../PhabricatorBadgesAwardController.php | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/src/applications/badges/controller/PhabricatorBadgesAwardController.php b/src/applications/badges/controller/PhabricatorBadgesAwardController.php index 63af70b791..1a31584a51 100644 --- a/src/applications/badges/controller/PhabricatorBadgesAwardController.php +++ b/src/applications/badges/controller/PhabricatorBadgesAwardController.php @@ -6,6 +6,8 @@ final class PhabricatorBadgesAwardController public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); + $errors = array(); + $e_badge = true; $user = id(new PhabricatorPeopleQuery()) ->setViewer($viewer) @@ -19,37 +21,41 @@ final class PhabricatorBadgesAwardController if ($request->isFormPost()) { $badge_phids = $request->getArr('badgePHIDs'); - $badges = id(new PhabricatorBadgesQuery()) - ->setViewer($viewer) - ->withPHIDs($badge_phids) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_EDIT, - PhabricatorPolicyCapability::CAN_VIEW, - )) - ->execute(); - if (!$badges) { - return new Aphront404Response(); + + if (empty($badge_phids)) { + $errors[] = pht('Badge name is required.'); + $e_badge = pht('Required'); } - $award_phids = array($user->getPHID()); + if (!$errors) { + $badges = id(new PhabricatorBadgesQuery()) + ->setViewer($viewer) + ->withPHIDs($badge_phids) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_EDIT, + PhabricatorPolicyCapability::CAN_VIEW, + )) + ->execute(); + $award_phids = array($user->getPHID()); - foreach ($badges as $badge) { - $xactions = array(); - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType( - PhabricatorBadgesBadgeAwardTransaction::TRANSACTIONTYPE) - ->setNewValue($award_phids); + foreach ($badges as $badge) { + $xactions = array(); + $xactions[] = id(new PhabricatorBadgesTransaction()) + ->setTransactionType( + PhabricatorBadgesBadgeAwardTransaction::TRANSACTIONTYPE) + ->setNewValue($award_phids); - $editor = id(new PhabricatorBadgesEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->setContinueOnMissingFields(true) - ->applyTransactions($badge, $xactions); + $editor = id(new PhabricatorBadgesEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($badge, $xactions); + } + + return id(new AphrontRedirectResponse()) + ->setURI($view_uri); } - - return id(new AphrontRedirectResponse()) - ->setURI($view_uri); } $form = id(new AphrontFormView()) @@ -58,6 +64,7 @@ final class PhabricatorBadgesAwardController id(new AphrontFormTokenizerControl()) ->setLabel(pht('Badge')) ->setName('badgePHIDs') + ->setError($e_badge) ->setDatasource( id(new PhabricatorBadgesDatasource()) ->setParameters( From 91815cefc547ce6f7eb8430445c39a2250116137 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 11 Jun 2024 01:13:33 +0200 Subject: [PATCH 058/118] Fix broken URIs on "Rendering HTML" Diviner page Summary: Remove incorrect book scope from numerous `find` URIs on the "Rendering HTML" Diviner page in order to get results instead of 404 pages. Test Plan: Refresh Diviner documentation from Phorge directory: ./bin/diviner generate Go to https://we.phorge.it/book/contrib/article/rendering_html/ and click random links on that page. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25688 --- src/docs/contributor/rendering_html.diviner | 50 ++++++++++----------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/docs/contributor/rendering_html.diviner b/src/docs/contributor/rendering_html.diviner index 40892b5ad7..05e0cf39a2 100644 --- a/src/docs/contributor/rendering_html.diviner +++ b/src/docs/contributor/rendering_html.diviner @@ -13,13 +13,13 @@ pipeline, and the browser will treat it as plain text, not HTML. This document describes the right way to build HTML components so they are safe from XSS and render correctly. Broadly: - - Use @{function@arcanist:phutil_tag} (and @{function:javelin_tag}) to build + - Use @{function:phutil_tag} (and @{function:javelin_tag}) to build tags. - - Use @{function@arcanist:hsprintf} where @{function@arcanist:phutil_tag} + - Use @{function:hsprintf} where @{function:phutil_tag} is awkward. - Combine elements with arrays, not string concatenation. - @{class:AphrontView} subclasses should return a - @{class@arcanist:PhutilSafeHTML} object from their `render()` method. + @{class:PhutilSafeHTML} object from their `render()` method. - @{class:AphrontView} subclasses act like tags when rendering. - @{function:pht} has some special rules. - There are some other things that you should be aware of. @@ -28,7 +28,7 @@ See below for discussion. = Building Tags: phutil_tag() = -Build HTML tags with @{function@arcanist:phutil_tag}. For example: +Build HTML tags with @{function:phutil_tag}. For example: phutil_tag( 'div', @@ -37,10 +37,10 @@ Build HTML tags with @{function@arcanist:phutil_tag}. For example: ), $content); -@{function@arcanist:phutil_tag} will properly escape the content and all the -attributes, and return a @{class@arcanist:PhutilSafeHTML} object. The rendering +@{function:phutil_tag} will properly escape the content and all the +attributes, and return a @{class:PhutilSafeHTML} object. The rendering pipeline knows that this object represents a properly escaped HTML tag. This -allows @{function@arcanist:phutil_tag} to render tags with other tags as +allows @{function:phutil_tag} to render tags with other tags as content correctly (without double-escaping): phutil_tag( @@ -52,14 +52,14 @@ content correctly (without double-escaping): $content)); In Phorge, the @{function:javelin_tag} function is similar to -@{function@arcanist:phutil_tag}, but provides special handling for the +@{function:phutil_tag}, but provides special handling for the `sigil` and `meta` attributes. = Building Blocks: hsprintf() = -Sometimes, @{function@arcanist:phutil_tag} can be particularly awkward to -use. You can use @{function@arcanist:hsprintf} to build larger and more -complex blocks of HTML, when @{function@arcanist:phutil_tag} is a poor fit. +Sometimes, @{function:phutil_tag} can be particularly awkward to +use. You can use @{function:hsprintf} to build larger and more +complex blocks of HTML, when @{function:phutil_tag} is a poor fit. @{function:hsprintf} has `sprintf()` semantics, but `%s` escapes HTML: // Safely build fragments or unwieldy blocks. @@ -72,13 +72,13 @@ complex blocks of HTML, when @{function@arcanist:phutil_tag} is a poor fit. - You need to build a block with a lot of tags, like a table with rows and cells. - You need to build part of a tag (usually you should avoid this, but if you - do need to, @{function@arcanist:phutil_tag} can not do it). + do need to, @{function:phutil_tag} can not do it). Note that it is unsafe to provide any user-controlled data to the first -parameter of @{function@arcanist:hsprintf} (the `sprintf()`-style pattern). +parameter of @{function:hsprintf} (the `sprintf()`-style pattern). -Like @{function@arcanist:phutil_tag}, this function returns a -@{class@arcanist:PhutilSafeHTML} object. +Like @{function:phutil_tag}, this function returns a +@{class:PhutilSafeHTML} object. = Composing Tags = @@ -99,7 +99,7 @@ Instead, use an array: // Render a tag containing other tags safely. phutil_tag('div', array(), array($header, $body)); -If you concatenate @{class@arcanist:PhutilSafeHTML} objects, they revert to +If you concatenate @{class:PhutilSafeHTML} objects, they revert to normal strings and are no longer marked as properly escaped tags. (In the future, these objects may stop converting to strings, but for now they @@ -118,7 +118,7 @@ If you need to build a list of items with some element in between each of them = AphrontView Classes = Subclasses of @{class:AphrontView} in Phorge should return a -@{class@arcanist:PhutilSafeHTML} object. The easiest way to do this is to +@{class:PhutilSafeHTML} object. The easiest way to do this is to return `phutil_tag()` or `javelin_tag()`: return phutil_tag('div', ...); @@ -130,8 +130,8 @@ You can use an @{class:AphrontView} subclass like you would a tag: = Internationalization: pht() = The @{function:pht} function has some special rules. If any input to -@{function:pht} is a @{class@arcanist:PhutilSafeHTML} object, @{function:pht} -returns a @{class@arcanist:PhutilSafeHTML} object itself. Otherwise, it returns +@{function:pht} is a @{class:PhutilSafeHTML} object, @{function:pht} +returns a @{class:PhutilSafeHTML} object itself. Otherwise, it returns normal text. This is generally safe because translations are not permitted to have more tags @@ -146,23 +146,23 @@ like you would expect, but it is worth being aware of. NOTE: This section describes dangerous methods which can bypass XSS protections. If possible, do not use them. -You can build @{class@arcanist:PhutilSafeHTML} out of a string explicitly by +You can build @{class:PhutilSafeHTML} out of a string explicitly by calling @{function:phutil_safe_html} on it. This is **dangerous**, because if you are wrong and the string is not actually safe, you have introduced an XSS vulnerability. Consequently, you should avoid calling this if possible. -You can use @{function@arcanist:phutil_escape_html_newlines} to escape HTML +You can use @{function:phutil_escape_html_newlines} to escape HTML while converting newlines to `
`. You should not need to explicitly use -@{function@arcanist:phutil_escape_html} anywhere. +@{function:phutil_escape_html} anywhere. If you need to apply a string function (such as `trim()`) to safe HTML, use -@{method@arcanist:PhutilSafeHTML::applyFunction}. +@{method:PhutilSafeHTML::applyFunction}. -If you need to extract the content of a @{class@arcanist:PhutilSafeHTML} +If you need to extract the content of a @{class:PhutilSafeHTML} object, you should call `getHTMLContent()`, not cast it to a string. Eventually, we would like to remove the string cast entirely. -Functions @{function@arcanist:phutil_tag} and @{function@arcanist:hsprintf} +Functions @{function:phutil_tag} and @{function:hsprintf} are not safe if you pass the user input for the tag or attribute name. All the following examples are dangerous: From 30257515eb34181f54041f1c717c113d8739bbbb Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Sun, 30 Jun 2024 16:18:43 +0200 Subject: [PATCH 059/118] Fix typo in CSS definition Summary: Replace `-webkit-trasform` with `-webkit-transform`. Test Plan: Read the entire CSS spec at your fireplace. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25707 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/phui/phui-badge.css | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c6a05974cb..3cce373c8a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -137,7 +137,7 @@ return array( 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '9b03a61f', 'rsrc/css/phui/phui-action-list.css' => '1b0085b2', 'rsrc/css/phui/phui-action-panel.css' => '6c386cbf', - 'rsrc/css/phui/phui-badge.css' => '666e25ad', + 'rsrc/css/phui/phui-badge.css' => 'd12f6f6c', 'rsrc/css/phui/phui-basic-nav-view.css' => 'a5693cf0', 'rsrc/css/phui/phui-big-info-view.css' => '362ad37b', 'rsrc/css/phui/phui-box.css' => '5ed3b8cb', @@ -822,7 +822,7 @@ return array( 'phrequent-css' => 'bd79cc67', 'phriction-document-css' => '03380da0', 'phui-action-panel-css' => '6c386cbf', - 'phui-badge-view-css' => '666e25ad', + 'phui-badge-view-css' => 'd12f6f6c', 'phui-basic-nav-view-css' => 'a5693cf0', 'phui-big-info-view-css' => '362ad37b', 'phui-box-css' => '5ed3b8cb', diff --git a/webroot/rsrc/css/phui/phui-badge.css b/webroot/rsrc/css/phui/phui-badge.css index 1538e5de4d..a40c25390a 100644 --- a/webroot/rsrc/css/phui/phui-badge.css +++ b/webroot/rsrc/css/phui/phui-badge.css @@ -75,7 +75,7 @@ /* Firefox Fix */ .phui-badge-card-front { transform: rotateY(0deg); - -webkit-trasform: rotateY(0deg); + -webkit-transform: rotateY(0deg); } .phui-badge-front-view, From 118f989366a03245965ca7261d61d69fdf3b8d13 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Mon, 1 Jul 2024 18:40:28 +0200 Subject: [PATCH 060/118] Fix missing file attachment in Phriction Summary: When you drag and drop a file in the Phriction editor and then save the corresponding wiki document, the file was only visible by the author. Now the file is also attached to that document, making it visible. Refs T15106 Test Plan: 1) open the Phriction editor to edit an existing or create a new wiki document 2) drag and drop file in it 3) save wiki document 4) verify file is attached to wiki document (other user can see file in wiki page) Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15106 Differential Revision: https://we.phorge.it/D25705 --- .../controller/PhrictionEditController.php | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php index e6dea7f0ad..c48cac6400 100644 --- a/src/applications/phriction/controller/PhrictionEditController.php +++ b/src/applications/phriction/controller/PhrictionEditController.php @@ -134,9 +134,31 @@ final class PhrictionEditController $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); - $xactions[] = id(new PhrictionTransaction()) + $content_xaction = id(new PhrictionTransaction()) ->setTransactionType($edit_type) ->setNewValue($content_text); + + $content_metadata = $request->getStr('content_metadata'); + if ($content_metadata) { + $content_metadata = phutil_json_decode($content_metadata); + $attached_file_phids = idx( + $content_metadata, + 'attachedFilePHIDs', + array()); + + if ($attached_file_phids) { + $metadata_object = array( + 'remarkup.control' => array( + 'attachedFilePHIDs' => $attached_file_phids, + ), + ); + + $content_xaction->setMetadata($metadata_object); + } + } + + $xactions[] = $content_xaction; + $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) ->setNewValue($v_view) From 9a44a81e797d1829a8ea4b98e07e75cbe38f4302 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 16 May 2024 12:41:38 +0200 Subject: [PATCH 061/118] Remove unused save() method in PhabricatorUserCache Summary: Seems to be unused. Plus it calls `Filesystem::digestForIndex()` which does not exist. So if this method was ever called then it would through an exception and not reach the `parent::save()` call either in the line after. Test Plan: Grep and read the codebase. Tried things that may use cache and they still work, including: - running `./bin/cache purge --caches user` successfully shows `Purging "user" cache...` - the button "Purge cache" in /config/cache/ still work. - editing a user - changing user picture using default one - uploading new user picture - creating Calendar event and inviting yourself - visiting user settings - changing a user setting, including Timezone, Display preferences Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25645 --- src/applications/people/storage/PhabricatorUserCache.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/applications/people/storage/PhabricatorUserCache.php b/src/applications/people/storage/PhabricatorUserCache.php index 71b3e4816b..36292db4e9 100644 --- a/src/applications/people/storage/PhabricatorUserCache.php +++ b/src/applications/people/storage/PhabricatorUserCache.php @@ -32,11 +32,6 @@ final class PhabricatorUserCache extends PhabricatorUserDAO { ) + parent::getConfiguration(); } - public function save() { - $this->cacheIndex = Filesystem::digestForIndex($this->getCacheKey()); - return parent::save(); - } - public static function writeCache( PhabricatorUserCacheType $type, $key, From b0bba4d14235348f71ad2222192170922805f9ce Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 16 May 2024 12:06:43 +0200 Subject: [PATCH 062/118] Remove unused methods in PhabricatorEditorExtension Summary: The methods `newRequiredTransasctionError()` [sic!] and `newInvalidTransactionError()` are not called anywhere in the codebase. Both are `final protected`, thus remove them. Incidentally, speedup Diviner generation by at least 0.000000000000000000000001 milliseconds. Test Plan: Grep the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25643 --- .../engineextension/PhabricatorEditorExtension.php | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/applications/transactions/engineextension/PhabricatorEditorExtension.php b/src/applications/transactions/engineextension/PhabricatorEditorExtension.php index 6ffac522c2..643ecd0350 100644 --- a/src/applications/transactions/engineextension/PhabricatorEditorExtension.php +++ b/src/applications/transactions/engineextension/PhabricatorEditorExtension.php @@ -66,18 +66,4 @@ abstract class PhabricatorEditorExtension $xaction); } - final protected function newRequiredTransasctionError( - PhabricatorApplicationTransaction $xaction, - $message) { - return $this->newError($xaction, pht('Required'), $message) - ->setIsMissingFieldError(true); - } - - final protected function newInvalidTransactionError( - PhabricatorApplicationTransaction $xaction, - $message) { - return $this->newTransactionError($xaction, pht('Invalid'), $message); - } - - } From 136091ecd0970246c34e6de3a19f8ec33a42ead8 Mon Sep 17 00:00:00 2001 From: Merula Turdus Date: Fri, 5 Jul 2024 17:46:52 +0200 Subject: [PATCH 063/118] Fix missing file attachment in Conpherence Summary: When you drag and drop a file in a Conpherence chat, the file was only visible by the author. Now the file is also attached to that chat, making it visible. This is a follow-up from: https://we.phorge.it/D25705?id=2178 Refs T15106 Test Plan: 1) open a Conpherence chat or create a new one 2) drag and drop file in it and send the message 3) verify file is attached to the chat Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: avivey, Cigaryno, Matthew, valerio.bozzolan, tobiaswiese Maniphest Tasks: T15106 Differential Revision: https://we.phorge.it/D25709 --- .../ConpherenceUpdateController.php | 24 +++++++++++++++++++ .../constants/PhabricatorTransactions.php | 15 ++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 6d838e72bc..1b2ae3ab62 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -79,6 +79,30 @@ final class ConpherenceUpdateController $user, $conpherence, $message); + + $xaction_comment = PhabricatorTransactions::findOneByType( + $xactions, + PhabricatorTransactions::TYPE_COMMENT); + + $text_metadata = $request->getStr('text_metadata'); + if ($text_metadata) { + $text_metadata = phutil_json_decode($text_metadata); + $attached_file_phids = idx( + $text_metadata, + 'attachedFilePHIDs', + array()); + + if ($attached_file_phids) { + $metadata_object = array( + 'remarkup.control' => array( + 'attachedFilePHIDs' => $attached_file_phids, + ), + ); + + $xaction_comment->setMetadata($metadata_object); + } + } + $delete_draft = true; } else { $action = ConpherenceUpdateActions::LOAD; diff --git a/src/applications/transactions/constants/PhabricatorTransactions.php b/src/applications/transactions/constants/PhabricatorTransactions.php index 3e31bdb1db..1f896d662d 100644 --- a/src/applications/transactions/constants/PhabricatorTransactions.php +++ b/src/applications/transactions/constants/PhabricatorTransactions.php @@ -41,4 +41,19 @@ final class PhabricatorTransactions extends Phobject { ); } + /** + * Find the first transaction that matches a type. + * @param array $xactions + * @param string $type + * @return PhabricatorTransactions|null + */ + public static function findOneByType($xactions, $type) { + foreach ($xactions as $xaction) { + if ($xaction->getTransactionType() === $type) { + return $xaction; + } + } + return null; + } + } From 1b5d2f83c3aa03dada5cbed4a18d341733348425 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 2 Jul 2024 21:07:18 +0200 Subject: [PATCH 064/118] Fix property name typo in DrydockResource Summary: `$capbilities` has a typo. Test Plan: Slowly spell the word "capabilities"; compare with the top of the file. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25714 --- src/applications/drydock/storage/DrydockResource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index 38e6660f7e..f7f29c9bc1 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -69,7 +69,7 @@ final class DrydockResource extends DrydockDAO } public function getCapability($key, $default = null) { - return idx($this->capbilities, $key, $default); + return idx($this->capabilities, $key, $default); } public function getInterface(DrydockLease $lease, $type) { From 290883b2a9953fab8301891078b8fefdeb9bebce Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 9 Jul 2024 11:57:09 +0200 Subject: [PATCH 065/118] Set specific timeline title for form creation Summary: The timeline for an EditEngine form says `created this object`. It should say `created this form`. Closes T15880 Test Plan: Go to http://phorge.localhost/transactions/editengine/maniphest.task/ and create an EditEngine form, scroll down to its timeline. Reviewers: O1 Blessed Committers, speck Reviewed By: O1 Blessed Committers, speck Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15880 Differential Revision: https://we.phorge.it/D25726 --- .../editor/PhabricatorEditEngineConfigurationEditor.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php b/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php index 5229ee6bec..a1b94f419a 100644 --- a/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php +++ b/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php @@ -7,6 +7,10 @@ final class PhabricatorEditEngineConfigurationEditor return PhabricatorTransactionsApplication::class; } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this form.', $author); + } + public function getEditorObjectsDescription() { return pht('Edit Configurations'); } From ff7be2cf1444d9637d3402573c0b21d47b1cf75a Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 9 Jul 2024 23:25:21 +0200 Subject: [PATCH 066/118] Fix invalid logic in PhabricatorEmailPreferencesSettingsPanel::processRequest() Summary: The expression `$all_tags[$tag]['count']` does not do anything. Reading the surrounding code, intention is to display a "Common" section on `/settings/panel/emailpreferences/` listing MailTags defined by at least two different applications. (This is currently not the case anyway as Phorge prefixes all MailTags with their corresponding application but might be a future use case.) This change fixes the logic accordingly. Closes T15874 Test Plan: Apply the patch; replace the value of a random `const MAILTAG_*` line in the codebase with the value of another random `const MAILTAG_*` line in the codebase to make it "common", now see that `/settings/panel/emailpreferences/` displays this MailTag in a "Common" section on top of the options. Reviewers: O1 Blessed Committers, speck Reviewed By: O1 Blessed Committers, speck Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15874 Differential Revision: https://we.phorge.it/D25727 --- .../panel/PhabricatorEmailPreferencesSettingsPanel.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php index 50d64072f5..6e84e28622 100644 --- a/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php @@ -97,14 +97,15 @@ final class PhabricatorEmailPreferencesSettingsPanel 'count' => 0, 'name' => $name, ); + } else { + $all_tags[$tag]['count']++; } - $all_tags[$tag]['count']; } } $common_tags = array(); foreach ($all_tags as $tag => $info) { - if ($info['count'] > 1) { + if ($info['count'] > 0) { $common_tags[$tag] = $info['name']; } } From d9592e03481214e23e0497d4909f12ecd7b50989 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 2 Jul 2024 22:52:37 +0200 Subject: [PATCH 067/118] Removed unused validateNavigationMenuItem() from PhabricatorProfileMenuEngine Summary: The commit rP7bde92b9c9806c5967df617fb4e843dea315ecf7 (2016) introduced `private function validateNavigationMenuItem()`. The only call to this method was removed in rP950e9d085b482bc3484e2066591ac07c20154c9a. Thus remove this unused method. Test Plan: Read/grep the code in `PhabricatorProfileMenuEngine`. Run also `grep -RF 'validateNavigationMenuItem' --include="*.php" .` to verify that this is an orphan method. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25719 --- .../search/engine/PhabricatorProfileMenuEngine.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/applications/search/engine/PhabricatorProfileMenuEngine.php b/src/applications/search/engine/PhabricatorProfileMenuEngine.php index 89cb6cb40a..7fbfe1c2da 100644 --- a/src/applications/search/engine/PhabricatorProfileMenuEngine.php +++ b/src/applications/search/engine/PhabricatorProfileMenuEngine.php @@ -469,15 +469,6 @@ abstract class PhabricatorProfileMenuEngine extends Phobject { return $map; } - private function validateNavigationMenuItem($item) { - if (!($item instanceof PHUIListItemView)) { - throw new Exception( - pht( - 'Expected buildNavigationMenuItems() to return a list of '. - 'PHUIListItemView objects, but got a surprise.')); - } - } - public function getConfigureURI() { $mode = $this->getEditMode(); From 3fbb0309b53f2557dea0590723c93b2e258b83a8 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Wed, 3 Jul 2024 12:01:43 +0200 Subject: [PATCH 068/118] Update external timezone information to IANA TZDB 2024a Summary: Update the list of timezones by pulling from CLDR upstream (revision `9d96f34` from 2024-02-08) located here: https://github.com/unicode-org/cldr/blob/main/common/supplemental/windowsZones.xml Permalink: https://github.com/unicode-org/cldr/blob/9d96f340ab145003b3e6d12d95ffb16f19a8a529/common/supplemental/windowsZones.xml Closes T15876 Test Plan: * As an admin, go to http://phorge.localhost/config/edit/phabricator.timezone/ and change the default value. * As a user, go to http://phorge.localhost/settings/panel/datetime/ and change the value in the `Timezone` dropdown. * Run the `diff` command against Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: revi, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15876 Differential Revision: https://we.phorge.it/D25722 --- externals/cldr/cldr_windows_timezones.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/externals/cldr/cldr_windows_timezones.xml b/externals/cldr/cldr_windows_timezones.xml index 2fae6c3c18..a81d0beb83 100644 --- a/externals/cldr/cldr_windows_timezones.xml +++ b/externals/cldr/cldr_windows_timezones.xml @@ -537,7 +537,8 @@ For terms of use, see http://www.unicode.org/copyright.html - + + @@ -569,13 +570,12 @@ For terms of use, see http://www.unicode.org/copyright.html - - + + - From 941b769abae654b2581ab84d67bc7eff7f60d855 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 2 Jul 2024 21:18:09 +0200 Subject: [PATCH 069/118] Declare missing class property in PhabricatorTimelineEngine Summary: `setRequest()` and `getRequest` in this class access the undefined property `$request`. Thus define this property. Test Plan: Run PHPStan static code analysis; grep the code in this class. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25716 --- .../transactions/engine/PhabricatorTimelineEngine.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/transactions/engine/PhabricatorTimelineEngine.php b/src/applications/transactions/engine/PhabricatorTimelineEngine.php index 2fa0b1451d..8f32d81ff9 100644 --- a/src/applications/transactions/engine/PhabricatorTimelineEngine.php +++ b/src/applications/transactions/engine/PhabricatorTimelineEngine.php @@ -6,6 +6,7 @@ abstract class PhabricatorTimelineEngine private $viewer; private $object; private $xactions; + private $request; private $viewData; final public static function newForObject($object) { From c4246cf871a76a8dd61a50ab5765e53d609f98d8 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Wed, 10 Jul 2024 15:24:40 +0200 Subject: [PATCH 070/118] Remove unreachable return statement in AphrontFormDateControlValue Summary: Two unconditional return statements lead to never reaching the second one. Test Plan: Read the code of this method. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25729 --- src/view/form/control/AphrontFormDateControlValue.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php index 873ad44fe4..b4d3e08a0b 100644 --- a/src/view/form/control/AphrontFormDateControlValue.php +++ b/src/view/form/control/AphrontFormDateControlValue.php @@ -207,8 +207,6 @@ final class AphrontFormDateControlValue extends Phobject { $datetime->format($this->getDateFormat()), $datetime->format($this->getTimeFormat()), ); - - return array($date, $time); } private function newDateTime($date, $time) { From 59428f64ca2c3e2f8e0fc1e85b0c3b4a7ec95ee0 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Wed, 10 Jul 2024 00:22:57 +0200 Subject: [PATCH 071/118] Fix PHP 8.1 "file_exists(null)" exception rendering AphrontStackTraceView Summary: Passing null to `file_exists()` is deprecated behavior since PHP 8.1. The already existing `if ($file)` check in `AphrontStackTraceView` implies that `$file` can indeed be empty. Thus add another such check higher up in that class to avoid deprecation warnings when rendering stacktraces. ``` ERROR 8192: file_exists(): Passing null to parameter #1 ($filename) of type string is deprecated at [/var/www/html/phorge/arcanist/src/filesystem/Filesystem.php:1068]; #0 file_exists(NULL) called at [/src/filesystem/Filesystem.php:1068]; #1 Filesystem::pathExists(NULL) called at [/src/filesystem/Filesystem.php:1180]; #2 Filesystem::assertExists(NULL) called at [/src/filesystem/Filesystem.php:1020]; #3 Filesystem::isDescendant(NULL, string) called at [/src/view/widget/AphrontStackTraceView.php:33]; ``` Closes T15881 Test Plan: Intentionally inject an `Array to string conversion bug` in `PhabricatorEmailPreferencesSettingsPanel` (see T15881). Then visit `/settings/panel/emailpreferences/`. See the same stacktrace before and after applying this change, but see only a single line of output in DarkConsole / Error Log after applying this patch and not anymore numerous `Passing null to parameter #1 ($filename) of type string is deprecated` errors in DarkConsole / Error Log. Reviewers: O1 Blessed Committers, speck Reviewed By: O1 Blessed Committers, speck Subscribers: speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15881 Differential Revision: https://we.phorge.it/D25728 --- src/view/widget/AphrontStackTraceView.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/view/widget/AphrontStackTraceView.php b/src/view/widget/AphrontStackTraceView.php index 15b39be878..263e6e0d74 100644 --- a/src/view/widget/AphrontStackTraceView.php +++ b/src/view/widget/AphrontStackTraceView.php @@ -27,13 +27,15 @@ final class AphrontStackTraceView extends AphrontView { foreach ($trace as $part) { $lib = null; $file = idx($part, 'file'); - $relative = $file; - foreach ($libraries as $library) { - $root = phutil_get_library_root($library); - if (Filesystem::isDescendant($file, $root)) { - $lib = $library; - $relative = Filesystem::readablePath($file, $root); - break; + if ($file !== null && $file !== '') { + $relative = $file; + foreach ($libraries as $library) { + $root = phutil_get_library_root($library); + if (Filesystem::isDescendant($file, $root)) { + $lib = $library; + $relative = Filesystem::readablePath($file, $root); + break; + } } } From 877ac8a873f979f418064eb2f36f1148c9838694 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 2 Jul 2024 21:10:36 +0200 Subject: [PATCH 072/118] Fix property name typo in PhabricatorPDFIterator Summary: `$framgentKey` has a typo. Test Plan: Slowly spell the word "fragmentKey"; compare with the top of the file. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25715 --- src/applications/phortune/pdf/PhabricatorPDFIterator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/phortune/pdf/PhabricatorPDFIterator.php b/src/applications/phortune/pdf/PhabricatorPDFIterator.php index d39168369d..81626b91f5 100644 --- a/src/applications/phortune/pdf/PhabricatorPDFIterator.php +++ b/src/applications/phortune/pdf/PhabricatorPDFIterator.php @@ -45,7 +45,7 @@ final class PhabricatorPDFIterator } public function key() { - return $this->framgentKey; + return $this->fragmentKey; } public function next() { From 5a454cef8a98a71aead7f46e97270836c029b36c Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Sat, 13 Jul 2024 17:46:32 +0200 Subject: [PATCH 073/118] Set ActionName to "Created" for PhabricatorTransactions::TYPE_CREATE Summary: Explicitly set "Created" as ActionName (to be used as a mail subject line vary prefix) when the transaction type is `PhabricatorTransactions::TYPE_CREATE` to avoid falling back to a generic "Updated" prefix. Closes T15865 Test Plan: Either create a new task and check the mail subject line prefix (if you have a mail setup in your Phorge instance), or use the debug patch in P45 and check the order of action strengths. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15865 Differential Revision: https://we.phorge.it/D25732 --- .../transactions/storage/PhabricatorApplicationTransaction.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index 607b3713a6..01ebc178cb 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -1608,6 +1608,8 @@ abstract class PhabricatorApplicationTransaction return pht('Changed Policy'); case PhabricatorTransactions::TYPE_SUBSCRIBERS: return pht('Changed Subscribers'); + case PhabricatorTransactions::TYPE_CREATE: + return pht('Created'); default: return pht('Updated'); } From 03092ca422025df6bc394bee4cc2958c34ccef4f Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 2 Jul 2024 22:43:46 +0200 Subject: [PATCH 074/118] Removed unused loadGitLocalRefs() from PhabricatorRepositoryPullEngine Summary: rP2b0ad243d179f81baad24ccd9748a4ba59017d25 introduced `private function loadGitLocalRefs()`. In rPe910c76e65e54a439a0af4735ae89c70076673d5, the only call to `loadGitLocalRefs()` was removed, using `loadGitRemoteRefs()` to load (and compare) both remote and local refs. Thus remove this unused method. Test Plan: Read/grep the code in `PhabricatorRepositoryPullEngine`. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25718 --- .../engine/PhabricatorRepositoryPullEngine.php | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index 5bcf4554fd..f611c51112 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -602,22 +602,6 @@ final class PhabricatorRepositoryPullEngine return $map; } - private function loadGitLocalRefs(PhabricatorRepository $repository) { - $refs = id(new DiffusionLowLevelGitRefQuery()) - ->setRepository($repository) - ->execute(); - - $map = array(); - foreach ($refs as $ref) { - $fields = $ref->getRawFields(); - $map[idx($fields, 'refname')] = $ref->getCommitIdentifier(); - } - - ksort($map); - - return $map; - } - private function logRefDifferences(array $remote, array $local) { $all = $local + $remote; From fec00256be1f8b9133076b8d7329f0a99c891daa Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Mon, 27 May 2024 23:36:46 +0200 Subject: [PATCH 075/118] Allow collapsing/expanding workboard column content by clicking its header Summary: Reduce users' need for scrolling on smaller screens with 920px or less viewport width by using HTML5's `
`/`` so clicking on a workboard column header hides the content of that column, in all CSS views (mobile, tablet, desktop). Keep expanding its content by default. On mobile and tablet devices, display an arrow in the column header box below the header text to potentially make those users aware of this functionality that benefit the most from it. Do not render these arrows on desktop devices (though the collapse/expand functionality still works there). See https://caniuse.com/details for browser (in)compatibility. Closes T15843 Test Plan: Go to a project workboard with several columns and tasks in them on a screen with 920px or less width. See a small arrow below the column header text. Click on a column header to collapse and expand the column content. Reviewers: O1 Blessed Committers, valerio.bozzolan, avivey Reviewed By: O1 Blessed Committers, valerio.bozzolan, avivey Subscribers: avivey, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15843 Differential Revision: https://we.phorge.it/D25672 --- resources/celerity/map.php | 6 +++--- src/view/phui/PHUIBoxView.php | 24 +++++++++++++++++++++- src/view/phui/PHUIHeaderView.php | 15 ++++++++++++++ src/view/phui/PHUIWorkpanelView.php | 4 +++- webroot/rsrc/css/phui/phui-header-view.css | 21 +++++++++++++++++++ 5 files changed, 65 insertions(+), 5 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 3cce373c8a..c6c577aa3a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '2f25eb4f', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => '3471f5d3', + 'core.pkg.css' => 'ac619266', 'core.pkg.js' => '2eeda9e0', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => '6d3700f0', @@ -158,7 +158,7 @@ return array( 'rsrc/css/phui/phui-form.css' => '1f177cb7', 'rsrc/css/phui/phui-formation-view.css' => 'd2dec8ed', 'rsrc/css/phui/phui-head-thing.css' => 'd7f293df', - 'rsrc/css/phui/phui-header-view.css' => '36c86a58', + 'rsrc/css/phui/phui-header-view.css' => '4cd25427', 'rsrc/css/phui/phui-hovercard.css' => '39fd2e14', 'rsrc/css/phui/phui-icon-set-selector.css' => '19e0253b', 'rsrc/css/phui/phui-icon.css' => '084ac612', @@ -851,7 +851,7 @@ return array( 'phui-form-view-css' => '57edecb7', 'phui-formation-view-css' => 'd2dec8ed', 'phui-head-thing-view-css' => 'd7f293df', - 'phui-header-view-css' => '36c86a58', + 'phui-header-view-css' => '4cd25427', 'phui-hovercard' => '6199f752', 'phui-hovercard-list' => 'de4b4919', 'phui-hovercard-view-css' => '39fd2e14', diff --git a/src/view/phui/PHUIBoxView.php b/src/view/phui/PHUIBoxView.php index 7068f8811c..33924379cc 100644 --- a/src/view/phui/PHUIBoxView.php +++ b/src/view/phui/PHUIBoxView.php @@ -6,6 +6,7 @@ final class PHUIBoxView extends AphrontTagView { private $padding = array(); private $border = false; private $color; + private $collapsible; const BLUE = 'phui-box-blue'; const GREY = 'phui-box-grey'; @@ -30,6 +31,17 @@ final class PHUIBoxView extends AphrontTagView { return $this; } + /** + * Render PHUIBoxView as a
instead of a
HTML tag. + * To be used for collapse/expand in combination with PHUIHeaderView. + * + * @param bool True to wrap in instead of
HTML tag. + */ + public function setCollapsible($collapsible) { + $this->collapsible = $collapsible; + return $this; + } + protected function getTagAttributes() { require_celerity_resource('phui-box-css'); $outer_classes = array(); @@ -51,10 +63,20 @@ final class PHUIBoxView extends AphrontTagView { $outer_classes[] = $this->color; } - return array('class' => $outer_classes); + $tag_classes = array('class' => $outer_classes); + + if ($this->collapsible) { + $attribute = array('open' => ''); // expand column by default + $tag_classes = array_merge($tag_classes, $attribute); + } + + return $tag_classes; } protected function getTagName() { + if ($this->collapsible) { + return 'details'; + } return 'div'; } diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index 465768ae16..f1952b4843 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -24,6 +24,7 @@ final class PHUIHeaderView extends AphrontTagView { private $href; private $actionList; private $actionListID; + private $collapsible; public function setHeader($header) { $this->header = $header; @@ -90,6 +91,17 @@ final class PHUIHeaderView extends AphrontTagView { return $this; } + /** + * Render PHUIHeaderView as a instead of a
HTML tag. + * To be used for collapse/expand in combination with PHUIBoxView. + * + * @param bool True to wrap in instead of
HTML tag. + */ + public function setCollapsible($collapsible) { + $this->collapsible = $collapsible; + return $this; + } + public function setPolicyObject(PhabricatorPolicyInterface $object) { $this->policyObject = $object; return $this; @@ -156,6 +168,9 @@ final class PHUIHeaderView extends AphrontTagView { } protected function getTagName() { + if ($this->collapsible) { + return 'summary'; + } return 'div'; } diff --git a/src/view/phui/PHUIWorkpanelView.php b/src/view/phui/PHUIWorkpanelView.php index 911d38c2e3..ad8c77fd07 100644 --- a/src/view/phui/PHUIWorkpanelView.php +++ b/src/view/phui/PHUIWorkpanelView.php @@ -81,7 +81,8 @@ final class PHUIWorkpanelView extends AphrontTagView { $header = id(new PHUIHeaderView()) ->setHeader($this->header) - ->setSubheader($this->subheader); + ->setSubheader($this->subheader) + ->setCollapsible(true); foreach ($this->headerActions as $action) { $header->addActionItem($action); @@ -112,6 +113,7 @@ final class PHUIWorkpanelView extends AphrontTagView { $view = id(new PHUIBoxView()) ->setColor(PHUIBoxView::GREY) ->addClass('phui-workpanel-view-inner') + ->setCollapsible(true) ->appendChild( array( $header, diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index 7131db5640..0e418059ec 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -56,6 +56,27 @@ body .phui-header-shell.phui-bleed-header border-top-width: 0; } +details > summary.phui-header-shell { + cursor: pointer; + list-style: none; +} + +details > summary.phui-header-shell::marker { + content: none; +} + +.device-phone details > summary.phui-header-shell::after, +.device-tablet details > summary.phui-header-shell::after { + font-family: FontAwesome; + content: "\f061"; +} + +.device-phone details[open] > summary.phui-header-shell::after, +.device-tablet details[open] > summary.phui-header-shell::after { + font-family: FontAwesome; + content: "\f063"; +} + .phui-property-list-view + .diviner-document-section { margin-top: -1px; } From c3aa581f8440df8e89135a3a48da56e66774c1e5 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 2 Jul 2024 22:24:09 +0200 Subject: [PATCH 076/118] Removed unused renderStatus() from PhabricatorProjectSubprojectsController Summary: rPfc9db6e2a2ee929f56eb40530bb6f1fc1b75f563 introduced `private function renderStatus()`. All calls to this method were removed in rP29cfcc82ef7f84580e798aebeb2abcb8ffec57d7. Thus remove this unused method. Test Plan: Read/grep the code in `PhabricatorProjectSubprojectsController`. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25717 --- .../PhabricatorProjectSubprojectsController.php | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php index be787e7a4f..5b8ac2486a 100644 --- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -212,16 +212,4 @@ final class PhabricatorProjectSubprojectsController return $curtain; } - private function renderStatus($icon, $target, $note) { - $item = id(new PHUIStatusItemView()) - ->setIcon($icon) - ->setTarget(phutil_tag('strong', array(), $target)) - ->setNote($note); - - return id(new PHUIStatusListView()) - ->addItem($item); - } - - - } From 2aecae4ede9d9d42ae005db69f64d59152569c23 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Mon, 8 Jul 2024 03:55:36 +0200 Subject: [PATCH 077/118] Update phpqrcode from version 1.1.4 to 1.9.9 Summary: Update the internal copy of `phpqrcode` from version 1.1.4 (2010100721) to version 1.9.9 (20130526). Single reason is that it adds a lot of documentation which may come handy one day. This more recent code is taken from https://sourceforge.net/p/phpqrcode/code/19/ which equals https://github.com/Darkflib/php-qrcode/blob/master/lib/merged/phpqrcode.php, with our two custom one-line patches still on top. Closes T15878 Test Plan: * Manually diff between old downstream and slightly newer upstream version in a visual diff tool to review the changes. * Go to http://phorge.localhost/settings/panel/multifactor/ and select "Add Auth Factor", select TOTP, click "Continue", still see a QR code properly rendered which works. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15878 Differential Revision: https://we.phorge.it/D25725 --- externals/phpqrcode/phpqrcode.php | 2223 +++++++++++++++++++++++++---- 1 file changed, 1963 insertions(+), 260 deletions(-) diff --git a/externals/phpqrcode/phpqrcode.php b/externals/phpqrcode/phpqrcode.php index d441593f63..a25d313769 100644 --- a/externals/phpqrcode/phpqrcode.php +++ b/externals/phpqrcode/phpqrcode.php @@ -33,19 +33,19 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - - -/* - * Version: 1.1.4 - * Build: 2010100721 - */ - - - -//---- qrconst.php ----------------------------- - - - + + +/* + * Version: 1.9.9 + * Build: 20130526 + */ + + + +//---- qrconst.php ----------------------------- + + + /* @@ -57,7 +57,7 @@ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi * * PHP QR Code is distributed under LGPL 3 - * Copyright (C) 2010 Dominik Dzienia + * Copyright (C) 2010-2013 Dominik Dzienia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -74,39 +74,60 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - // Encoding modes - - define('QR_MODE_NUL', -1); - define('QR_MODE_NUM', 0); - define('QR_MODE_AN', 1); - define('QR_MODE_8', 2); - define('QR_MODE_KANJI', 3); - define('QR_MODE_STRUCTURE', 4); - // Levels of error correction. + /** \defgroup QR_CONST Global Constants + Constant used globally for function arguments. + Make PHP calls a little bit more clear, in place of missing (in dynamicaly typed language) enum types. + * @{ + */ + + /** @name QR-Code Encoding Modes */ + /** @{ */ + + /** null encoding, used when no encoding was speciffied yet */ + define('QR_MODE_NUL', -1); + /** Numerical encoding, only numbers (0-9) */ + define('QR_MODE_NUM', 0); + /** AlphaNumerical encoding, numbers (0-9) uppercase text (A-Z) and few special characters (space, $, %, *, +, -, ., /, :) */ + define('QR_MODE_AN', 1); + /** 8-bit encoding, raw 8 bit encoding */ + define('QR_MODE_8', 2); + /** Kanji encoding */ + define('QR_MODE_KANJI', 3); + /** Structure, internal encoding for structure-related data */ + define('QR_MODE_STRUCTURE', 4); + /**@}*/ + + /** @name QR-Code Levels of Error Correction + Constants speciffy ECC level from lowest __L__ to the highest __H__. + Higher levels are recomended for Outdoor-presented codes, but generates bigger codes. + */ + /** @{*/ + /** ~7% of codewords can be restored */ + define('QR_ECLEVEL_L', 0); + /** ~15% of codewords can be restored */ + define('QR_ECLEVEL_M', 1); + /** ~25% of codewords can be restored */ + define('QR_ECLEVEL_Q', 2); + /** ~30% of codewords can be restored */ + define('QR_ECLEVEL_H', 3); + /** @}*/ + + /** @name QR-Code Supported output formats */ + /** @{*/ + define('QR_FORMAT_TEXT', 0); + define('QR_FORMAT_PNG', 1); + /** @}*/ + + /** @}*/ + + + + +//---- merged_config.php ----------------------------- + + - define('QR_ECLEVEL_L', 0); - define('QR_ECLEVEL_M', 1); - define('QR_ECLEVEL_Q', 2); - define('QR_ECLEVEL_H', 3); - - // Supported output formats - - define('QR_FORMAT_TEXT', 0); - define('QR_FORMAT_PNG', 1); - - class qrstr { - public static function set(&$srctab, $x, $y, $repl, $replLen = false) { - $srctab[$y] = substr_replace($srctab[$y], ($replLen !== false)?substr($repl,0,$replLen):$repl, $x, ($replLen !== false)?$replLen:strlen($repl)); - } - } - - - -//---- merged_config.php ----------------------------- - - - /* * PHP QR Code encoder @@ -123,14 +144,14 @@ define('QR_DEFAULT_MASK', 2); // when QR_FIND_BEST_MASK === false define('QR_PNG_MAXIMUM_SIZE', 1024); // maximum allowed png image width (in pixels), tune to make sure GD and PHP can handle such big images - - - - -//---- qrtools.php ----------------------------- - - - + + + + +//---- qrtools.php ----------------------------- + + + /* * PHP QR Code encoder @@ -138,7 +159,7 @@ * Toolset, handy and debug utilites. * * PHP QR Code is distributed under LGPL 3 - * Copyright (C) 2010 Dominik Dzienia + * Copyright (C) 2010-2013 Dominik Dzienia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -155,8 +176,14 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + /** @addtogroup CoreGroup */ + /** @{ */ + + /** Helper class */ class QRtools { + public static $timeBenchmarkStarted = false; + //---------------------------------------------------------------------- public static function binarize($frame) { @@ -255,6 +282,13 @@ } } + //---------------------------------------------------------------------- + public static function startTimeBenchmark() + { + $GLOBALS['qr_time_bench'] = array(); + self::markTime('start'); + } + //---------------------------------------------------------------------- public static function markTime($markerId) { @@ -265,6 +299,11 @@ $GLOBALS['qr_time_bench'] = array(); $GLOBALS['qr_time_bench'][$markerId] = $time; + + if ((!self::$timeBenchmarkStarted)&&($markerId != 'start')) { + self::$timeBenchmarkStarted = true; + $GLOBALS['qr_time_bench']['start'] = $time; + } } //---------------------------------------------------------------------- @@ -299,17 +338,17 @@ } + /** @}*/ + //########################################################################## - QRtools::markTime('start'); - - - - -//---- qrspec.php ----------------------------- - - - + + + +//---- qrspec.php ----------------------------- + + + /* * PHP QR Code encoder @@ -320,7 +359,7 @@ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi * * PHP QR Code is distributed under LGPL 3 - * Copyright (C) 2010 Dominik Dzienia + * Copyright (C) 2010-2013 Dominik Dzienia * * The following data / specifications are taken from * "Two dimensional symbol -- QR-code -- Basic Specification" (JIS X0510:2004) @@ -343,16 +382,35 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + /** Maximal Version no allowed by QR-Code spec */ define('QRSPEC_VERSION_MAX', 40); + /** Maximal Code size in pixels allowed by QR-Code spec */ define('QRSPEC_WIDTH_MAX', 177); define('QRCAP_WIDTH', 0); define('QRCAP_WORDS', 1); define('QRCAP_REMINDER', 2); define('QRCAP_EC', 3); + + /** @addtogroup CoreGroup */ + /** @{ */ + /** QR-Code specification and Code Frame handling. + Contains code specifications, calculates base frame, code structure + and base properties + */ class QRspec { + /** Array specifying properties of QR-Code "versions". + Each so-called version has specified code area size and capacity. + There are 40 versions, this table specifies for each of them four parameters: + + - Integer __QRCAP_WIDTH__ - size of code in pixels + - Integer __QRCAP_WORDS__ - code capacity, in words + - Integer __QRCAP_REMINDER__ - remainder words + - Array of Integers __QRCAP_EC__ - RS correction code count for each of four ECC levels + \hideinitializer + */ public static $capacity = array( array( 0, 0, 0, array( 0, 0, 0, 0)), array( 21, 26, 0, array( 7, 10, 13, 17)), // 1 @@ -398,30 +456,53 @@ ); //---------------------------------------------------------------------- + /** Calculates data length for specified code configuration. + @param Integer $version Code version + @param Integer $level ECC level + @returns Code data capacity + */ public static function getDataLength($version, $level) { return self::$capacity[$version][QRCAP_WORDS] - self::$capacity[$version][QRCAP_EC][$level]; } //---------------------------------------------------------------------- + /** Calculates count of Error Correction Codes for specified code configuration. + @param Integer $version Code version + @param Integer $level ECC level + @returns ECC code count + */ public static function getECCLength($version, $level) { return self::$capacity[$version][QRCAP_EC][$level]; } //---------------------------------------------------------------------- + /** Gets pixel width of code. + @param Integer $version Code version + @returns Code width, in pixels + */ public static function getWidth($version) { return self::$capacity[$version][QRCAP_WIDTH]; } //---------------------------------------------------------------------- + /** Gets reminder chars length. + @param Integer $version Code version + @returns Reminder length + */ public static function getRemainder($version) { return self::$capacity[$version][QRCAP_REMINDER]; } //---------------------------------------------------------------------- + /** Finds minimal code version capable of hosting specified data length. + @param Integer $size amount of raw data + @param Integer $level ECC level + @returns code version capable of hosting specified amount of data at specified ECC level + */ public static function getMinimumVersion($size, $level) { @@ -435,7 +516,10 @@ } //###################################################################### - + + /** Length bits Table. + \hideinitializer + */ public static $lengthTableBits = array( array(10, 12, 14), array( 9, 11, 13), @@ -485,8 +569,10 @@ } // Error correction code ----------------------------------------------- - // Table of the error correction code (Reed-Solomon block) - // See Table 12-16 (pp.30-36), JIS X0510:2004. + /** Table of the error correction code (Reed-Solomon block). + @see Table 12-16 (pp.30-36), JIS X0510:2004. + \hideinitializer + */ public static $eccTable = array( array(array( 0, 0), array( 0, 0), array( 0, 0), array( 0, 0)), @@ -563,12 +649,14 @@ // Alignment pattern --------------------------------------------------- - // Positions of alignment patterns. - // This array includes only the second and the third position of the - // alignment patterns. Rest of them can be calculated from the distance - // between them. + /** Positions of alignment patterns. + This array includes only the second and the third position of the + lignment patterns. Rest of them can be calculated from the distance + between them. - // See Table 1 in Appendix E (pp.71) of JIS X0510:2004. + @see Table 1 in Appendix E (pp.71) of JIS X0510:2004. + \hideinitializer + */ public static $alignmentPattern = array( array( 0, 0), @@ -582,13 +670,12 @@ array(24, 50), array(28, 54), array(32, 58), array(26, 54), array(30, 58), //35-40 ); - - /** -------------------------------------------------------------------- - * Put an alignment marker. - * @param frame - * @param width - * @param ox,oy center coordinate of the pattern - */ + //---------------------------------------------------------------------- + /** Puts an alignment marker. + @param frame + @param width + @param ox,oy center coordinate of the pattern + */ public static function putAlignmentMarker(array &$frame, $ox, $oy) { $finder = array( @@ -603,7 +690,7 @@ $xStart = $ox-2; for($y=0; $y<5; $y++) { - QRstr::set($frame, $xStart, $yStart+$y, $finder[$y]); + self::set($frame, $xStart, $yStart+$y, $finder[$y]); } } @@ -646,12 +733,12 @@ } // Version information pattern ----------------------------------------- - - // Version information pattern (BCH coded). - // See Table 1 in Appendix D (pp.68) of JIS X0510:2004. + /** Version information pattern (BCH coded). + size: [QRSPEC_VERSION_MAX - 6] + @see Table 1 in Appendix D (pp.68) of JIS X0510:2004. + \hideinitializer + */ - // size: [QRSPEC_VERSION_MAX - 6] - public static $versionPattern = array( 0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d, 0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9, @@ -669,8 +756,11 @@ return self::$versionPattern[$version -7]; } - // Format information -------------------------------------------------- - // See calcFormatInfo in tests/test_qrspec.c (orginal qrencode c lib) + //---------------------------------------------------------------------- + /** Format information. + @see calcFormatInfo in tests/test_qrspec.c (orginal qrencode c lib) + \hideinitializer + */ public static $formatInfo = array( array(0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976), @@ -691,16 +781,16 @@ } // Frame --------------------------------------------------------------- - // Cache of initial frames. - + + /** Cache of initial frames. */ public static $frames = array(); - /** -------------------------------------------------------------------- - * Put a finder pattern. - * @param frame - * @param width - * @param ox,oy upper-left coordinate of the pattern - */ + /** Put a finder pattern. + @param frame + @param width + @param ox,oy upper-left coordinate of the pattern + \hideinitializer + */ public static function putFinderPattern(&$frame, $ox, $oy) { $finder = array( @@ -714,7 +804,7 @@ ); for($y=0; $y<7; $y++) { - QRstr::set($frame, $ox, $oy+$y, $finder[$y]); + self::set($frame, $ox, $oy+$y, $finder[$y]); } } @@ -742,14 +832,14 @@ $setPattern = str_repeat("\xc0", 8); - QRstr::set($frame, 0, 7, $setPattern); - QRstr::set($frame, $width-8, 7, $setPattern); - QRstr::set($frame, 0, $width - 8, $setPattern); + self::set($frame, 0, 7, $setPattern); + self::set($frame, $width-8, 7, $setPattern); + self::set($frame, 0, $width - 8, $setPattern); // Format info $setPattern = str_repeat("\x84", 9); - QRstr::set($frame, 0, 8, $setPattern); - QRstr::set($frame, $width - 8, 8, $setPattern, 8); + self::set($frame, 0, 8, $setPattern); + self::set($frame, $width - 8, 8, $setPattern, 8); $yOffset = $width - 8; @@ -797,6 +887,10 @@ } //---------------------------------------------------------------------- + /** Dumps debug HTML of frame. + @param Array $frame code frame + @param Boolean $binary_mode in binary mode only contents is dumped, without styling + */ public static function debug($frame, $binary_mode = false) { if ($binary_mode) { @@ -806,11 +900,7 @@ $frameLine = join('██', explode('1', $frameLine)); } - ?> - - .m { background-color: white; } '; echo '



        '; echo join("
        ", $frame); echo '






'; @@ -818,45 +908,71 @@ } else { foreach ($frame as &$frameLine) { - $frameLine = join(' ', explode("\xc0", $frameLine)); - $frameLine = join('', explode("\xc1", $frameLine)); - $frameLine = join(' ', explode("\xa0", $frameLine)); - $frameLine = join('', explode("\xa1", $frameLine)); - $frameLine = join('', explode("\x84", $frameLine)); //format 0 - $frameLine = join('', explode("\x85", $frameLine)); //format 1 - $frameLine = join('', explode("\x81", $frameLine)); //special bit - $frameLine = join(' ', explode("\x90", $frameLine)); //clock 0 - $frameLine = join('', explode("\x91", $frameLine)); //clock 1 - $frameLine = join(' ', explode("\x88", $frameLine)); //version - $frameLine = join('', explode("\x89", $frameLine)); //version - $frameLine = join('♦', explode("\x01", $frameLine)); - $frameLine = join('⋅', explode("\0", $frameLine)); - } - ?> - - "; + $frameLine = strtr($frameLine, array( + "\xc0" => ' ', //marker 0 + "\xc1" => '', //marker 1 + "\xa0" => ' ', //submarker 0 + "\xa1" => '', //submarker 1 + "\x84" => 'F', //format 0 + "\x85" => 'f', //format 1 + "\x81" => 'S', //special bit + "\x90" => 'C', //clock 0 + "\x91" => 'c', //clock 1 + "\x88" => ' ', //version 0 + "\x89" => '', //version 1 + "\x03" => '1', // 1 + "\x02" => '0', // 0 + )); + } + + echo ''; + + echo ""; echo join("
", $frame); - echo "
"; + echo "
Legend:
"; + echo '1 - data 1
'; + echo '0 - data 0
'; + echo '  - marker bit 0
'; + echo ' - marker bit 1
'; + echo '  - secondary marker bit 0
'; + echo ' - secondary marker bit 1
'; + echo 'F - format bit 0
'; + echo 'f - format bit 1
'; + echo 'S - special bit
'; + echo 'C - clock bit 0
'; + echo 'c - clock bit 1
'; + echo '  - version bit 0
'; + echo ' - version bit 1
'; + echo "
"; } } //---------------------------------------------------------------------- + /** Serializes frame. + Create compressed, serialized version of frame. + @param Array $frame Code Frame + @return String binary compresed Code Frame + */ public static function serial($frame) { return gzcompress(join("\n", $frame), 9); } //---------------------------------------------------------------------- + /** Deserializes frame. + Loads frame from serialized compressed binary + @param String $code binary, GZipped, serialized frame + @return Array Code Frame array + */ public static function unserial($code) { return explode("\n", gzuncompress($code)); @@ -891,6 +1007,23 @@ } //---------------------------------------------------------------------- + /** Sets code frame with speciffied code. + @param Array $frame target frame (modified by reference) + @param Integer $x X-axis position of replacement + @param Integer $y Y-axis position of replacement + @param Byte $repl replacement string + @param Integer $replLen (optional) replacement string length, when __Integer__ > 1 subset of given $repl is used, when __false__ whole $repl is used + */ + public static function set(&$frame, $x, $y, $repl, $replLen = false) { + $frame[$y] = substr_replace($frame[$y], ($replLen !== false)?substr($repl,0,$replLen):$repl, $x, ($replLen !== false)?$replLen:strlen($repl)); + } + + //---------------------------------------------------------------------- + + /** @name Reed-Solomon related shorthand getters. + Syntax-sugar to access code speciffication by getter name, not by spec array field. + */ + /** @{*/ public static function rsBlockNum($spec) { return $spec[0] + $spec[3]; } public static function rsBlockNum1($spec) { return $spec[0]; } public static function rsDataCodes1($spec) { return $spec[1]; } @@ -900,15 +1033,19 @@ public static function rsEccCodes2($spec) { return $spec[2]; } public static function rsDataLength($spec) { return ($spec[0] * $spec[1]) + ($spec[3] * $spec[4]); } public static function rsEccLength($spec) { return ($spec[0] + $spec[3]) * $spec[2]; } - - } - - - -//---- qrimage.php ----------------------------- - - - + /** @}*/ + } + + /** @}*/ + + + + + +//---- qrimage.php ----------------------------- + + + /* * PHP QR Code encoder @@ -916,7 +1053,7 @@ * Image output of code using GD2 * * PHP QR Code is distributed under LGPL 3 - * Copyright (C) 2010 Dominik Dzienia + * Copyright (C) 2010-2013 Dominik Dzienia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -935,9 +1072,27 @@ define('QR_IMAGE', true); + + /** @defgroup OutputGroup Standard API Output + Provide simple Raster & Vector output */ + + /** @addtogroup OutputGroup */ + /** @{ */ + + /** Image rendering helper. + Uses GD2 image to render QR Code into image file */ class QRimage { //---------------------------------------------------------------------- + /** + Creates PNG image. + @param Array $frame frame containing code + @param String $filename (optional) output file name, if __false__ outputs to browser with required headers + @param Integer $pixelPerPoint (optional) pixel size, multiplier for each 'virtual' pixel + @param Integer $outerFrame (optional) code margin (silent zone) in 'virtual' pixels + @param Boolean $saveandprint (optional) if __true__ code is outputed to browser and saved to file, otherwise only saved to file. It is effective only if $outfile is specified. + */ + public static function png($frame, $filename = false, $pixelPerPoint = 4, $outerFrame = 4,$saveandprint=FALSE) { $image = self::image($frame, $pixelPerPoint, $outerFrame); @@ -959,6 +1114,15 @@ } //---------------------------------------------------------------------- + /** + Creates JPEG image. + @param Array $frame frame containing code + @param String $filename (optional) output file name, if __false__ outputs to browser with required headers + @param Integer $pixelPerPoint (optional) pixel size, multiplier for each 'virtual' pixel + @param Integer $outerFrame (optional) code margin (silent zone) in 'virtual' pixels + @param Integer $q (optional) JPEG compression level (__0__ .. __100__) + */ + public static function jpg($frame, $filename = false, $pixelPerPoint = 8, $outerFrame = 4, $q = 85) { $image = self::image($frame, $pixelPerPoint, $outerFrame); @@ -974,7 +1138,14 @@ } //---------------------------------------------------------------------- - private static function image($frame, $pixelPerPoint = 4, $outerFrame = 4) + /** + Creates generic GD2 image object + @param Array $frame frame containing code + @param Integer $pixelPerPoint (optional) pixel size, multiplier for each 'virtual' pixel + @param Integer $outerFrame (optional) code margin (silent zone) in 'virtual' pixels + @return __Resource__ GD2 image resource (remember to ImageDestroy it!) + */ + public static function image($frame, $pixelPerPoint = 4, $outerFrame = 4) { $h = count($frame); $w = strlen($frame[0]); @@ -1003,14 +1174,16 @@ return $target_image; } - } - - - -//---- qrinput.php ----------------------------- - - - + } + + /** @} */ + + + +//---- qrinput.php ----------------------------- + + + /* * PHP QR Code encoder @@ -1021,7 +1194,7 @@ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi * * PHP QR Code is distributed under LGPL 3 - * Copyright (C) 2010 Dominik Dzienia + * Copyright (C) 2010-2013 Dominik Dzienia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -1038,8 +1211,11 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - define('STRUCTURE_HEADER_BITS', 20); + define('STRUCTURE_HEADER_BITS', 20); define('MAX_STRUCTURED_SYMBOLS', 16); + + /** @addtogroup CoreGroup */ + /** @{ */ class QRinputItem { @@ -1738,15 +1914,14 @@ } } - - - - - -//---- qrbitstream.php ----------------------------- - - - + /** @}*/ + + + +//---- qrbitstream.php ----------------------------- + + + /* * PHP QR Code encoder @@ -1757,7 +1932,7 @@ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi * * PHP QR Code is distributed under LGPL 3 - * Copyright (C) 2010 Dominik Dzienia + * Copyright (C) 2010-2013 Dominik Dzienia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -1773,18 +1948,38 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - + + /** @addtogroup CoreGroup */ + /** @{ */ + + /** + PHP bit stream. + Class implementing array of bits (= 1 or 0 ints). Allows to initialize and append + bits from given Integer or array of Bytes. + */ class QRbitstream { + /** + Array containing bit data stream + */ public $data = array(); //---------------------------------------------------------------------- + /** + @return Integer size of byte stream + */ public function size() { return count($this->data); } //---------------------------------------------------------------------- + /** + Allocates bit stream, fills bit data stream with 0's. + This operation is __destructive__, will replace orginal stream contents! + @param Integer $setLength desired target stream size + @return Integer 0 on success, other on failure + */ public function allocate($setLength) { $this->data = array_fill(0, $setLength, 0); @@ -1792,6 +1987,12 @@ } //---------------------------------------------------------------------- + /** + Creates new bit stream from given Integer number. + @param Integer $bits bit count + @param Integer $num integer to convert + @return QRbitstream bit stream object containing first $bits bits from $num in order from LSB to MSB + */ public static function newFromNum($bits, $num) { $bstream = new QRbitstream(); @@ -1811,6 +2012,12 @@ } //---------------------------------------------------------------------- + /** + Creates new bit stream from given byte array. + @param Integer $size size of array + @param Array $data array ob bytes + @return QRbitstream bit stream object containing bit contents of given bytes array + */ public static function newFromBytes($size, $data) { $bstream = new QRbitstream(); @@ -1834,6 +2041,11 @@ } //---------------------------------------------------------------------- + /** + Appends given bit stream at end of this stream. + @param QRbitstream $arg bit stream to be appended + @return Integer status of append operation, 0 when success, -1 when $arg is null + */ public function append(QRbitstream $arg) { if (is_null($arg)) { @@ -1855,6 +2067,12 @@ } //---------------------------------------------------------------------- + /** + Appends bit stream cteated from given Integer number at end of current stream. + @param Integer $bits bit count + @param Integer $num integer to convert + @return Integer status of append operation, status of append operation, 0 when success, -1 otherwise + */ public function appendNum($bits, $num) { if ($bits == 0) @@ -1872,6 +2090,12 @@ } //---------------------------------------------------------------------- + /** + Appends bit stream created from from given byte array at end of current stream. + @param Integer $size size of array + @param Array $data array ob bytes + @return Integer status of append operation, status of append operation, 0 when success, -1 otherwise + */ public function appendBytes($size, $data) { if ($size == 0) @@ -1889,6 +2113,10 @@ } //---------------------------------------------------------------------- + /** + Converts current bit stream into byte array. + @returns Array array of bytes + */ public function toByte() { @@ -1927,14 +2155,15 @@ } } - - - - -//---- qrsplit.php ----------------------------- - - - + + /** @}*/ + + + +//---- qrsplit.php ----------------------------- + + + /* * PHP QR Code encoder @@ -1945,7 +2174,7 @@ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi * * PHP QR Code is distributed under LGPL 3 - * Copyright (C) 2010 Dominik Dzienia + * Copyright (C) 2010-2013 Dominik Dzienia * * The following data / specifications are taken from * "Two dimensional symbol -- QR-code -- Basic Specification" (JIS X0510:2004) @@ -1967,6 +2196,11 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + + /** @addtogroup CoreGroup */ + /** @{ */ + + /** Input stream splitter. */ class QRsplit { public $dataStr = ''; @@ -2245,14 +2479,16 @@ return $split->splitString(); } - } - - - -//---- qrrscode.php ----------------------------- - - - + } + + /** @} */ + + + +//---- qrrscode.php ----------------------------- + + + /* * PHP QR Code encoder @@ -2266,7 +2502,7 @@ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi * * PHP QR Code is distributed under LGPL 3 - * Copyright (C) 2010 Dominik Dzienia + * Copyright (C) 2010-2013 Dominik Dzienia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -2283,22 +2519,40 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + /** @addtogroup CoreGroup */ + /** @{ */ + + /** Reed-Solomon encoder item */ class QRrsItem { - public $mm; // Bits per symbol - public $nn; // Symbols per block (= (1<= $this->nn) { $x -= $this->nn; @@ -2309,7 +2563,15 @@ } //---------------------------------------------------------------------- - public static function init_rs_char($symsize, $gfpoly, $fcr, $prim, $nroots, $pad) + /** Encoder initialisation + @param Integer $symsize symbol size, bit count (1..8) + @param Integer $gfpoly Galois Field Polynomial + @param Integer $fcr First consecutive root + @param Integer $prim Primitive element + @param Integer $nroots Number of generator roots = number of parity symbols + @param Integer $pad Padding bytes in shortened block + */ + public static function init_rs_char($symsize, $gfpoly, $fcr, $prim, $nroots, $pad) { // Common code for intializing a Reed-Solomon control block (char or int symbols) // Copyright 2004 Phil Karn, KA9Q @@ -2395,6 +2657,10 @@ } //---------------------------------------------------------------------- + /** Appends char into encoder + @param String input + @param Array parity table + */ public function encode_rs_char($data, &$parity) { $MM =& $this->mm; @@ -2438,12 +2704,21 @@ } //########################################################################## - + /** Reed-Solomon encoder */ class QRrs { + /** Encoder items array */ public static $items = array(); //---------------------------------------------------------------------- + /** Encoder initialisation + @param Integer $symsize symbol size, bit count (1..8) + @param Integer $gfpoly Galois Field Polynomial + @param Integer $fcr First consecutive root + @param Integer $prim Primitive element + @param Integer $nroots Number of generator roots = number of parity symbols + @param Integer $pad Padding bytes in shortened block + */ public static function init_rs($symsize, $gfpoly, $fcr, $prim, $nroots, $pad) { foreach(self::$items as $rs) { @@ -2462,14 +2737,16 @@ return $rs; } - } - - - -//---- qrmask.php ----------------------------- - - - + } + + /** @}*/ + + + +//---- qrmask.php ----------------------------- + + + /* * PHP QR Code encoder @@ -2480,7 +2757,7 @@ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi * * PHP QR Code is distributed under LGPL 3 - * Copyright (C) 2010 Dominik Dzienia + * Copyright (C) 2010-2013 Dominik Dzienia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -2497,11 +2774,16 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + + define('N1', 3); define('N2', 3); define('N3', 40); define('N4', 10); + /** @addtogroup CoreGroup */ + /** @{ */ + class QRmask { public $runLength = array(); @@ -2643,7 +2925,7 @@ $b += (int)(ord($d[$y][$x]) & 1); } } - + return $b; } @@ -2798,25 +3080,619 @@ //---------------------------------------------------------------------- } - - - - -//---- qrencode.php ----------------------------- - - - + + /** @}*/ + + + +//---- qrarea.php ----------------------------- + + + /* * PHP QR Code encoder * - * Main encoder classes. + * Area finding for SVG and CANVAS output * * Based on libqrencode C library distributed under LGPL 2.1 * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi * * PHP QR Code is distributed under LGPL 3 - * Copyright (C) 2010 Dominik Dzienia + * Copyright (C) 2010-2013 Dominik Dzienia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + // N + // W E + // S + + define('QR_AREA_N', 0); + define('QR_AREA_E', 1); + define('QR_AREA_S', 2); + define('QR_AREA_W', 3); + + define('QR_AREA_X', 0); + define('QR_AREA_Y', 1); + + define('QR_AREA_TRACKER', 0); + define('QR_AREA_PATH', 1); + define('QR_AREA_POINT', 2); + define('QR_AREA_RECT', 3); + define('QR_AREA_LSHAPE', 4); + + /** @addtogroup OutputGroup */ + /** @{ */ + + class QRareaGroup { + public $total = 0; + public $vertical = false; + public $horizontal = false; + public $points = array(); + public $id = 0; + public $paths = array(); + + //---------------------------------------------------------------------- + public function __construct($selfId, $sx, $sy) + { + $this->total = 1; + $this->points = array(array($sx,$sy,false)); + $this->id = $selfId; + } + + } + + //########################################################################## + + class QRarea { + + public $width = 0; + private $tab = array(); + private $tab_edges = array(); + private $groups = array(); + private $curr_group = 0; + public $paths = array(); + + + //---------------------------------------------------------------------- + public function __construct($source_tab) + { + $py = 0; + $this->width = count($source_tab); + $this->tab = array(); + $this->tab_edges = array(); + $this->paths = array(); + + foreach ($source_tab as $line) { + $arr = array(); + $arr_edge = array(); + $px=0; + + foreach (str_split($line) as $item) { + + if ($py<7 && $px<7) + $item = 0; + + if ($py<7 && $px>=($this->width-7)) + $item = 0; + + if ($py>=($this->width-7) && $px<7) + $item = 0; + + $arr[] = (int)$item; + $arr_edge[] = array(false, false, false, false); + + $px++; + } + + $this->tab[] = $arr; + $this->tab_edges[] = $arr_edge; + $py++; + } + + $this->paths[] = array(QR_AREA_TRACKER, 0,0); + $this->paths[] = array(QR_AREA_TRACKER, 0,($this->width-7)); + $this->paths[] = array(QR_AREA_TRACKER, ($this->width-7),0); + + $this->groups = array(); + $this->curr_group = 1; + } + + //---------------------------------------------------------------------- + public function getGroups() + { + return $this->groups; + } + + //---------------------------------------------------------------------- + public function getPaths() + { + return $this->paths; + } + + //---------------------------------------------------------------------- + public function getWidth() + { + return $this->width; + } + + //---------------------------------------------------------------------- + public function dumpTab() + { + echo ""; + echo ""; + + $colorTab = array(); + + foreach($this->tab as $line) { + foreach($line as $item) { + if (!isset($colorTab[$item])) { + $colorTab[$item] = 'hsl('.mt_rand(0, 360).', '.floor((mt_rand(0, 25))+75).'%, 50%)'; + } + } + } + + foreach($this->tab as $line) { + echo ""; + foreach($line as $item) { + if ($item == 0) { + echo ""; + } else { + echo ""; + } + } + echo ""; + } + echo "
 ".$item."
"; + } + + //---------------------------------------------------------------------- + public function dumpEdges() + { + $style_off = '1px dotted silver;'; + $style_on = '3px solid red;'; + + $colorAlloc = array(); + + echo ""; + $py = 0; + foreach($this->tab_edges as $line) { + $px = 0; + echo ""; + foreach($line as $item) { + + $styles = 'border-top:'; + if ($item[QR_AREA_N]) + $styles .= $style_on; + else $styles .= $style_off; + + $styles .= 'border-bottom:'; + if ($item[QR_AREA_S]) + $styles .= $style_on; + else $styles .= $style_off; + + $styles .= 'border-right:'; + if ($item[QR_AREA_E]) + $styles .= $style_on; + else $styles .= $style_off; + + $styles .= 'border-left:'; + if ($item[QR_AREA_W]) + $styles .= $style_on; + else $styles .= $style_off; + + $color = ''; + $grp = $this->tab[$py][$px]; + + if ($grp>0) { + if (!isset($colorAlloc[$grp])) { + $colorAlloc[$grp] = 'hsl('.mt_rand(0, 360).', '.floor((mt_rand(0, 25))+75).'%, 50%)'; + } + + $color = 'background:'.$colorAlloc[$grp]; + } + + if ($grp == 0) + $grp = ' '; + + echo ""; + $px++; + } + echo ""; + $py++; + } + echo "
".$grp."
"; + } + + //---------------------------------------------------------------------- + private static function rle(&$stringData) + { + $outArray = array(); + $symbolArray = str_split($stringData); + $last = ''; + $run = 1; + + while (count($symbolArray) > 0) { + $symbol = array_shift($symbolArray); + + if ($symbol != $last) { + if ($run > 1) + $outArray[] = $run; + + if ($last != '') + $outArray[] = $last; + + $run = 1; + $last = $symbol; + } else { + $run++; + } + } + + if ($run > 1) + $outArray[] = $run; + + $outArray[] = $last; + + $stringData = $outArray; + } + + + //---------------------------------------------------------------------- + private function getAt($posx, $posy) + { + if (($posx<0)||($posy<0)||($posx>=$this->width)||($posy>=$this->width)) + return 0; + + return $this->tab[$posy][$posx]; + } + + //---------------------------------------------------------------------- + private function getOnElem($elem, $deltax = 0, $deltay = 0) + { + $posx = $elem[0]+$deltax; + $posy = $elem[1]+$deltay; + + if (($posx<0)||($posy<0)||($posx>=$this->width)||($posy>=$this->width)) + return 0; + + return $this->tab[$posy][$posx]; + } + + //---------------------------------------------------------------------- + private function addGroupElement($groupId, $h, $v, $sx, $sy) + { + $this->groups[$groupId]->total++; + if ($h) + $this->groups[$groupId]->horizontal = true; + if ($v) + $this->groups[$groupId]->vertical = true; + $this->groups[$groupId]->points[] = array($sx, $sy, false); + } + + //---------------------------------------------------------------------- + public function detectGroups() + { + for ($sy = 0; $sy < $this->width; $sy++) { + for ($sx = 0; $sx < $this->width; $sx++) { + + if ($this->tab[$sy][$sx] == 1) { // non-allocated + + $gid_left = 0; + $gid_top = 0; + + $grouped = false; + + if ($sx>0) { + + $gid_left = $this->tab[$sy][$sx-1]; // previous on left + + if ($gid_left > 1) { // if already in group + $this->tab[$sy][$sx] = $gid_left; + $grouped = true; + $this->addGroupElement($gid_left, true, false, $sx, $sy); + } + } + + if ($sy > 0) { + + $gid_top = $this->tab[$sy-1][$sx]; // previous on top + + if ($gid_top > 1) { //if in group + if (!$grouped) { // and not grouped + + $this->tab[$sy][$sx] = $gid_top; + $grouped = true; + + $this->addGroupElement($gid_top, false, true, $sx, $sy); + + } else if($gid_top != $gid_left) { // was in left group + + $grouped = true; + + $this->groups[$gid_top]->vertical = true; + $this->groups[$gid_top]->horizontal = true; + + $this->groups[$gid_top]->total = $this->groups[$gid_top]->total + $this->groups[$gid_left]->total; + + foreach($this->groups[$gid_left]->points as $elem) + $this->tab[$elem[1]][$elem[0]] = $gid_top; + + $this->groups[$gid_top]->points = array_values(array_merge($this->groups[$gid_top]->points, $this->groups[$gid_left]->points)); + unset($this->groups[$gid_left]); + + //refarb group + } + } + } + + if (!$grouped) { + $this->curr_group++; + $this->tab[$sy][$sx] = $this->curr_group; + $this->groups[$this->curr_group] = new QRareaGroup($this->curr_group, $sx, $sy); + } + + } + } + } + } + + //---------------------------------------------------------------------- + private function detectSquare($group) + { + $max_x = 0; + $max_y = 0; + $min_x = $this->width; + $min_y = $this->width; + + foreach($group->points as $elem) { + $min_x = min($min_x, $elem[QR_AREA_X]); + $max_x = max($max_x, $elem[QR_AREA_X]); + $min_y = min($min_y, $elem[QR_AREA_Y]); + $max_y = max($max_y, $elem[QR_AREA_Y]); + } + + return array($min_x, $min_y, $max_x+1, $max_y+1); + } + + //---------------------------------------------------------------------- + public function detectAreas() + { + $squares = array(); + $points = array(); + $lshapes = array(); + + foreach ($this->groups as $groupId=>&$group) { + if ($group->total > 3) { + + if ((!$group->vertical)||(!$group->horizontal)) { + + $squareCoord = $this->detectSquare($group); + array_unshift($squareCoord, QR_AREA_RECT); + + $this->paths[] = $squareCoord; + + } else { + + $this->detectPaths($group); + unset($group->points); + + foreach($group->paths as &$path) + self::rle($path[2]); + + $this->paths[] = array(QR_AREA_PATH, $group->paths); + } + } else if (($group->total == 3)&&($group->vertical)&&($group->horizontal)) { + $squareCoord = $this->detectSquare($group); + $variant = 0; + + if ($this->getOnElem($squareCoord, 0, 0) != $group->id) + $variant = 0; + + if ($this->getOnElem($squareCoord, 1, 0) != $group->id) + $variant = 1; + + if ($this->getOnElem($squareCoord, 0, 1) != $group->id) + $variant = 2; + + if ($this->getOnElem($squareCoord, 1, 1) != $group->id) + $variant = 3; + + $lshapes[] = $squareCoord[QR_AREA_X]; + $lshapes[] = $squareCoord[QR_AREA_Y]; + $lshapes[] = $variant; + + } else if ($group->total >= 2) { + $squareCoord = $this->detectSquare($group); + $squares = array_merge($squares, $squareCoord); + } else if ($group->total == 1) { + $points[] = $group->points[0][0]; + $points[] = $group->points[0][1]; + } + } + + if (count($points) > 0) { + array_unshift($points, QR_AREA_POINT); + $this->paths[] = $points; + } + + if (count($squares) > 0) { + array_unshift($squares, QR_AREA_RECT); + $this->paths[] = $squares; + } + + if (count($lshapes) > 0) { + array_unshift($lshapes, QR_AREA_LSHAPE); + $this->paths[] = $lshapes; + } + } + + //---------------------------------------------------------------------- + private function reserveEdgeOnElem($elem, $edgeNo) + { + $this->tab_edges[$elem[QR_AREA_Y]][$elem[QR_AREA_X]][$edgeNo] = true; + } + + //---------------------------------------------------------------------- + private function reserveEdge($px, $py, $edgeNo) + { + $this->tab_edges[$py][$px][$edgeNo] = true; + } + + //---------------------------------------------------------------------- + private function markAdjacentEdges($group) + { + foreach($group->points as $elem) { + if ($this->getOnElem($elem, -1, 0) == $group->id) + $this->reserveEdgeOnElem($elem, QR_AREA_W); + + if ($this->getOnElem($elem, +1, 0) == $group->id) + $this->reserveEdgeOnElem($elem, QR_AREA_E); + + if ($this->getOnElem($elem, 0, -1) == $group->id) + $this->reserveEdgeOnElem($elem, QR_AREA_N); + + if ($this->getOnElem($elem, 0, +1) == $group->id) + $this->reserveEdgeOnElem($elem, QR_AREA_S); + } + } + + //---------------------------------------------------------------------- + private function detectPaths(&$group) + { + $this->markAdjacentEdges($group); + + $elem = $group->points[0]; + $waylist = $this->findPath($group, $elem[QR_AREA_X], $elem[QR_AREA_Y]); + $group->paths[] = array($elem[QR_AREA_X], $elem[QR_AREA_Y], $waylist); + + $tab = array(); + foreach($group->points as $elem) { + + $edgeTab = $this->tab_edges[$elem[QR_AREA_Y]][$elem[QR_AREA_X]]; + + if (!( $edgeTab[QR_AREA_N] + && $edgeTab[QR_AREA_E] + && $edgeTab[QR_AREA_S] + && $edgeTab[QR_AREA_W])) { + + if (!$edgeTab[QR_AREA_S]) { + + $waylistw = $this->findPath($group, $elem[QR_AREA_X], $elem[QR_AREA_Y]+1); + $group->paths[] = array($elem[QR_AREA_X], $elem[QR_AREA_Y]+1, $waylistw); + } + } + } + + } + + //---------------------------------------------------------------------- + private function findPath($group, $sx, $sy) + { + $px = $sx; + $py = $sy; + + $waylist = ''; + $dir = ''; + $lastdir = ''; + + $moves = array( + // magic :) + 0=>'', 1=>'L', 2=>'T', 3=>'L', 4=>'B', 5=>'B', 6=>'B,T', 7=>'B' + ,8=>'R', 9=>'R,L', 10=>'T', 11=>'L',12=>'R',13=>'R',14=>'T',15=>'' + ); + + do + { + $Q = ($this->getAt($px-1, $py-1) == $group->id)?1:0; + $Q += ($this->getAt($px, $py-1) == $group->id)?2:0; + $Q += ($this->getAt($px-1, $py) == $group->id)?4:0; + $Q += ($this->getAt($px, $py) == $group->id)?8:0; + + if ($moves[$Q] == '') + throw new Exception('It should NEVER happened!'); + + $move_expl = explode(',', $moves[$Q]); + $have_way = false; + + $dir = ''; + + while ((count($move_expl) > 0)&&($have_way == false)) { + $way = array_shift($move_expl); + + if (($have_way==false)&&($way=='R')&&($this->tab_edges[$py][$px][QR_AREA_N]==false)) { + $have_way = true; + $dir = $way; + $this->reserveEdge($px, $py, QR_AREA_N); + $px++; + } + + if (($have_way==false)&&($way=='B')&&($this->tab_edges[$py][$px-1][QR_AREA_E]==false)) { + $have_way = true; + $dir = $way; + $this->reserveEdge($px-1, $py, QR_AREA_E); + $py++; + } + + if (($have_way==false)&&($way=='L')&&($this->tab_edges[$py-1][$px-1][QR_AREA_S]==false)) { + $have_way = true; + $dir = $way; + $this->reserveEdge($px-1, $py-1, QR_AREA_S); + $px--; + } + + if (($have_way==false)&&($way=='T')&&($this->tab_edges[$py-1][$px][QR_AREA_W]==false)) { + $have_way = true; + $dir = $way; + $this->reserveEdge($px, $py-1, QR_AREA_W); + $py--; + } + } + + $waylist .= $dir; + + } while (!(($px==$sx)&&($py==$sy))); + + return $waylist; + } + } + + /** @} */ + + + +//---- qrcanvas.php ----------------------------- + + + + +/* + * PHP QR Code encoder + * + * CANVAS output + * + * Based on libqrencode C library distributed under LGPL 2.1 + * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi + * + * PHP QR Code is distributed under LGPL 3 + * Copyright (C) 2010-2013 Dominik Dzienia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -2833,12 +3709,575 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + /** @addtogroup OutputGroup */ + /** @{ */ + + class QRcanvasOutput extends QRarea { + + public function __construct($source_tab) + { + parent::__construct($source_tab); + } + + //---------------------------------------------------------------------- + public static function encodeNum($num) + { + $addTab = array(0=>'', 1=>'z', 2=>'Z', 3=>'+'); + $map = '0123456789abcdefghijklmnopqrstuvwxyABCDEFGHIJKLMNOPQRSTUVWXY'; + $mapPos = $num % 60; + $mapAdd = (int)($num / 60); + + return $addTab[$mapAdd].$map[$mapPos]; + } + + //---------------------------------------------------------------------- + public static function compact_path(&$pathTab) + { + if (count($pathTab) == 0) { + $pathTab = ''; + } else { + $pathTab = count($pathTab).','.join(',', $pathTab); + } + } + + //---------------------------------------------------------------------- + public static function compact_points(&$pointsTab) + { + if (count($pointsTab) == 0) { + $pointsTab = ''; + } else { + $compacted = ''; + foreach ($pointsTab as $point) + $compacted .= self::encodeNum($point); + $pointsTab = $compacted; + } + } + + //---------------------------------------------------------------------- + public static function compactCanvasCommands($ops) + { + $accumulated = array(); + + $accumulated['SR'] = array(); + $accumulated['WR'] = array(); + $accumulated['SP'] = array(); + $accumulated['WP'] = array(); + $accumulated['SB'] = array(); + $accumulated['WB'] = array(); + $accumulated['SO'] = array(); + + while (count($ops) > 0) { + $color = array_shift($ops); + $opcode = array_shift($ops); + + if (($opcode == 'R') || ($opcode == 'P')) { + + do { + $num = array_shift($ops); + if (is_int($num)) { + $accumulated[$color.$opcode][] = $num; + } else { + array_unshift($ops, $num); + } + + } while ((count($ops) > 0)&&(is_int($num))); + + + } else if ($opcode == 'B') { + + array_shift($ops); + + $px = array_shift($ops); + $py = array_shift($ops); + + array_shift($ops); + + $conftab = array(); + $num = array_shift($ops); + + while ((count($ops) > 0)&&(!($num === 'E'))) { + $conftab[] = $num; + $num = array_shift($ops); + } + + $cc = count($conftab); + $deltas = ''; + + $lastposx = $px; + $lastposy = $py; + + for($pos=0;$pos <$cc; $pos+=2) { + + $dx = $lastposx - $conftab[$pos]; + $dy = $lastposy - $conftab[$pos+1]; + + $lastposx = $conftab[$pos]; + $lastposy = $conftab[$pos+1]; + + if ($dx < 0) { + $deltas .= chr(ord('a')-1-$dx); + } else if ($dx > 0) { + $deltas .= chr(ord('A')-1+$dx); + } else { + $deltas .= '0'; + } + + if ($dy < 0) { + $deltas .= chr(ord('a')-1-$dy); + } else if ($dy > 0) { + $deltas .= chr(ord('A')-1+$dy); + } else { + $deltas .= '0'; + } + + } + + $deltas = strtr($deltas, array( + '00'=>'1', + 'aa'=>'2', + 'aA'=>'3', + 'Aa'=>'4', + 'AA'=>'5', + 'aB'=>'6', + 'Ab'=>'7', + 'bA'=>'8', + 'Ba'=>'9' + )); + + $accumulated[$color.$opcode][] = join(',', array($px, $py, $deltas)); + } else if ($opcode == 'O') { + $px = array_shift($ops); + $py = array_shift($ops); + + $accumulated[$color.$opcode][] = join(',', array($px, $py)); + } + } + + self::compact_points($accumulated['SR']); + self::compact_points($accumulated['WR']); + self::compact_points($accumulated['SP']); + self::compact_points($accumulated['WP']); + + self::compact_path($accumulated['SB']); + self::compact_path($accumulated['WB']); + + if (count($accumulated['SO']) > 0) + $accumulated['SO'] = join(',',$accumulated['SO']); + else $accumulated['SO'] = ''; + + $mapping = array( + 'SO'=>'O', + 'SB'=>'B', + 'WB'=>'b', + 'SR'=>'R', + 'WR'=>'r', + 'SP'=>'P', + 'WP'=>'p' + ); + + $whole = array(); + + foreach($mapping as $key=>$symb) { + if ($accumulated[$key]!='') + $whole[] = $symb.','.$accumulated[$key]; + } + + return join(',', $whole); + } + + + //---------------------------------------------------------------------- + public function getCanvasOps() + { + $ops = array(); + + foreach ($this->paths as $path) { + switch ($path[0]) { + case QR_AREA_PATH: + $pNum = 0; + + foreach($path[1] as $pathDetails) { + if ($pNum == 0) { + $ops[] = 'S'; + } else if ($pNum > 0) { + $ops[] = 'W'; + } + + $ops[] = 'B'; + + $px = array_shift($pathDetails); + $py = array_shift($pathDetails); + + $ops[] = 'M'; + $ops[] = $px; + $ops[] = $py; + + $rle_steps = array_shift($pathDetails); + + $lastOp = ''; + + while(count($rle_steps) > 0) { + + $delta = 1; + + $operator = array_shift($rle_steps); + if (($operator != 'R') && ($operator != 'L') && ($operator != 'T') && ($operator != 'B')) { + $delta = (int)$operator; + $operator = array_shift($rle_steps); + } + + if ($operator == 'R') $px += $delta; + if ($operator == 'L') $px -= $delta; + if ($operator == 'T') $py -= $delta; + if ($operator == 'B') $py += $delta; + + if ($lastOp != 'T') + $ops[] = 'T'; + + $ops[] = $px; + $ops[] = $py; + + $lastOp = 'T'; + } + + $ops[] = 'E'; + + $pNum++; + } + + break; + case QR_AREA_POINT: + + $symb = array_shift($path); + + $ops[] = 'S'; + + $lastOp = ''; + + while(count($path) > 0) { + $px = array_shift($path); + $py = array_shift($path); + + if ($lastOp != 'P') + $ops[] = 'P'; + + $ops[] = $px; + $ops[] = $py; + + $lastOp = 'P'; + } + + break; + + case QR_AREA_RECT: + + $symb = array_shift($path); + + $ops[] = 'S'; + + $lastOp = ''; + + while(count($path) > 0) { + $px = array_shift($path); + $py = array_shift($path); + $ex = array_shift($path); + $ey = array_shift($path); + + if ($lastOp != 'R') + $ops[] = 'R'; + + $ops[] = $px; + $ops[] = $py; + $ops[] = $ex-$px; + $ops[] = $ey-$py; + + $lastOp = 'R'; + } + + break; + + case QR_AREA_LSHAPE: + + $symb = array_shift($path); + + while(count($path) > 0) { + $px = array_shift($path); + $py = array_shift($path); + $mode = (int)array_shift($path); + + $pxd = ($mode % 2)?1:0; + $pyd = ($mode > 1)?1:0; + + $ops[] = 'S'; + $ops[] = 'R'; + $ops[] = $px; + $ops[] = $py; + $ops[] = 2; + $ops[] = 2; + + $ops[] = 'W'; + $ops[] = 'P'; + $ops[] = $px+$pxd; + $ops[] = $py+$pyd; + } + + break; + + case QR_AREA_TRACKER: + + $symb = array_shift($path); + + $px = array_shift($path); + $py = array_shift($path); + + $ops[] = 'S'; + $ops[] = 'O'; + $ops[] = $px; + $ops[] = $py; + + break; + } + } + + return self::compactCanvasCommands($ops); + } + } + + /** @} */ + + + + +//---- qrsvg.php ----------------------------- + + + + +/* + * PHP QR Code encoder + * + * SVG output support + * + * Based on libqrencode C library distributed under LGPL 2.1 + * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi + * + * PHP QR Code is distributed under LGPL 3 + * Copyright (C) 2010-2013 Dominik Dzienia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + /** @addtogroup OutputGroup */ + /** @{ */ + + class QRsvgOutput extends QRarea { + + public function __construct($source_tab) + { + parent::__construct($source_tab); + } + + //---------------------------------------------------------------------- + public function mapX($px) + { + return $px; + } + + //---------------------------------------------------------------------- + public function mapY($py) + { + return $py; + } + + //---------------------------------------------------------------------- + public function getRawSvg() + { + $lib = array(); + $svg = array(); + + $aggregate_paths = array(); + + foreach ($this->paths as $path) { + switch ($path[0]) { + case QR_AREA_PATH: + $pNum = 0; + + foreach($path[1] as $pathDetails) { + + $px = array_shift($pathDetails); + $py = array_shift($pathDetails); + $rle_steps = array_shift($pathDetails); + + $aggregate_add = 'M'.$px.','.$py.' '; + + while(count($rle_steps) > 0) { + + $delta = 1; + + $operator = array_shift($rle_steps); + if (($operator != 'R') && ($operator != 'L') && ($operator != 'T') && ($operator != 'B')) { + $delta = (int)$operator; + $operator = array_shift($rle_steps); + } + + if ($operator == 'R') $aggregate_add .= 'h'.$delta; + if ($operator == 'L') $aggregate_add .= 'h-'.$delta; + if ($operator == 'T') $aggregate_add .= 'v-'.$delta; + if ($operator == 'B') $aggregate_add .= 'v'.$delta; + } + + $aggregate_paths[] = $aggregate_add; + + $pNum++; + } + + break; + case QR_AREA_POINT: + + $symb = array_shift($path); + + while(count($path) > 0) { + $px = array_shift($path); + $py = array_shift($path); + + $aggregate_paths[] = 'M'.$px.','.$py.' v1h1v-1h-1'; + } + + break; + + case QR_AREA_RECT: + + $symb = array_shift($path); + + while(count($path) > 0) { + $px = array_shift($path); + $py = array_shift($path); + $ex = array_shift($path); + $ey = array_shift($path); + + $w = $ex-$px; + $h = $ey-$py; + + $aggregate_paths[] = 'M'.$px.','.$py.' h'.$w.'v'.$h.'h-'.$w.'v-'.$h; + } + + break; + + case QR_AREA_LSHAPE: + + $symb = array_shift($path); + + $l_shapes[0] = 'm1,0h1v2h-2v-1h1z'; + $l_shapes[1] = 'h1v1h1v1h-2z'; + $l_shapes[2] = 'h2v2h-1v-1h-1z'; + $l_shapes[3] = 'h2v1h-1v1h-1z'; + + while(count($path) > 0) { + $px = array_shift($path); + $py = array_shift($path); + $mode = (int)array_shift($path); + + $aggregate_paths[] = 'M'.$px.','.$py.' '.$l_shapes[$mode]; + } + + break; + + case QR_AREA_TRACKER: + + if (!isset($lib['tracker'])) { + $lib['tracker'] = ''; + } + + $symb = array_shift($path); + + $px = array_shift($path); + $py = array_shift($path); + + $svg[] = ''; + + break; + } + } + + $svg[] = ''; + + + + return join("\n", $lib)."\n".join("\n", $svg); + } + } + + /** @} */ + + + + +//---- qrencode.php ----------------------------- + + + + +/* + * PHP QR Code encoder + * + * Main encoder classes. + * + * Based on libqrencode C library distributed under LGPL 2.1 + * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi + * + * PHP QR Code is distributed under LGPL 3 + * Copyright (C) 2010-2013 Dominik Dzienia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + /** @defgroup CoreGroup Standard API Core + Core encoder classes */ + + /** @addtogroup CoreGroup */ + /** @{ */ + + //########################################################################## + /** + Data block with raw data and it's Error Correction Code data. + */ class QRrsblock { public $dataLength; public $data = array(); public $eccLength; public $ecc = array(); + /** Data block Constructor + @param Integer $dl length of data stream + @param Array $data data stream + @param Integer $el ECC length + @param Array $el ECC stream (modified, by reference) + @param QRrsItem $rs RS encoding item + */ public function __construct($dl, $data, $el, &$ecc, QRrsItem $rs) { $rs->encode_rs_char($data, $ecc); @@ -2851,19 +4290,25 @@ }; //########################################################################## - + /** Raw Code holder. + Contains encoded code data before there are spatialy distributed into frame and masked. + Here goes dividing data into blocks and calculating ECC stream. */ class QRrawcode { - public $version; - public $datacode = array(); - public $ecccode = array(); - public $blocks; - public $rsblocks = array(); //of RSblock - public $count; - public $dataLength; - public $eccLength; - public $b1; + + public $version; ///< __Integer__ code Version + public $datacode = array(); ///< __Array__ data stream + public $ecccode = array(); ///< __Array__ ECC Stream + public $blocks; ///< __Integer__ RS Blocks count + public $rsblocks = array(); ///< __Array__ of RSblock, ECC code blocks + public $count; ///< __Integer__ position of currently processed ECC code + public $dataLength; ///< __Integer__ data stream length + public $eccLength; ///< __Integer__ ECC stream length + public $b1; ///< __Integer__ width of code in pixels, used as a modulo base for column overflow //---------------------------------------------------------------------- + /** Raw Code holder Constructor + @param QRinput $input input stream + */ public function __construct(QRinput $input) { $spec = array(0,0,0,0,0); @@ -2892,6 +4337,9 @@ } //---------------------------------------------------------------------- + /** Initializes Raw Code according to current code speciffication + @param Array $spec code speciffigation, as provided by QRspec + */ public function init(array $spec) { $dl = QRspec::rsDataCodes1($spec); @@ -2935,6 +4383,9 @@ } //---------------------------------------------------------------------- + /** Gets ECC code + @return Integer ECC byte for current object position + */ public function getCode() { $ret = null; @@ -2960,14 +4411,45 @@ } //########################################################################## + /** + __Main class to create QR-code__. + QR Code symbol is a 2D barcode that can be scanned by handy terminals such as a mobile phone with CCD. + The capacity of QR Code is up to 7000 digits or 4000 characters, and has high robustness. + This class supports QR Code model 2, described in JIS (Japanese Industrial Standards) X0510:2004 or ISO/IEC 18004. + Currently the following features are not supported: ECI and FNC1 mode, Micro QR Code, QR Code model 1, Structured mode. + + @abstract Class for generating QR-code images, SVG and HTML5 Canvas + @author Dominik Dzienia + @copyright 2010-2013 Dominik Dzienia and others + @link http://phpqrcode.sourceforge.net + @license http://www.gnu.org/copyleft/lesser.html LGPL + */ + class QRcode { - public $version; - public $width; - public $data; + public $version; ///< __Integer__ QR code version. Size of QRcode is defined as version. Version is from 1 to 40. Version 1 is 21*21 matrix. And 4 modules increases whenever 1 version increases. So version 40 is 177*177 matrix. + public $width; ///< __Integer__ Width of code table. Because code is square shaped - same as height. + public $data; ///< __Array__ Ready, masked code data. + + /** Canvas JS include flag. + If canvas js support library was included, we remember it static in QRcode. + (because file should be included only once) + */ + public static $jscanvasincluded = false; //---------------------------------------------------------------------- + /** + Encode mask + Main function responsible for creating code. + We get empty frame, then fill it with data from input, then select best mask and apply it. + If $mask argument is greater than -1 we assume that user want's that specific mask number (ranging form 0-7) to be used. + Otherwise (when $mask is -1) mask is detected using algorithm depending of global configuration, + + @param QRinput $input data object + @param Integer $mask sugested masking mode + @return QRcode $this (current instance) + */ public function encodeMask(QRinput $input, $mask) { if($input->getVersion() < 0 || $input->getVersion() > QRSPEC_VERSION_MAX) { @@ -2985,7 +4467,7 @@ $width = QRspec::getWidth($version); $frame = QRspec::newFrame($version); - $filler = new FrameFiller($width, $frame); + $filler = new QRframeFiller($width, $frame); if(is_null($filler)) { return NULL; } @@ -3035,23 +4517,37 @@ QRtools::markTime('after_mask'); - $this->version = $version; - $this->width = $width; - $this->data = $masked; + $this->version = $version; + $this->width = $width; + $this->data = $masked; return $this; } //---------------------------------------------------------------------- + /** + Encode input with mask detection. + Shorthand for encodeMask, without specifing particular, static mask number. + + @param QRinput $input data object to be encoded + @return + */ public function encodeInput(QRinput $input) { return $this->encodeMask($input, -1); } //---------------------------------------------------------------------- + /** + Encode string, forcing 8-bit encoding + @param String $string input string + @param Integer $version code version (size of code area) + @param Integer $level ECC level (see: Global Constants -> Levels of Error Correction) + @return QRcode $this (current instance) + */ public function encodeString8bit($string, $version, $level) { - if(string == NULL) { + if($string == NULL) { throw new Exception('empty string!'); return NULL; } @@ -3059,7 +4555,7 @@ $input = new QRinput($version, $level); if($input == NULL) return NULL; - $ret = $input->append($input, QR_MODE_8, strlen($string), str_split($string)); + $ret = $input->append(QR_MODE_8, strlen($string), str_split($string)); if($ret < 0) { unset($input); return NULL; @@ -3068,6 +4564,21 @@ } //---------------------------------------------------------------------- + /** + Encode string, using optimal encodings. + Encode string dynamically adjusting encoding for subsections of string to + minimize resulting code size. For complex string it will split string into + subsections: Numerical, Alphanumerical or 8-bit. + @param String $string input string + @param Integer $version code version (size of code area) + @param String $level ECC level (see: Global Constants -> Levels of Error Correction) + @param Integer $hint __QR_MODE_8__ or __QR_MODE_KANJI__, Because Kanji encoding + is kind of 8 bit encoding we need to hint encoder to use Kanji mode explicite. + (otherwise it may try to encode it as plain 8 bit stream) + @param Boolean $casesensitive hint if given string is case-sensitive, because + if not - encoder may use optimal QR_MODE_AN instead of QR_MODE_8 + @return QRcode $this (current instance) + */ public function encodeString($string, $version, $level, $hint, $casesensitive) { @@ -3087,7 +4598,18 @@ return $this->encodeInput($input); } - //---------------------------------------------------------------------- + //###################################################################### + /** + Creates PNG image containing QR-Code. + Simple helper function to create QR-Code Png image with one static call. + @param String $text text string to encode + @param String $outfile (optional) output file name, if __false__ outputs to browser with required headers + @param Integer $level (optional) error correction level __QR_ECLEVEL_L__, __QR_ECLEVEL_M__, __QR_ECLEVEL_Q__ or __QR_ECLEVEL_H__ + @param Integer $size (optional) pixel size, multiplier for each 'virtual' pixel + @param Integer $margin (optional) code margin (silent zone) in 'virtual' pixels + @param Boolean $saveandprint (optional) if __true__ code is outputed to browser and saved to file, otherwise only saved to file. It is effective only if $outfile is specified. + */ + public static function png($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4, $saveandprint=false) { $enc = QRencode::factory($level, $size, $margin); @@ -3095,6 +4617,17 @@ } //---------------------------------------------------------------------- + /** + Creates text (1's & 0's) containing QR-Code. + Simple helper function to create QR-Code text with one static call. + @param String $text text string to encode + @param String $outfile (optional) output file name, when __false__ file is not saved + @param Integer $level (optional) error correction level __QR_ECLEVEL_L__, __QR_ECLEVEL_M__, __QR_ECLEVEL_Q__ or __QR_ECLEVEL_H__ + @param Integer $size (optional) pixel size, multiplier for each 'virtual' pixel + @param Integer $margin (optional) code margin (silent zone) in 'virtual' pixels + @return Array containing line of code with 1 and 0 for every code line + */ + public static function text($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4) { $enc = QRencode::factory($level, $size, $margin); @@ -3102,25 +4635,162 @@ } //---------------------------------------------------------------------- + /** + Creates Raw Array containing QR-Code. + Simple helper function to create QR-Code array with one static call. + @param String $text text string to encode + @param Boolean $outfile (optional) not used, shuold be __false__ + @param Integer $level (optional) error correction level __QR_ECLEVEL_L__, __QR_ECLEVEL_M__, __QR_ECLEVEL_Q__ or __QR_ECLEVEL_H__ + @param Integer $size (optional) pixel size, multiplier for each 'virtual' pixel + @param Integer $margin (optional) code margin (silent zone) in 'virtual' pixels + @return Array containing Raw QR code + */ + public static function raw($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4) { $enc = QRencode::factory($level, $size, $margin); return $enc->encodeRAW($text, $outfile); } + + //---------------------------------------------------------------------- + /** + Creates Html+JS code to draw QR-Code with HTML5 Canvas. + Simple helper function to create QR-Code array with one static call. + @param String $text text string to encode + @param String $elemId (optional) target Canvas tag id attribute, if __false__ Canvas tag with auto id will be created + @param Integer $level (optional) error correction level __QR_ECLEVEL_L__, __QR_ECLEVEL_M__, __QR_ECLEVEL_Q__ or __QR_ECLEVEL_H__ + @param Integer $width (optional) CANVAS element width (sam as height) + @param Integer $size (optional) pixel size, multiplier for each 'virtual' pixel + @param Integer $margin (optional) code margin (silent zone) in 'virtual' pixels + @param Boolean $autoInclude (optional) if __true__, required qrcanvas.js lib will be included (only once) + @return String containing JavaScript creating the code, Canvas element (when $elemId is __false__) and script tag with required lib (when $autoInclude is __true__ and not yet included) + */ + + public static function canvas($text, $elemId = false, $level = QR_ECLEVEL_L, $width = false, $size = false, $margin = 4, $autoInclude = false) + { + $html = ''; + $extra = ''; + + if ($autoInclude) { + if (!self::$jscanvasincluded) { + self::$jscanvasincluded = true; + echo ''; + } + } + + $enc = QRencode::factory($level, 1, 0); + $tab_src = $enc->encode($text, false); + $area = new QRcanvasOutput($tab_src); + $area->detectGroups(); + $area->detectAreas(); + + if ($elemId === false) { + $elemId = 'qrcode-'.md5(mt_rand(1000,1000000).'.'.mt_rand(1000,1000000).'.'.mt_rand(1000,1000000).'.'.mt_rand(1000,1000000)); + + if ($width == false) { + if (($size !== false) && ($size > 0)) { + $width = ($area->getWidth()+(2*$margin)) * $size; + } else { + $width = ($area->getWidth()+(2*$margin)) * 4; + } + } + + $html .= 'Your browser does not support CANVAS tag! Please upgrade to modern version of FireFox, Opera, Chrome or Safari/Webkit based browser'; + } + + if ($width !== false) { + $extra .= ', '.$width.', '.$width; + } + + if ($margin !== false) { + $extra .= ', '.$margin.', '.$margin; + } + + $html .= ''; + + return $html; + } + + //---------------------------------------------------------------------- + /** + Creates SVG with QR-Code. + Simple helper function to create QR-Code SVG with one static call. + @param String $text text string to encode + @param Boolean $elemId (optional) target SVG tag id attribute, if __false__ SVG tag with auto id will be created + @param String $outfile (optional) output file name, when __false__ file is not saved + @param Integer $level (optional) error correction level __QR_ECLEVEL_L__, __QR_ECLEVEL_M__, __QR_ECLEVEL_Q__ or __QR_ECLEVEL_H__ + @param Integer $width (optional) SVG element width (sam as height) + @param Integer $size (optional) pixel size, multiplier for each 'virtual' pixel + @param Integer $margin (optional) code margin (silent zone) in 'virtual' pixels + @param Boolean $compress (optional) if __true__, compressed SVGZ (instead plaintext SVG) is saved to file + @return String containing SVG tag + */ + + public static function svg($text, $elemId = false, $outFile = false, $level = QR_ECLEVEL_L, $width = false, $size = false, $margin = 4, $compress = false) + { + $enc = QRencode::factory($level, 1, 0); + $tab_src = $enc->encode($text, false); + $area = new QRsvgOutput($tab_src); + $area->detectGroups(); + $area->detectAreas(); + + if ($elemId === false) { + $elemId = 'qrcode-'.md5(mt_rand(1000,1000000).'.'.mt_rand(1000,1000000).'.'.mt_rand(1000,1000000).'.'.mt_rand(1000,1000000)); + + if ($width == false) { + if (($size !== false) && ($size > 0)) { + $width = ($area->getWidth()+(2*$margin)) * $size; + } else { + $width = ($area->getWidth()+(2*$margin)) * 4; + } + } + } + + $svg = ''."\n"; + + $svg .= $area->getRawSvg().''; + + if ($outFile !== false) { + $xmlPreamble = 'width = $width; @@ -3132,18 +4802,27 @@ } //---------------------------------------------------------------------- + /** Sets frame code at given position. + @param Array $at position, map containing __x__ and __y__ coordinates + @param Integer $val value to set + */ public function setFrameAt($at, $val) { $this->frame[$at['y']][$at['x']] = chr($val); } //---------------------------------------------------------------------- + /** Gets frame code from given position. + @param Array $at position, map containing __x__ and __y__ coordinates + @return Integer value at requested position + */ public function getFrameAt($at) { return ord($this->frame[$at['y']][$at['x']]); } //---------------------------------------------------------------------- + /** Proceed to next code point. */ public function next() { do { @@ -3200,22 +4879,29 @@ } ; //########################################################################## - + /** QR Code encoder. + Encoder is used by QRCode to create simple static code generators. */ class QRencode { - public $casesensitive = true; - public $eightbit = false; + public $casesensitive = true; ///< __Boolean__ does input stream id case sensitive, if not encoder may use more optimal charsets + public $eightbit = false; ///< __Boolean__ does input stream is 8 bit - public $version = 0; - public $size = 3; - public $margin = 4; + public $version = 0; ///< __Integer__ code version (total size) if __0__ - will be auto-detected + public $size = 3; ///< __Integer__ pixel zoom factor, multiplier to map virtual code pixels to image output pixels + public $margin = 4; ///< __Integer__ margin (silent zone) size, in code pixels - public $structured = 0; // not supported yet + public $structured = 0; ///< Structured QR codes. Not supported. - public $level = QR_ECLEVEL_L; - public $hint = QR_MODE_8; + public $level = QR_ECLEVEL_L; ///< __Integer__ error correction level __QR_ECLEVEL_L__, __QR_ECLEVEL_M__, __QR_ECLEVEL_Q__ or __QR_ECLEVEL_H__ + public $hint = QR_MODE_8; ///< __Integer__ encoding hint, __QR_MODE_8__ or __QR_MODE_KANJI__, Because Kanji encoding is kind of 8 bit encoding we need to hint encoder to use Kanji mode explicite. (otherwise it may try to encode it as plain 8 bit stream) //---------------------------------------------------------------------- + /** Encoder instances factory. + @param Integer $level error correction level __QR_ECLEVEL_L__, __QR_ECLEVEL_M__, __QR_ECLEVEL_Q__ or __QR_ECLEVEL_H__ + @param Integer $size pixel zoom factor, multiplier to map virtual code pixels to image output pixels + @param Integer $margin margin (silent zone) size, in code pixels + @return builded QRencode instance + */ public static function factory($level = QR_ECLEVEL_L, $size = 3, $margin = 4) { $enc = new QRencode(); @@ -3251,7 +4937,12 @@ } //---------------------------------------------------------------------- - public function encodeRAW($intext, $outfile = false) + /** Encodes input into Raw code table. + @param String $intext input text + @param Boolean $notused (optional, not used) placeholder for similar outfile parameter + @return __Array__ Raw code frame + */ + public function encodeRAW($intext, $notused = false) { $code = new QRcode(); @@ -3265,6 +4956,11 @@ } //---------------------------------------------------------------------- + /** Encodes input into binary code table. + @param String $intext input text + @param String $outfile (optional) output file to save code table, if __false__ file will be not saved + @return __Array__ binary code frame + */ public function encode($intext, $outfile = false) { $code = new QRcode(); @@ -3277,15 +4973,21 @@ QRtools::markTime('after_encode'); + $binarized = QRtools::binarize($code->data); if ($outfile!== false) { - file_put_contents($outfile, join("\n", QRtools::binarize($code->data))); - } else { - return QRtools::binarize($code->data); + file_put_contents($outfile, join("\n", $binarized)); } + + return $binarized; } //---------------------------------------------------------------------- - public function encodePNG($intext, $outfile = false,$saveandprint=false) + /** Encodes input into PNG image. + @param String $intext input text + @param String $outfile (optional) output file name, if __false__ outputs to browser with required headers + @param Boolean $saveandprint (optional) if __true__ code is outputed to browser and saved to file, otherwise only saved to file. It is effective only if $outfile is specified. + */ + public function encodePNG($intext, $outfile = false, $saveandprint=false) { try { @@ -3308,5 +5010,6 @@ } } } - - + + /** @}*/ + From 0873b36569bfcf502a092870d7d5f4d088ed9003 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Wed, 3 Jul 2024 08:38:35 +0200 Subject: [PATCH 078/118] Remove call to non-existing AphrontTypeaheadTemplateView::renderToken() Summary: `AphrontTypeaheadTemplateView::renderToken()` is an undefined method, and its result is stored in an orphan variable, and no child class extends `AphrontTypeaheadTemplateView` defining a `renderToken()`. Thus remove its call from `AphrontTypeaheadTemplateView::render()`. Test Plan: Read/grep the code in this class. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25721 --- src/view/control/AphrontTypeaheadTemplateView.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/view/control/AphrontTypeaheadTemplateView.php b/src/view/control/AphrontTypeaheadTemplateView.php index e536a7a7cf..08c7140edd 100644 --- a/src/view/control/AphrontTypeaheadTemplateView.php +++ b/src/view/control/AphrontTypeaheadTemplateView.php @@ -34,12 +34,6 @@ final class AphrontTypeaheadTemplateView extends AphrontView { $id = $this->id; $name = $this->getName(); - $values = nonempty($this->getValue(), array()); - - $tokens = array(); - foreach ($values as $key => $value) { - $tokens[] = $this->renderToken($key, $value); - } $input = javelin_tag( 'input', From a039c4952f6b13f380e2df3a8989c856c87bb952 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 19 Jul 2024 13:42:28 +0200 Subject: [PATCH 079/118] Remove call to undefined method DoorkeeperDAO::getObjectKey() Summary: Remove call to undefined static method `DoorKeeperDAO::getObjectKey()` in the `DoorkeeperExternalObject` class. `getObjectKey()` does not exist (and never existed) in any of its parent classes (`DoorkeeperDAO`, `PhabricatorLiskDAO`, `LiskDAO`). Test Plan: Run static code analysis. Grep the source code and check the git history of parent classes. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25734 --- .../doorkeeper/storage/DoorkeeperExternalObject.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/applications/doorkeeper/storage/DoorkeeperExternalObject.php b/src/applications/doorkeeper/storage/DoorkeeperExternalObject.php index a4ce41adac..a6d4704a90 100644 --- a/src/applications/doorkeeper/storage/DoorkeeperExternalObject.php +++ b/src/applications/doorkeeper/storage/DoorkeeperExternalObject.php @@ -60,11 +60,7 @@ final class DoorkeeperExternalObject extends DoorkeeperDAO } public function getObjectKey() { - $key = parent::getObjectKey(); - if ($key === null) { - $key = $this->getRef()->getObjectKey(); - } - return $key; + return $this->getRef()->getObjectKey(); } public function getRef() { From 0b93685cc999cea86979592e038590bce1a06647 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Wed, 24 Jul 2024 17:47:46 +0200 Subject: [PATCH 080/118] Fix undefined variable in HeraldAction Summary: `$no_permission` is only defined within `if ($object instanceof PhabricatorPolicyInterface)`, thus move the check for `if ($no_permission)` into that clause. Test Plan: Read the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25741 --- src/applications/herald/action/HeraldAction.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/applications/herald/action/HeraldAction.php b/src/applications/herald/action/HeraldAction.php index d914c97a1c..38542cf5ed 100644 --- a/src/applications/herald/action/HeraldAction.php +++ b/src/applications/herald/action/HeraldAction.php @@ -298,10 +298,9 @@ abstract class HeraldAction extends Phobject { $no_permission[] = $phid; unset($targets[$phid]); } - } - - if ($no_permission) { - $this->logEffect(self::DO_STANDARD_PERMISSION, $no_permission); + if ($no_permission) { + $this->logEffect(self::DO_STANDARD_PERMISSION, $no_permission); + } } return $targets; From 4e31cadb54d2e454bf63100f6d0873eaadf6f756 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 25 Jul 2024 15:49:43 +0200 Subject: [PATCH 081/118] Remove unused parameter from PhabricatorConfigConsoleController::newLibraryVersionTable() call Summary: `Method PhabricatorConfigConsoleController::newLibraryVersionTable() invoked with 1 parameter, 0 required.` Thus remove the parameter from its only call across the codebase. Test Plan: Read the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25745 --- .../config/controller/PhabricatorConfigConsoleController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/config/controller/PhabricatorConfigConsoleController.php b/src/applications/config/controller/PhabricatorConfigConsoleController.php index e545019666..ad81e28be7 100644 --- a/src/applications/config/controller/PhabricatorConfigConsoleController.php +++ b/src/applications/config/controller/PhabricatorConfigConsoleController.php @@ -60,7 +60,7 @@ final class PhabricatorConfigConsoleController ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setObjectList($menu); - $versions = $this->newLibraryVersionTable($viewer); + $versions = $this->newLibraryVersionTable(); $binary_versions = $this->newBinaryVersionTable(); $launcher_view = id(new PHUILauncherView()) From c5c2b8ce5a679adaa1b5b8e7dd605ba78bf3ff3e Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 25 Jul 2024 20:33:14 +0200 Subject: [PATCH 082/118] Remove unused parameter from PhabricatorMailEmailEngine::newEmailThreadingHeaders() call Summary: `Method PhabricatorMailEmailEngine::newEmailThreadingHeaders() invoked with 1 parameter, 0 required.` Thus remove the parameter from its call in the same class. The called private function does `$mailer = $this->getMailer()` anyway. Test Plan: Read the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25747 --- src/applications/metamta/engine/PhabricatorMailEmailEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/metamta/engine/PhabricatorMailEmailEngine.php b/src/applications/metamta/engine/PhabricatorMailEmailEngine.php index 4278aa96a1..872f417cb2 100644 --- a/src/applications/metamta/engine/PhabricatorMailEmailEngine.php +++ b/src/applications/metamta/engine/PhabricatorMailEmailEngine.php @@ -72,7 +72,7 @@ final class PhabricatorMailEmailEngine $message->setSubject($subject); $headers = $this->newEmailHeaders(); - foreach ($this->newEmailThreadingHeaders($mailer) as $threading_header) { + foreach ($this->newEmailThreadingHeaders() as $threading_header) { $headers[] = $threading_header; } From 903015312a0bf1efaee75bf2d48e0dedd66ae14a Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 25 Jul 2024 15:53:09 +0200 Subject: [PATCH 083/118] Remove unused parameter from PhabricatorDaemonController::buildSideNavView() call Summary: `Method PhabricatorDaemonController::buildSideNavView() invoked with 1 parameter, 0 required.` Thus remove the parameter from its call in the same class. Test Plan: Read the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25746 --- .../daemon/controller/PhabricatorDaemonController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/daemon/controller/PhabricatorDaemonController.php b/src/applications/daemon/controller/PhabricatorDaemonController.php index 05c850d399..bbf259b000 100644 --- a/src/applications/daemon/controller/PhabricatorDaemonController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonController.php @@ -8,7 +8,7 @@ abstract class PhabricatorDaemonController } public function buildApplicationMenu() { - return $this->buildSideNavView(true)->getMenu(); + return $this->buildSideNavView()->getMenu(); } protected function buildSideNavView() { From 7909f6a91937709c472c708a4b928ab31eb56fd3 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 26 Jul 2024 16:45:05 +0200 Subject: [PATCH 084/118] Fix PHP 8.1 "strlen(null)" exception on LDAP login without password Summary: `strlen()` was used in Phabricator to check if a generic value is a non-empty string. This behavior is deprecated since PHP 8.1. Phorge adopts `phutil_nonempty_string()` as a replacement. Note: this may highlight other absurd input values that might be worth correcting instead of just ignoring. If phutil_nonempty_string() throws an exception in your instance, report it to Phorge to evaluate and fix that specific corner case. Note: This patch also corrects two further `strlen()` occurrences with the same pattern. ``` ERROR 8192: strlen(): Passing null to parameter #1 ($string) of type string is deprecated at [/var/www/html/phorge/phorge/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php:145] ``` Closes T15893 Test Plan: Create an LDAP user without setting their password; try to log into Phabricator with that user via the LDAP auth provider. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15893 Differential Revision: https://we.phorge.it/D25748 --- .../auth/provider/PhabricatorLDAPAuthProvider.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php b/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php index 36a83f3678..3975e8da79 100644 --- a/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php @@ -142,10 +142,10 @@ final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider { $username = $request->getStr('ldap_username'); $password = $request->getStr('ldap_password'); - $has_password = strlen($password); + $has_password = phutil_nonempty_string($password); $password = new PhutilOpaqueEnvelope($password); - if (!strlen($username) || !$has_password) { + if (!phutil_nonempty_string($username) || !$has_password) { $response = $controller->buildProviderPageResponse( $this, $this->renderLoginForm($request, 'login')); @@ -154,7 +154,7 @@ final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider { if ($request->isFormPost()) { try { - if (strlen($username) && $has_password) { + if (phutil_nonempty_string($username) && $has_password) { $adapter = $this->getAdapter(); $adapter->setLoginUsername($username); $adapter->setLoginPassword($password); From 59678094fb777aed3c3eb66e8075c082b3dc393f Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 25 Jul 2024 15:42:14 +0200 Subject: [PATCH 085/118] Refactor PhabricatorBadgesEditRecipientsController to remove dead code Summary: `$form` in `$dialog = id(new AphrontDialogView())->appendForm($form)` is only defined when `if ($can_edit)` was `true` beforehand. But that was always true. Thus add a variable definition (and remove some unused variables like `$form_box`). Test Plan: Visit the page /badges/recipients/1/ or also directly /badges/recipients/1/add/ and add some recipients. It still works as before. Visit the page without enough permissions. It does not allow to edit them, as before. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25744 --- ...bricatorBadgesEditRecipientsController.php | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php index ce437f1a5d..1706b0a644 100644 --- a/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php +++ b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php @@ -49,26 +49,15 @@ final class PhabricatorBadgesEditRecipientsController ->setURI($view_uri); } - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $badge, - PhabricatorPolicyCapability::CAN_EDIT); - - $form_box = null; - $title = pht('Add Recipient'); - if ($can_edit) { - $header_name = pht('Edit Recipients'); - - $form = new AphrontFormView(); - $form - ->setUser($viewer) - ->setFullWidth(true) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setName('phids') - ->setLabel(pht('Recipients')) - ->setDatasource(new PhabricatorPeopleDatasource())); - } + $form = new AphrontFormView(); + $form + ->setUser($viewer) + ->setFullWidth(true) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setName('phids') + ->setLabel(pht('Recipients')) + ->setDatasource(new PhabricatorPeopleDatasource())); $dialog = id(new AphrontDialogView()) ->setUser($viewer) From 349f006904fabf1d4df31ff4840502af3ab379a7 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Mon, 29 Jul 2024 12:39:05 +0200 Subject: [PATCH 086/118] Remarkup: make less internal links open in new tabs Summary: This is an attempt to improve the default behavior in Remarkup about links. It does not change any behaviors manually specified in the engine and it does not change any behaviors related to external domains. As default, now these kind of links will open in the same tab: - anchors - relative URLs - absolute URLs pointing to the base-URI domain All the other cases are kept as before - so they open in another tab. In short, assuming you are we.phorge.it, here the changes: | |https://gnu.org|[[changelog/]]|[[#anchor|#anchor]]|https://we.phorge.it/|[[/config/|/config/]]| |Before|external |internal |internal |external |external | |After |external |internal |internal |**internal** |**internal** | This situation can further improve but it already covers most of the cases where most users do not expect to break their navigation into several tabs. Moreover, if an user wants to open a link in another window, no one prevents from using the middle mouse button, or CTRL+click or any other nice really basic feature from their browser. Also, this change introduces a new CSS class, allowing web designers to style these external resources. Example CSS rule to try: ```css .remarkup-link-ext::before { content: "[external] "; } ``` Closes T15161 Closes T15182 Test Plan: - Copy the example text from this Task: https://we.phorge.it/T15161 - Verify that "internal resources" are internal links as default now - Verify that "external resources" are still external links as before Reviewers: O1 Blessed Committers, Cigaryno, avivey, speck Reviewed By: O1 Blessed Committers, Cigaryno, speck Subscribers: avivey, speck, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15182, T15161 Differential Revision: https://we.phorge.it/D25118 --- src/__phutil_library_map__.php | 4 + .../PhutilRemarkupDocumentLinkRule.php | 20 +++-- .../PhutilRemarkupHyperlinkRule.php | 6 +- .../markup/markuprule/PhutilRemarkupRule.php | 16 ++++ src/infrastructure/parser/PhutilURIHelper.php | 78 +++++++++++++++++++ .../__tests__/PhutilURIHelperTestCase.php | 63 +++++++++++++++ 6 files changed, 178 insertions(+), 9 deletions(-) create mode 100644 src/infrastructure/parser/PhutilURIHelper.php create mode 100644 src/infrastructure/parser/__tests__/PhutilURIHelperTestCase.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 257c69d1a4..396bb1a308 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5799,6 +5799,8 @@ phutil_register_library_map(array( 'PhutilTranslatedHTMLTestCase' => 'infrastructure/markup/__tests__/PhutilTranslatedHTMLTestCase.php', 'PhutilTwitchAuthAdapter' => 'applications/auth/adapter/PhutilTwitchAuthAdapter.php', 'PhutilTwitterAuthAdapter' => 'applications/auth/adapter/PhutilTwitterAuthAdapter.php', + 'PhutilURIHelper' => 'infrastructure/parser/PhutilURIHelper.php', + 'PhutilURIHelperTestCase' => 'infrastructure/parser/__tests__/PhutilURIHelperTestCase.php', 'PhutilWordPressAuthAdapter' => 'applications/auth/adapter/PhutilWordPressAuthAdapter.php', 'PhutilXHPASTSyntaxHighlighter' => 'infrastructure/markup/syntax/highlighter/PhutilXHPASTSyntaxHighlighter.php', 'PhutilXHPASTSyntaxHighlighterFuture' => 'infrastructure/markup/syntax/highlighter/xhpast/PhutilXHPASTSyntaxHighlighterFuture.php', @@ -12705,6 +12707,8 @@ phutil_register_library_map(array( 'PhutilTranslatedHTMLTestCase' => 'PhutilTestCase', 'PhutilTwitchAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilTwitterAuthAdapter' => 'PhutilOAuth1AuthAdapter', + 'PhutilURIHelper' => 'Phobject', + 'PhutilURIHelperTestCase' => 'PhabricatorTestCase', 'PhutilWordPressAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilXHPASTSyntaxHighlighter' => 'Phobject', 'PhutilXHPASTSyntaxHighlighterFuture' => 'FutureProxy', diff --git a/src/infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php b/src/infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php index ded57d4c77..e9156b7799 100644 --- a/src/infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php +++ b/src/infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php @@ -44,16 +44,16 @@ final class PhutilRemarkupDocumentLinkRule extends PhutilRemarkupRule { protected function renderHyperlink($link, $name) { $engine = $this->getEngine(); - $is_anchor = false; - if (strncmp($link, '/', 1) == 0) { + $uri = new PhutilURIHelper($link); + $is_anchor = $uri->isAnchor(); + $starts_with_slash = $uri->isStartingWithSlash(); + if ($starts_with_slash) { $base = phutil_string_cast($engine->getConfig('uri.base')); $base = rtrim($base, '/'); $link = $base.$link; - } else if (strncmp($link, '#', 1) == 0) { + } else if ($is_anchor) { $here = $engine->getConfig('uri.here'); $link = $here.$link; - - $is_anchor = true; } if ($engine->isTextMode()) { @@ -76,7 +76,13 @@ final class PhutilRemarkupDocumentLinkRule extends PhutilRemarkupRule { return $name; } - $same_window = $engine->getConfig('uri.same-window', false); + // Check if this link points to Phorge itself. Micro-optimized. + $is_self = $is_anchor || $starts_with_slash || $uri->isSelf(); + + // For historical reasons, links opened in a different tab + // for most links as default. + // Now internal resources keep internal link, as default. + $same_window = $engine->getConfig('uri.same-window', $is_self); if ($same_window) { $target = null; } else { @@ -92,7 +98,7 @@ final class PhutilRemarkupDocumentLinkRule extends PhutilRemarkupRule { 'a', array( 'href' => $link, - 'class' => 'remarkup-link', + 'class' => $this->getRemarkupLinkClass($is_self), 'target' => $target, 'rel' => 'noreferrer', ), diff --git a/src/infrastructure/markup/markuprule/PhutilRemarkupHyperlinkRule.php b/src/infrastructure/markup/markuprule/PhutilRemarkupHyperlinkRule.php index 560aa180c3..dbfca5e5a9 100644 --- a/src/infrastructure/markup/markuprule/PhutilRemarkupHyperlinkRule.php +++ b/src/infrastructure/markup/markuprule/PhutilRemarkupHyperlinkRule.php @@ -116,7 +116,9 @@ final class PhutilRemarkupHyperlinkRule extends PhutilRemarkupRule { $engine = $this->getEngine(); - $same_window = $engine->getConfig('uri.same-window', false); + $uri = new PhutilURIHelper($link); + $is_self = $uri->isSelf(); + $same_window = $engine->getConfig('uri.same-window', $is_self); if ($same_window) { $target = null; } else { @@ -127,7 +129,7 @@ final class PhutilRemarkupHyperlinkRule extends PhutilRemarkupRule { 'a', array( 'href' => $link, - 'class' => 'remarkup-link', + 'class' => $this->getRemarkupLinkClass($is_self), 'target' => $target, 'rel' => 'noreferrer', ), diff --git a/src/infrastructure/markup/markuprule/PhutilRemarkupRule.php b/src/infrastructure/markup/markuprule/PhutilRemarkupRule.php index 4a3c1460e9..a5ddaa4ab6 100644 --- a/src/infrastructure/markup/markuprule/PhutilRemarkupRule.php +++ b/src/infrastructure/markup/markuprule/PhutilRemarkupRule.php @@ -112,4 +112,20 @@ abstract class PhutilRemarkupRule extends Phobject { return (strpos($text, PhutilRemarkupBlockStorage::MAGIC_BYTE) === false); } + /** + * Get the CSS class="" attribute for a Remarkup link. + * It's just "remarkup-link" for all cases, plus the possibility for + * designers to style external links differently. + * @param boolean $is_internal Whenever the link was internal or not. + * @return string + */ + protected function getRemarkupLinkClass($is_internal) { + // Allow developers to style esternal links differently + $classes = array('remarkup-link'); + if (!$is_internal) { + $classes[] = 'remarkup-link-ext'; + } + return implode(' ', $classes); + } + } diff --git a/src/infrastructure/parser/PhutilURIHelper.php b/src/infrastructure/parser/PhutilURIHelper.php new file mode 100644 index 0000000000..4341585e39 --- /dev/null +++ b/src/infrastructure/parser/PhutilURIHelper.php @@ -0,0 +1,78 @@ +uriStr = phutil_string_cast($uri); + + // A PhutilURI may be useful. If available, import that as-is. + // Note that the constructor PhutilURI(string) is a bit expensive. + if ($uri instanceof PhutilURI) { + $this->phutilUri = $uri; + } + } + + /** + * Check if the URI points to Phorge itself. + * @return bool + */ + public function isSelf() { + // The backend prefers a PhutilURI object, if available. + $uri = $this->phutilUri ? $this->phutilUri : $this->uriStr; + return PhabricatorEnv::isSelfURI($uri); + } + + /** + * Check whenever an URI is just a simple fragment without path and protocol. + * @return bool + */ + public function isAnchor() { + return $this->isStartingWithChar('#'); + } + + /** + * Check whenever an URI starts with a slash (no protocol, etc.) + * @return bool + */ + public function isStartingWithSlash() { + return $this->isStartingWithChar('/'); + } + + /** + * A sane default. + */ + public function __toString() { + return $this->uriStr; + } + + /** + * Check whenever the URI starts with the provided character. + * @param string $char String that MUST have length of 1. + * @return boolean + */ + private function isStartingWithChar($char) { + return strncmp($this->uriStr, $char, 1) === 0; + } + +} diff --git a/src/infrastructure/parser/__tests__/PhutilURIHelperTestCase.php b/src/infrastructure/parser/__tests__/PhutilURIHelperTestCase.php new file mode 100644 index 0000000000..4c3a43323c --- /dev/null +++ b/src/infrastructure/parser/__tests__/PhutilURIHelperTestCase.php @@ -0,0 +1,63 @@ +getDomain(); + $tests[] = array('base uri', $base, true, false, false); + $tests[] = array('base uri anchor', "{$base}#asd", true, false, false); + } + + foreach ($tests as $test) { + $name = $test[0]; + $uri = $test[1]; + $is_self = $test[2]; + $is_anchor = $test[3]; + $is_slash = $test[4]; + + // Test input variants for the constructor of PhutilURIHelper. + $uri_variants = array( + $uri, + new PhutilURI($uri), + ); + foreach ($uri_variants as $variant_uri) { + + $test_name = pht("test %s value '%s' (from '%s' type %s)", + $name, $variant_uri, $uri, phutil_describe_type($variant_uri)); + + $uri = new PhutilURIHelper($variant_uri); + + $this->assertEqual($is_self, $uri->isSelf(), + pht('%s - points to myself', $test_name)); + + $this->assertEqual($is_anchor, $uri->isAnchor(), + pht('%s - is just an anchor', $test_name)); + + $this->assertEqual($is_slash, $uri->isStartingWithSlash(), + pht('%s - is starting with slash', $test_name)); + } + } + } +} From 6d5cde8e1cbcc6654cffde2d282c7da657932c5c Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Sun, 28 Jul 2024 23:25:50 +0200 Subject: [PATCH 087/118] Fix accessing private parent class properties in QueryFuture Summary: PHPStan complains about `Access to private property $exception of parent class Future` and `Access to private property $result of parent class Future` in `QueryFuture`. Thus call the corresponding public functions provided by `Future` instead. Test Plan: Run static code analysis; read the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25755 --- src/infrastructure/storage/future/QueryFuture.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/infrastructure/storage/future/QueryFuture.php b/src/infrastructure/storage/future/QueryFuture.php index 9878581214..0f1fc3b9d4 100644 --- a/src/infrastructure/storage/future/QueryFuture.php +++ b/src/infrastructure/storage/future/QueryFuture.php @@ -51,7 +51,7 @@ final class QueryFuture extends Future { } public function isReady() { - if ($this->result !== null || $this->exception) { + if ($this->canResolve()) { return true; } @@ -105,7 +105,7 @@ final class QueryFuture extends Future { $this->processResults($this->conn->resolveAsyncQueries($conns, $asyncs)); - if ($this->result !== null || $this->exception) { + if ($this->canResolve()) { return true; } return false; From f75b66b27a34ac98048035e7eb60c2d388b11e2f Mon Sep 17 00:00:00 2001 From: jesse Date: Sun, 28 Jul 2024 09:41:20 +0800 Subject: [PATCH 088/118] fix PHP8 array_slice($results, null) which cause diffusion locate file broken Test Plan: check diffusion locate file if works Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: avivey, aklapper, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25750 --- .../diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php index 98cc006419..e511492fc4 100644 --- a/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php @@ -27,7 +27,7 @@ final class DiffusionQueryPathsConduitAPIMethod protected function getResult(ConduitAPIRequest $request) { $results = parent::getResult($request); - $offset = $request->getValue('offset'); + $offset = $request->getValue('offset', 0); return array_slice($results, $offset); } From 8793bdbaa87c821664288b39c820ea3ed569be76 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 23 Jul 2024 00:08:18 +0200 Subject: [PATCH 089/118] Disallow awarding a badge without selecting recipient Summary: Do not continue on missing fields (in this case: the badge recipient) in `PhabricatorBadgesEditRecipientsController`. Closes T15827 Test Plan: See steps in T15827. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15827 Differential Revision: https://we.phorge.it/D25740 --- ...bricatorBadgesEditRecipientsController.php | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php index 1706b0a644..f25fd93542 100644 --- a/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php +++ b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php @@ -7,6 +7,8 @@ final class PhabricatorBadgesEditRecipientsController $viewer = $request->getViewer(); $id = $request->getURIData('id'); $xactions = array(); + $errors = array(); + $e_recipient = true; $badge = id(new PhabricatorBadgesQuery()) ->setViewer($viewer) @@ -29,24 +31,29 @@ final class PhabricatorBadgesEditRecipientsController $add_recipients = $request->getArr('phids'); if ($add_recipients) { foreach ($add_recipients as $phid) { - $award_phids[] = $phid; + $award_phids[$phid] = $phid; } + } else { + $errors[] = pht('Recipient name is required.'); + $e_recipient = pht('Required'); } - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType( - PhabricatorBadgesBadgeAwardTransaction::TRANSACTIONTYPE) - ->setNewValue($award_phids); + if (!$errors) { + $xactions[] = id(new PhabricatorBadgesTransaction()) + ->setTransactionType( + PhabricatorBadgesBadgeAwardTransaction::TRANSACTIONTYPE) + ->setNewValue($award_phids); - $editor = id(new PhabricatorBadgesEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->setContinueOnMissingFields(true) - ->applyTransactions($badge, $xactions); + $editor = id(new PhabricatorBadgesEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($badge, $xactions); - return id(new AphrontRedirectResponse()) - ->setURI($view_uri); + return id(new AphrontRedirectResponse()) + ->setURI($view_uri); + } } $form = new AphrontFormView(); @@ -57,6 +64,7 @@ final class PhabricatorBadgesEditRecipientsController id(new AphrontFormTokenizerControl()) ->setName('phids') ->setLabel(pht('Recipients')) + ->setError($e_recipient) ->setDatasource(new PhabricatorPeopleDatasource())); $dialog = id(new AphrontDialogView()) From e2919211f3c273675c765426cc937e8738f62ff5 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 11 Jul 2024 12:07:28 +0200 Subject: [PATCH 090/118] Avoid PhabricatorApplicationTransactionStructureException on editors not supporting Mute Notifications Summary: Do not expose the "Mute Notifications" sidebar menu entry when the underlying Editor for that object type does not support muting notifications (means: creating a transaction of type "core:edge") at all. This avoids a disappointing `PhabricatorApplicationTransactionStructureException` after two clicks. Disabling the menu entry while still exposing it makes no sense here as the user could never get it enabled anyway. ``` EXCEPTION: (PhabricatorApplicationTransactionStructureException) Attempting to apply a transaction (of class "PhabricatorFileTransaction", with type "core:edge") which has not been constructed correctly: Transaction has type "core:edge", but that transaction type is not supported by this editor (PhabricatorFileEditor). at [/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php:1830] ``` ``` EXCEPTION: (PhabricatorApplicationTransactionStructureException) Attempting to apply a transaction (of class "LegalpadTransaction", with type "core:edge") which has not been constructed correctly: Transaction has type "core:edge", but that transaction type is not supported by this editor (LegalpadDocumentEditor). at [/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php:1830] ``` Closes T15378 Test Plan: * Open various different object types: ** Files: http://phorge.localhost/F1 (not implemented) ** Legalpad: http://phorge.localhost/legalpad/view/1/ (not implemented) ** Maniphest: http://phorge.localhost/T1 (implemented) * Click "Subscribe" on these various types of objects. * Click "Mute Notifications" on these various types of objects, remember which objects throw an exception. * Apply this patch. * Check that "Mute Notifications" is not exposed for those types of objects that threw an exception. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15378 Differential Revision: https://we.phorge.it/D25730 --- ...habricatorSubscriptionsUIEventListener.php | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php index 2077160b7c..3d43d972f6 100644 --- a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php +++ b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php @@ -94,26 +94,33 @@ final class PhabricatorSubscriptionsUIEventListener } } - $mute_action = id(new PhabricatorActionView()) - ->setWorkflow(true) - ->setHref('/subscriptions/mute/'.$object->getPHID().'/') - ->setDisabled(!$user_phid); - - if (!$is_muted) { - $mute_action - ->setName(pht('Mute Notifications')) - ->setIcon('fa-volume-up'); - } else { - $mute_action - ->setName(pht('Unmute Notifications')) - ->setIcon('fa-volume-off') - ->setColor(PhabricatorActionView::RED); - } - - $actions = $event->getValue('actions'); $actions[] = $sub_action; - $actions[] = $mute_action; + + // Hide "Mute Notifications" in sidebar if not supported by Editor - T15378 + $supported_editor_transaction_types = + array_fill_keys($object->getApplicationTransactionEditor() + ->getTransactionTypesForObject($object), true); + if (array_key_exists(PhabricatorTransactions::TYPE_EDGE, + $supported_editor_transaction_types)) { + $mute_action = id(new PhabricatorActionView()) + ->setWorkflow(true) + ->setHref('/subscriptions/mute/'.$object->getPHID().'/') + ->setDisabled(!$user_phid); + + if (!$is_muted) { + $mute_action + ->setName(pht('Mute Notifications')) + ->setIcon('fa-volume-up'); + } else { + $mute_action + ->setName(pht('Unmute Notifications')) + ->setIcon('fa-volume-off') + ->setColor(PhabricatorActionView::RED); + } + $actions[] = $mute_action; + } + $event->setValue('actions', $actions); } From 721d5ed64b704f4ee71630c00cf44163f2156de0 Mon Sep 17 00:00:00 2001 From: jesse Date: Tue, 30 Jul 2024 09:10:45 +0800 Subject: [PATCH 091/118] fix PHP8 array_slice($results, null) which cause diffusion pattern search broken Test Plan: check diffusion pattern search if working Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25756 --- .../diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php index ba4c824061..5f40aa06bb 100644 --- a/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php @@ -40,7 +40,7 @@ final class DiffusionSearchQueryConduitAPIMethod throw $ex; } - $offset = $request->getValue('offset'); + $offset = $request->getValue('offset', 0); $results = array_slice($results, $offset); return $results; From a110aa471e98b96c66d8f4d2e0394b97a9ea3cb8 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 2 Jul 2024 15:03:35 +0200 Subject: [PATCH 092/118] Suppress PHP 8.1 warnings "Return type mixed is not covariant with tentative return type mixed of method Iterator::key()" Summary: PHP 8.1 requires classes which implement `Iterator` to have all `Iterator` methods declared with appropriate return types or annotated with `#[\ReturnTypeWillChange]`. In my (limited) understanding we cannot declare return types while remaining backward-compatible, thus annotate. Closes T15835 Test Plan: Run PHPStan static code analyzer, see no warnings anymore. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15835 Differential Revision: https://we.phorge.it/D25711 --- src/applications/fact/extract/PhabricatorFactUpdateIterator.php | 1 + .../storage/build/HarbormasterBuildLogChunkIterator.php | 1 + src/infrastructure/storage/lisk/LiskMigrationIterator.php | 1 + src/infrastructure/storage/lisk/LiskRawMigrationIterator.php | 1 + src/infrastructure/storage/lisk/PhabricatorQueryIterator.php | 1 + 5 files changed, 5 insertions(+) diff --git a/src/applications/fact/extract/PhabricatorFactUpdateIterator.php b/src/applications/fact/extract/PhabricatorFactUpdateIterator.php index c0d229084f..910c3ffc7a 100644 --- a/src/applications/fact/extract/PhabricatorFactUpdateIterator.php +++ b/src/applications/fact/extract/PhabricatorFactUpdateIterator.php @@ -33,6 +33,7 @@ final class PhabricatorFactUpdateIterator extends PhutilBufferedIterator { } } + #[\ReturnTypeWillChange] public function key() { return $this->getCursorFromObject($this->current()); } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php index 514ad7013c..b477dd7e31 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php @@ -18,6 +18,7 @@ final class HarbormasterBuildLogChunkIterator $this->cursor = $this->min; } + #[\ReturnTypeWillChange] public function key() { return $this->current()->getID(); } diff --git a/src/infrastructure/storage/lisk/LiskMigrationIterator.php b/src/infrastructure/storage/lisk/LiskMigrationIterator.php index edd31c8123..2d2f8c98e9 100644 --- a/src/infrastructure/storage/lisk/LiskMigrationIterator.php +++ b/src/infrastructure/storage/lisk/LiskMigrationIterator.php @@ -26,6 +26,7 @@ final class LiskMigrationIterator extends PhutilBufferedIterator { $this->cursor = 0; } + #[\ReturnTypeWillChange] public function key() { return $this->current()->getID(); } diff --git a/src/infrastructure/storage/lisk/LiskRawMigrationIterator.php b/src/infrastructure/storage/lisk/LiskRawMigrationIterator.php index a7edbd7f91..3d8954a269 100644 --- a/src/infrastructure/storage/lisk/LiskRawMigrationIterator.php +++ b/src/infrastructure/storage/lisk/LiskRawMigrationIterator.php @@ -16,6 +16,7 @@ final class LiskRawMigrationIterator extends PhutilBufferedIterator { $this->cursor = 0; } + #[\ReturnTypeWillChange] public function key() { return idx($this->current(), $this->column); } diff --git a/src/infrastructure/storage/lisk/PhabricatorQueryIterator.php b/src/infrastructure/storage/lisk/PhabricatorQueryIterator.php index cc88678cdf..70e55509b9 100644 --- a/src/infrastructure/storage/lisk/PhabricatorQueryIterator.php +++ b/src/infrastructure/storage/lisk/PhabricatorQueryIterator.php @@ -18,6 +18,7 @@ final class PhabricatorQueryIterator extends PhutilBufferedIterator { $this->pager = $pager; } + #[\ReturnTypeWillChange] public function key() { return $this->current()->getID(); } From 81ffe4f8b152a8125e309a19930ea6efc04a4cc8 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 2 Aug 2024 15:34:41 +0200 Subject: [PATCH 093/118] Fix PHP 8.1 "strlen(null)" exception in PhabricatorAuthAccountView Summary: `strlen()` was used in Phabricator to check if a generic value is a non-empty string. This behavior is deprecated since PHP 8.1. Phorge adopts `phutil_nonempty_string()` as a replacement. Note: this may highlight other absurd input values that might be worth correcting instead of just ignoring. If phutil_nonempty_string() throws an exception in your instance, report it to Phorge to evaluate and fix that specific corner case. ``` ERROR 8192: strlen(): Passing null to parameter #1 ($string) of type string is deprecated at [/var/www/html/phorge/phorge/src/applications/auth/view/PhabricatorAuthAccountView.php:66] ``` Closes T15900 Test Plan: Try to register an account via LDAP. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15900 Differential Revision: https://we.phorge.it/D25761 --- src/applications/auth/view/PhabricatorAuthAccountView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/auth/view/PhabricatorAuthAccountView.php b/src/applications/auth/view/PhabricatorAuthAccountView.php index f903f45d26..82ac7c6152 100644 --- a/src/applications/auth/view/PhabricatorAuthAccountView.php +++ b/src/applications/auth/view/PhabricatorAuthAccountView.php @@ -63,7 +63,7 @@ final class PhabricatorAuthAccountView extends AphrontView { )); $account_uri = $account->getAccountURI(); - if (strlen($account_uri)) { + if (phutil_nonempty_string($account_uri)) { // Make sure we don't link a "javascript:" URI if a user somehow // managed to get one here. From d8b94dd87d24105902ec32b4c3be18a1e952e66a Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Sat, 3 Aug 2024 01:40:35 +0200 Subject: [PATCH 094/118] Misc PHPDoc additions to PhabricatorFile Summary: Add some PHPDoc comments to the `PhabricatorFile` class as I was playing with calling some of its methods. Test Plan: Call those methods and see what they do and return. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25763 --- .../files/storage/PhabricatorFile.php | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 2c4c2fcc73..3918adcec2 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -176,6 +176,10 @@ final class PhabricatorFile extends PhabricatorFileDAO return true; } + /** + * Get file monogram in the format of "F123" + * @return string + */ public function getMonogram() { return 'F'.$this->getID(); } @@ -821,10 +825,19 @@ final class PhabricatorFile extends PhabricatorFileDAO return $iterator; } + /** + * Get file URI in the format of "/F123" + * @return string + */ public function getURI() { return $this->getInfoURI(); } + /** + * Get file view URI in the format of + * https://phorge.example.com/file/data/foo/PHID-FILE-bar/filename + * @return string + */ public function getViewURI() { if (!$this->getPHID()) { throw new Exception( @@ -834,6 +847,12 @@ final class PhabricatorFile extends PhabricatorFileDAO return $this->getCDNURI('data'); } + /** + * Get file view URI in the format of + * https://phorge.example.com/file/data/foo/PHID-FILE-bar/filename or + * https://phorge.example.com/file/download/foo/PHID-FILE-bar/filename + * @return string + */ public function getCDNURI($request_kind) { if (($request_kind !== 'data') && ($request_kind !== 'download')) { @@ -876,7 +895,10 @@ final class PhabricatorFile extends PhabricatorFileDAO } } - + /** + * Get file info URI in the format of "/F123" + * @return string + */ public function getInfoURI() { return '/'.$this->getMonogram(); } @@ -889,6 +911,11 @@ final class PhabricatorFile extends PhabricatorFileDAO } } + /** + * Get file view URI in the format of + * https://phorge.example.com/file/download/foo/PHID-FILE-bar/filename + * @return string + */ public function getDownloadURI() { return $this->getCDNURI('download'); } @@ -917,10 +944,20 @@ final class PhabricatorFile extends PhabricatorFileDAO return PhabricatorEnv::getCDNURI($path); } + /** + * Whether the file can be viewed in a browser + * @return bool True if MIME type of the file is listed in the + * files.viewable-mime-types setting + */ public function isViewableInBrowser() { return ($this->getViewableMimeType() !== null); } + /** + * Whether the file is an image viewable in the browser + * @return bool True if MIME type of the file is listed in the + * files.image-mime-types setting and file is viewable in the browser + */ public function isViewableImage() { if (!$this->isViewableInBrowser()) { return false; @@ -931,6 +968,11 @@ final class PhabricatorFile extends PhabricatorFileDAO return idx($mime_map, $mime_type); } + /** + * Whether the file is an audio file + * @return bool True if MIME type of the file is listed in the + * files.audio-mime-types setting and file is viewable in the browser + */ public function isAudio() { if (!$this->isViewableInBrowser()) { return false; @@ -941,6 +983,11 @@ final class PhabricatorFile extends PhabricatorFileDAO return idx($mime_map, $mime_type); } + /** + * Whether the file is a video file + * @return bool True if MIME type of the file is listed in the + * files.video-mime-types setting and file is viewable in the browser + */ public function isVideo() { if (!$this->isViewableInBrowser()) { return false; @@ -951,6 +998,11 @@ final class PhabricatorFile extends PhabricatorFileDAO return idx($mime_map, $mime_type); } + /** + * Whether the file is a PDF file + * @return bool True if MIME type of the file is application/pdf and file is + * viewable in the browser + */ public function isPDF() { if (!$this->isViewableInBrowser()) { return false; @@ -1048,6 +1100,11 @@ final class PhabricatorFile extends PhabricatorFileDAO ->execute(); } + /** + * Whether the file is listed as a viewable MIME type + * @return bool True if MIME type of the file is listed in the + * files.viewable-mime-types setting + */ public function getViewableMimeType() { $mime_map = PhabricatorEnv::getEnvConfig('files.viewable-mime-types'); From 62aaa0fcbef5fb0c4aaf22de78dc0813585c3804 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 1 Aug 2024 16:55:37 +0200 Subject: [PATCH 095/118] Restrict maximum task title length to 255 characters Summary: A task title is supposed to summarize a task. For long novels, there is the task description. Closes T15898 Test Plan: Go to http://phorge.localhost/maniphest/task/edit/form/default/ and enter a task title with multi-byte characters above the threshold, press "Enter". Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15898 Differential Revision: https://we.phorge.it/D25759 --- .../maniphest/xaction/ManiphestTaskTitleTransaction.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php index c01c263ba6..bef8914cf7 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php @@ -4,6 +4,7 @@ final class ManiphestTaskTitleTransaction extends ManiphestTaskTransactionType { const TRANSACTIONTYPE = 'title'; + private $maximumTaskTitleLength = 255; public function generateOldValue($object) { return $object->getTitle(); @@ -78,6 +79,13 @@ final class ManiphestTaskTitleTransaction $xaction); continue; } + if (mb_strlen($new) > $this->maximumTaskTitleLength) { + $errors[] = $this->newInvalidError( + pht('Task title cannot exceed %d characters.', + $this->maximumTaskTitleLength), + $xaction); + continue; + } } if (!$errors) { From 4da3b096b08172251ef5d6f8cb3ea7f97e21ca20 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Mon, 22 Jul 2024 23:11:25 +0200 Subject: [PATCH 096/118] Configuration Guide: Set UnsafeAllow3F for Apache RewriteRule Summary: Since Apache HTTP Server 2.4.61 including https://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/mappers/mod_rewrite.c?r1=1918560&r2=1918561&pathrev=1918561&diff_format=h due to https://www.cve.org/CVERecord?id=CVE-2024-38474, URIs including %3F throw a HTTP 403 error and the following error log entry: `AH10508: Unsafe URL with %3f URL rewritten without UnsafeAllow3F` Update the corresponding RewriteRule in the Phorge configuration guide to explicitly set UnsafeAllow3F. https://httpd.apache.org/docs/2.4/rewrite/flags.html#flag_unsafe_allow_3f Closes T15889 Test Plan: Run Apache HTTP Server 2.4.61, go to https://phorge.localhost/maniphest/task/edit/form/default/?title=%3f and get a HTTP 403 (before) or a "?" as task title (after). Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15889 Differential Revision: https://we.phorge.it/D25739 --- src/docs/user/configuration/configuration_guide.diviner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/user/configuration/configuration_guide.diviner b/src/docs/user/configuration/configuration_guide.diviner index c5aa82af86..978506fb30 100644 --- a/src/docs/user/configuration/configuration_guide.diviner +++ b/src/docs/user/configuration/configuration_guide.diviner @@ -49,7 +49,7 @@ this: DocumentRoot /path/to/phorge/webroot RewriteEngine on - RewriteRule ^(.*)$ /index.php?__path__=$1 [B,L,QSA] + RewriteRule ^(.*)$ /index.php?__path__=$1 [B,L,QSA,UnsafeAllow3F] If Apache isn't currently configured to serve documents out of the directory From 93c9afd2f38744d0a9efe736dd39f9a3b5455cc0 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 2 Aug 2024 00:17:25 +0200 Subject: [PATCH 097/118] Do not add actor as subscriber when removing a comment Summary: When an admin removes a comment (e.g. spam), the admin gets subscribed to the task. This is usually unwanted as the removal action does not imply that the admin is interested in receiving future notifications about the task, in contrast to e.g. adding a comment to a discussion in the task. Any transaction of a comment (add, edit, remove) is a `"core:comment"` action. The code calls `applyImplicitCC()` which calls `shouldImplyCC()` which returns the bool `$xaction->isCommentTransaction()`. Expand this bool to `$xaction->isCommentTransaction() && !($xaction->getComment()->getIsRemoved())`. Closes T15899 Test Plan: * As an admin, go to a task which has comments and to which you are not subscribed * Click the dropdown for the comment, select Remove comment * See that you did not get subscribed to the task Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15899 Differential Revision: https://we.phorge.it/D25760 --- .../PhabricatorApplicationTransactionEditor.php | 14 ++++++++++++-- .../storage/PhabricatorApplicationTransaction.php | 4 ++++ .../PhabricatorApplicationTransactionComment.php | 4 ++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 1d9d3282b7..b643279bc8 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -3299,7 +3299,10 @@ abstract class PhabricatorApplicationTransactionEditor /** - * When a user interacts with an object, we might want to add them to CC. + * Adds the actor as a subscriber to the object with which they interact + * @param PhabricatorLiskDAO $object on which the action is performed + * @param array $xactions Transactions to apply + * @return array Transactions to apply */ final public function applyImplicitCC( PhabricatorLiskDAO $object, @@ -3381,11 +3384,18 @@ abstract class PhabricatorApplicationTransactionEditor return $xactions; } + /** + * Whether the action implies the actor should be subscribed on the object + * @param PhabricatorLiskDAO $object on which the action is performed + * @param PhabricatorApplicationTransaction $xaction Transaction to apply + * @return bool True if the actor should be subscribed on the object + */ protected function shouldImplyCC( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { - return $xaction->isCommentTransaction(); + return ($xaction->isCommentTransaction() && + !($xaction->getComment()->getIsRemoved())); } diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index 01ebc178cb..bfaa252d40 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -1580,6 +1580,10 @@ abstract class PhabricatorApplicationTransaction return 100; } + /** + * Whether the transaction concerns a comment (e.g. add, edit, remove) + * @return bool True if the transaction concerns a comment + */ public function isCommentTransaction() { if ($this->hasComment()) { return true; diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransactionComment.php b/src/applications/transactions/storage/PhabricatorApplicationTransactionComment.php index 00cad18d2d..126510a4b3 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransactionComment.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransactionComment.php @@ -74,6 +74,10 @@ abstract class PhabricatorApplicationTransactionComment return PhabricatorContentSource::newFromSerialized($this->contentSource); } + /** + * Whether the transaction removes the comment + * @return bool True if the transaction removes the comment + */ public function getIsRemoved() { return ($this->getIsDeleted() == 2); } From f6302c7ee3009c5d3b84c4ca84bbaac7da5cc13c Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 19 Jul 2024 15:28:07 +0200 Subject: [PATCH 098/118] Log Herald rules with invalid keys via phlog() Summary: When Herald rules fail unexpectedly due to relying on a (now) invalid key, allow Phorge administrators to get aware by creating an entry in the error log via `phlog()` instead of crossing fingers that someone may from time to time get the idea to open and read Herald transcripts. Refs T15885 Test Plan: See steps in T15885; check error log. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15885 Differential Revision: https://we.phorge.it/D25735 --- src/applications/herald/engine/HeraldEngine.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/applications/herald/engine/HeraldEngine.php b/src/applications/herald/engine/HeraldEngine.php index 43c3197e1a..7987301992 100644 --- a/src/applications/herald/engine/HeraldEngine.php +++ b/src/applications/herald/engine/HeraldEngine.php @@ -532,8 +532,12 @@ final class HeraldEngine extends Phobject { if ($caught) { $result_data = array( 'exception.class' => get_class($caught), - 'exception.message' => $ex->getMessage(), + 'exception.message' => $caught->getMessage(), ); + phlog(pht('An exception occurred executing Herald rule %s: "%s" Review '. + 'the Herald transcripts and correct or disable the problematic rule.', + $rule->getMonogram(), + $caught->getMessage())); } $result = HeraldConditionResult::newFromResultCode($result_code) From 71615f4b99175540815fb00135d611e216bc33df Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Sun, 21 Jul 2024 19:20:29 +0200 Subject: [PATCH 099/118] Log Herald rules having disabled Herald rules as condition via phlog() Summary: Do not silently fail when a Herald rule refers in its conditions to a disabled rule. Allow Phorge administrators to get aware by creating an entry in the error log via phlog() instead of crossing fingers that someone may from time to time get the idea to open and read Herald transcripts. This is similar to T15885 / D25735. Refs T15869 Test Plan: 1. Phorge at `0873b36569bfcf502a092870d7d5f4d088ed9003` 2. Set up a Herald rule and disable it 3. Set up a second Herald rule with several conditions, the first condition being "Another Herald rule matches" on the disabled first Herald rule 4. Go to http://phorge.localhost/herald/test/ to apply the Herald rule on a corresponding object 5. See an exception in the resulting transcript at http://phorge.localhost/herald/transcript/123456/ 6. After applying this patch, also find that exception in the error log Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15869 Differential Revision: https://we.phorge.it/D25736 --- src/applications/herald/engine/HeraldEngine.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/herald/engine/HeraldEngine.php b/src/applications/herald/engine/HeraldEngine.php index 7987301992..5569c3ddfd 100644 --- a/src/applications/herald/engine/HeraldEngine.php +++ b/src/applications/herald/engine/HeraldEngine.php @@ -593,6 +593,10 @@ final class HeraldEngine extends Phobject { $this->popProfilerRule($rule); if ($caught) { + phlog(pht('An exception occurred executing Herald rule %s: "%s" Review '. + 'the Herald transcripts and correct or disable the problematic rule.', + $rule->getMonogram(), + $caught->getMessage())); throw $caught; } From 0bfe9ca88f412acd9c40a2456dc450c4821c8ddf Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Mon, 22 Jul 2024 13:38:32 +0200 Subject: [PATCH 100/118] Log Herald rules with invalid actions via phlog() Summary: When Herald rules fail unexpectedly due to relying on a (now) invalid action, allow Phorge administrators to get aware by creating an entry in the error log via `phlog()` instead of crossing fingers that someone may from time to time get the idea to open and read Herald transcripts. Refs T15887 Test Plan: See steps in T15887; check error log. Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: avivey, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15887 Differential Revision: https://we.phorge.it/D25737 --- src/applications/herald/engine/HeraldEngine.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/applications/herald/engine/HeraldEngine.php b/src/applications/herald/engine/HeraldEngine.php index 5569c3ddfd..852fbab090 100644 --- a/src/applications/herald/engine/HeraldEngine.php +++ b/src/applications/herald/engine/HeraldEngine.php @@ -688,6 +688,14 @@ final class HeraldEngine extends Phobject { ->setTarget($action->getTarget()) ->setRule($rule); + if ($object->getActionImplementation($action->getAction()) === null) { + phlog(pht('An exception occurred executing Herald rule %s: Unknown '. + 'action: "%s". Review the Herald transcripts and correct or '. + 'disable the problematic rule.', + $rule->getMonogram(), + $action->getAction())); + } + $name = $rule->getName(); $id = $rule->getID(); $effect->setReason( From ec6ba69666867ed1e1dd2da2ce44447a9db44ac6 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 30 Jul 2024 19:24:31 +0200 Subject: [PATCH 101/118] Add Copy file path button to file header bar in Differential/Diffusion Summary: When looking at a commit/revision, allow to copy the path of a file to the clipboard, so you can quickly open that file in your local code checkout from the command line. Closes T15897 Test Plan: Open a proposed commit in Differential or a merged commit in Diffusion. Click the "Copy file path" button, paste that clipboard content. Change the width of the viewport and see that the button is not rendered when in phone mode. Run `./bin/celerity map` again and no changes. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15897 Differential Revision: https://we.phorge.it/D25757 --- resources/celerity/map.php | 12 ++++---- .../view/DifferentialChangesetDetailView.php | 29 +++++++++++++++++-- .../differential/changeset-view.css | 11 ++++++- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c6c577aa3a..0911be5e57 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -12,7 +12,7 @@ return array( 'core.pkg.css' => 'ac619266', 'core.pkg.js' => '2eeda9e0', 'dark-console.pkg.js' => '187792c2', - 'differential.pkg.css' => '6d3700f0', + 'differential.pkg.css' => 'fda9518d', 'differential.pkg.js' => '46fcb3af', 'diffusion.pkg.css' => '354279ea', 'diffusion.pkg.js' => '78c9885d', @@ -63,7 +63,7 @@ return array( 'rsrc/css/application/diff/diff-tree-view.css' => 'e2d3e222', 'rsrc/css/application/diff/inline-comment-summary.css' => '81eb368d', 'rsrc/css/application/differential/add-comment.css' => '7e5900d9', - 'rsrc/css/application/differential/changeset-view.css' => '1b0476bc', + 'rsrc/css/application/differential/changeset-view.css' => '979e688c', 'rsrc/css/application/differential/core.css' => '7300a73e', 'rsrc/css/application/differential/phui-inline-comment.css' => '9863a85e', 'rsrc/css/application/differential/revision-comment.css' => '7dbc8d1d', @@ -560,7 +560,7 @@ return array( 'conpherence-transaction-css' => '3a3f5e7e', 'd3' => 'e97b4b78', 'diff-tree-view-css' => 'e2d3e222', - 'differential-changeset-view-css' => '1b0476bc', + 'differential-changeset-view-css' => '979e688c', 'differential-core-view-css' => '7300a73e', 'differential-revision-add-comment-css' => '7e5900d9', 'differential-revision-comment-css' => '7dbc8d1d', @@ -1056,9 +1056,6 @@ return array( 'javelin-util', 'phabricator-keyboard-shortcut-manager', ), - '1b0476bc' => array( - 'phui-inline-comment-view-css', - ), '1b6acc2a' => array( 'javelin-magical-init', 'javelin-util', @@ -1777,6 +1774,9 @@ return array( 'javelin-stratcom', 'phabricator-notification', ), + '979e688c' => array( + 'phui-inline-comment-view-css', + ), '98ef467f' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/differential/view/DifferentialChangesetDetailView.php b/src/applications/differential/view/DifferentialChangesetDetailView.php index 9d04d081ac..8dfe71f2d3 100644 --- a/src/applications/differential/view/DifferentialChangesetDetailView.php +++ b/src/applications/differential/view/DifferentialChangesetDetailView.php @@ -233,6 +233,22 @@ final class DifferentialChangesetDetailView extends AphrontView { $show_directory_uri = phutil_string_cast($show_directory_uri); } + Javelin::initBehavior('phabricator-clipboard-copy'); + $path_copy_button = id(new PHUIButtonView()) + ->setTag('a') + ->setColor(PHUIButtonView::GREY) + ->setIcon('fa-clipboard') + ->setHref('#') + ->addSigil('clipboard-copy') + ->addSigil('has-tooltip') + ->setMetadata( + array( + 'tip' => pht('Copy file path'), + 'text' => $display_filename, + 'successMessage' => pht('File path copied.'), + 'errorMessage' => pht('Copy of file path failed.'), + )); + return javelin_tag( 'div', array( @@ -278,14 +294,23 @@ final class DifferentialChangesetDetailView extends AphrontView { 'sigil' => 'changeset-header', ), array( - $icon, + javelin_tag( + 'span', + array( + 'class' => 'differential-changeset-path-copy-button', + 'meta' => array( + 'selectID' => $display_filename, + 'once' => true, + ), + ), + $path_copy_button), javelin_tag( 'span', array( 'class' => 'differential-changeset-path-name', 'sigil' => 'changeset-header-path-name', ), - $display_filename), + $display_filename), )), javelin_tag( 'div', diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index a127d14648..f41bd8a167 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -340,7 +340,7 @@ td.cov-I { .differential-file-icon-header .phui-icon-view { display: inline-block; - margin: 0 6px 2px 0; + margin: 0 0 2px 0; vertical-align: middle; font-size: 14px; } @@ -349,6 +349,15 @@ td.cov-I { display: none; } +.differential-changeset-path-copy-button { + margin-right: 0; + padding-right: 10px; +} + +.device-phone .differential-changeset-path-copy-button { + display: none; +} + .differential-changeset-buttons { float: right; margin-top: 16px; From 1cc04fb83cd50d55b18a9fa72e99a58fdd2f8eaa Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 3 May 2024 18:02:23 +0200 Subject: [PATCH 102/118] Explicitly cast "limit" (page size) API parameter to int Summary: Do not throw an exception when the `limit` (page size) Conduit API parameter is a float but explicitly convert to int. As an admin, I am not interested in having invalid user-committed query data trigger an error in the server logs. ``` ERROR 8192: Implicit conversion from float to int loses precision at [/var/www/html/phorge/phorge/src/view/control/AphrontCursorPagerView.php:76] ``` Closes T15810 Test Plan: Call `/conduit/method/maniphest.search/` with a float value for the `limit` field. Check the server logs or DarkConsole. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15810 Differential Revision: https://we.phorge.it/D25614 --- src/view/control/AphrontCursorPagerView.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/view/control/AphrontCursorPagerView.php b/src/view/control/AphrontCursorPagerView.php index cdb9562624..cf4563fb9e 100644 --- a/src/view/control/AphrontCursorPagerView.php +++ b/src/view/control/AphrontCursorPagerView.php @@ -72,8 +72,9 @@ final class AphrontCursorPagerView extends AphrontView { public function sliceResults(array $results) { if (count($results) > $this->getPageSize()) { - $offset = ($this->beforeID ? count($results) - $this->getPageSize() : 0); - $results = array_slice($results, $offset, $this->getPageSize(), true); + $page_size = (int)$this->getPageSize(); + $offset = ($this->beforeID ? count($results) - $page_size : 0); + $results = array_slice($results, $offset, $page_size, true); $this->moreResults = true; } return $results; From 89778dc9e617b4edcdcbb457adf7b034e2b87c1b Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Mon, 20 May 2024 22:28:50 +0200 Subject: [PATCH 103/118] Add Open Graph protocol meta tags to Maniphest task pages Summary: Add OGP tags to Maniphest task pages when the task is publicly accessible and anonymously accessed. See https://ogp.me/ Based on rP2c72c2b924ffa3f8a49dbec636a2cdca3bae004f reverted in rP49b57eae7df52c189aef1d973823c697fc97fd4b. Closes T15472 Test Plan: * Use the default Phorge logo, open a Maniphest task, look at the headers in its HTML. * Set a custom Phorge logo via `config/edit/ui.logo/`. * Access a task with "View Policy: All Users" while logged in: No OGP headers included. * Access a task with "View Policy: Public" while logged in: No OGP headers included. * Access a task with "View Policy: All Users" while logged out: No OGP headers included; "Access Denied: Restricted Maniphest Task" displayed. * Access a task with "View Policy: Public" while logged out: OGP headers included. * Access a task with "View Policy: Public" while logged out with a task description and a task without a task description: OGP headers included. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: avivey, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15472 Differential Revision: https://we.phorge.it/D25668 --- .../PhabricatorCustomLogoConfigType.php | 39 ++++++++++ .../ManiphestTaskDetailController.php | 71 ++++++++++++++++++- src/view/page/PhabricatorStandardPageView.php | 1 - .../page/menu/PhabricatorMainMenuView.php | 34 ++------- 4 files changed, 114 insertions(+), 31 deletions(-) diff --git a/src/applications/config/custom/PhabricatorCustomLogoConfigType.php b/src/applications/config/custom/PhabricatorCustomLogoConfigType.php index cc20768119..6811f618cf 100644 --- a/src/applications/config/custom/PhabricatorCustomLogoConfigType.php +++ b/src/applications/config/custom/PhabricatorCustomLogoConfigType.php @@ -13,6 +13,45 @@ final class PhabricatorCustomLogoConfigType return idx($logo, 'wordmarkText'); } + /** + * Return the full URI of the Phorge logo + * @param PhabricatorUser Current viewer + * @return string Full URI of the Phorge logo + */ + public static function getLogoURI(PhabricatorUser $viewer) { + $logo_uri = null; + + $custom_header = self::getLogoImagePHID(); + if ($custom_header) { + $cache = PhabricatorCaches::getImmutableCache(); + $cache_key_logo = 'ui.custom-header.logo-phid.v3.'.$custom_header; + $logo_uri = $cache->getKey($cache_key_logo); + + if (!$logo_uri) { + // NOTE: If the file policy has been changed to be restrictive, we'll + // miss here and just show the default logo. The cache will fill later + // when someone who can see the file loads the page. This might be a + // little spooky, see T11982. + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($custom_header)) + ->execute(); + $file = head($files); + if ($file) { + $logo_uri = $file->getViewURI(); + $cache->setKey($cache_key_logo, $logo_uri); + } + } + } + + if (!$logo_uri) { + $logo_uri = + celerity_get_resource_uri('/rsrc/image/logo/project-logo.png'); + } + + return $logo_uri; + } + public function validateOption(PhabricatorConfigOption $option, $value) { if (!is_array($value)) { throw new Exception( diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index af23126f9b..fe79cd48b3 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -203,8 +203,7 @@ final class ManiphestTaskDetailController extends ManiphestController { ->addPropertySection(pht('Description'), $description) ->addPropertySection(pht('Details'), $details); - - return $this->newPage() + $page = $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->setPageObjectPHIDs( @@ -213,6 +212,74 @@ final class ManiphestTaskDetailController extends ManiphestController { )) ->appendChild($view); + if ($this->getIncludeOpenGraphMetadata($viewer, $task)) { + $page = $this->addOpenGraphProtocolMetadataTags($page, $task); + } + + return $page; + } + + /** + * Whether the page should include Open Graph metadata tags + * @param PhabricatorUser $viewer Viewer of the object + * @param object $object + * @return bool True if the page should serve Open Graph metadata tags + */ + private function getIncludeOpenGraphMetadata(PhabricatorUser $viewer, + $object) { + // Don't waste time adding OpenGraph metadata for logged-in users + if ($viewer->getIsStandardUser()) { + return false; + } + // Include OpenGraph tags only for public objects + return $object->getViewPolicy() === PhabricatorPolicies::POLICY_PUBLIC; + } + + /** + * Get Open Graph Protocol metadata values + * @param ManiphestTask $task + * @return array Map of Open Graph property => value + */ + private function getOpenGraphProtocolMetadataValues($task) { + $viewer = $this->getViewer(); + + $v = []; + $v['og:site_name'] = PlatformSymbols::getPlatformServerName(); + $v['og:type'] = 'object'; + $v['og:url'] = PhabricatorEnv::getProductionURI($task->getURI()); + $v['og:title'] = $task->getMonogram().' '.$task->getTitle(); + + $desc = $task->getDescription(); + if (phutil_nonempty_string($desc)) { + $v['og:description'] = + PhabricatorMarkupEngine::summarizeSentence($desc); + } + + $v['og:image'] = + PhabricatorCustomLogoConfigType::getLogoURI($viewer); + + $v['og:image:height'] = 64; + $v['og:image:width'] = 64; + + return $v; + } + + /** + * Add Open Graph Protocol metadata tags to Maniphest task page + * @param PhabricatorStandardPageView $page + * @param ManiphestTask $task + * @return $page with additional OGP tags + */ + private function addOpenGraphProtocolMetadataTags($page, $task) { + foreach ($this->getOpenGraphProtocolMetadataValues($task) as $k => $v) { + $page->addHeadItem(phutil_tag( + 'meta', + array( + 'property' => $k, + 'content' => $v, + ))); + } + return $page; } private function buildHeaderView(ManiphestTask $task) { diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index 005ede23a7..eb7b775f0f 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -378,7 +378,6 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView /** * Insert a HTML element into of the page to render. - * Used by PhameBlogViewController. * * @param PhutilSafeHTML HTML header to add */ diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index f0b2bbbe83..8db27628d9 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -293,35 +293,14 @@ final class PhabricatorMainMenuView extends AphrontView { } private function renderPhabricatorLogo() { - $custom_header = PhabricatorCustomLogoConfigType::getLogoImagePHID(); - $logo_style = array(); + $custom_header = PhabricatorCustomLogoConfigType::getLogoImagePHID(); if ($custom_header) { - $cache = PhabricatorCaches::getImmutableCache(); - $cache_key_logo = 'ui.custom-header.logo-phid.v3.'.$custom_header; - - $logo_uri = $cache->getKey($cache_key_logo); - if (!$logo_uri) { - // NOTE: If the file policy has been changed to be restrictive, we'll - // miss here and just show the default logo. The cache will fill later - // when someone who can see the file loads the page. This might be a - // little spooky, see T11982. - $files = id(new PhabricatorFileQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs(array($custom_header)) - ->execute(); - $file = head($files); - if ($file) { - $logo_uri = $file->getViewURI(); - $cache->setKey($cache_key_logo, $logo_uri); - } - } - - if ($logo_uri) { - $logo_style[] = 'background-size: 40px 40px;'; - $logo_style[] = 'background-position: 0 0;'; - $logo_style[] = 'background-image: url('.$logo_uri.')'; - } + $viewer = $this->getViewer(); + $logo_uri = PhabricatorCustomLogoConfigType::getLogoURI($viewer); + $logo_style[] = 'background-size: 40px 40px;'; + $logo_style[] = 'background-position: 0 0;'; + $logo_style[] = 'background-image: url('.$logo_uri.')'; } $logo_node = phutil_tag( @@ -331,7 +310,6 @@ final class PhabricatorMainMenuView extends AphrontView { 'style' => implode(' ', $logo_style), )); - $wordmark_text = PhabricatorCustomLogoConfigType::getLogoWordmark(); if (!phutil_nonempty_string($wordmark_text)) { $wordmark_text = PlatformSymbols::getPlatformServerName(); From 88225b99f2fc9c039e9dba56b06200357307832f Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Mon, 5 Aug 2024 13:42:53 +0200 Subject: [PATCH 104/118] Fix PHP 8.1 "strlen(null)" exception in ManiphestTaskPointsTransaction Summary: `strlen()` was used in Phabricator to check if a generic value is a non-empty string. This behavior is deprecated since PHP 8.1. Phorge adopts `phutil_nonempty_string()` as a replacement. Note: this may highlight other absurd input values that might be worth correcting instead of just ignoring. If phutil_nonempty_string() throws an exception in your instance, report it to Phorge to evaluate and fix that specific corner case. ``` ERROR 8192: strlen(): Passing null to parameter #1 ($string) of type string is deprecated at [/var/www/html/phorge/phorge/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php:85] ``` Closes T15902 Test Plan: Unclear due to lack of steps to reproduce. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15902 Differential Revision: https://we.phorge.it/D25765 --- .../maniphest/xaction/ManiphestTaskPointsTransaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php index fae125dccd..b856507374 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php @@ -82,7 +82,7 @@ final class ManiphestTaskPointsTransaction foreach ($xactions as $xaction) { $new = $xaction->getNewValue(); - if (strlen($new) && !is_numeric($new)) { + if (phutil_nonempty_string($new) && !is_numeric($new)) { $errors[] = $this->newInvalidError( pht('Points value must be numeric or empty.')); continue; From b74f1ad519e8a0d07dc1136f5927cd58d9aed8e5 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Sat, 3 Aug 2024 00:39:19 +0200 Subject: [PATCH 105/118] Fix 404 link in Diviner to "Configuring File Storage" article Summary: Fix a broken link in documentation. Test Plan: 1. Go to https://we.phorge.it/book/dev/class/PhabricatorFileStorageEngine/ 2. Click link "For more information, see File Storage Technical Documentation." 3. Get a 404 via https://we.phorge.it/diviner/find/?name=File_Storage_Technical_Documentation&type=article&jump=1 beforehand; get an article via https://we.phorge.it/diviner/find/?name=Configuring_File_Storage&type=article&jump=1 afterhand. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25762 --- src/applications/files/engine/PhabricatorFileStorageEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/files/engine/PhabricatorFileStorageEngine.php b/src/applications/files/engine/PhabricatorFileStorageEngine.php index 4f2847f8e0..a1e5131614 100644 --- a/src/applications/files/engine/PhabricatorFileStorageEngine.php +++ b/src/applications/files/engine/PhabricatorFileStorageEngine.php @@ -7,7 +7,7 @@ * * You can extend this class to provide new file storage backends. * - * For more information, see @{article:File Storage Technical Documentation}. + * For more information, see @{article:Configuring File Storage}. * * @task construct Constructing an Engine * @task meta Engine Metadata From 2b7c0ec92f6c5662d1fe332725b29df11721f98d Mon Sep 17 00:00:00 2001 From: Dylan F Date: Sat, 10 Aug 2024 22:15:10 +0200 Subject: [PATCH 106/118] Destroy file attachments when file is deleted, or object is deleted Summary: Adds file attachment deletion logics: - PhabricatorFile: delete the attachment if file is deleted - destruction engine extension: delete attachment if object is deleted - SQL patch: delete existing leftover attachments from deleted files To apply the cleanup, as usual, run: ./bin/storage upgrade This cleanup may take some time, proportionally to the size of these tables: phabricator_file.file phabricator_file.file_attachment Just as an indication: the storage upgrade, in a Phorge with `file` count 1.3M rows and `file_attachment` consisting in 9K rows, it may delete 170K rows in less than 1 second on average hardware. Closes T15110 Test Plan: Apply the patch, run `./bin/storage/upgrade`: - no "Unknown Object" in any "Referenced Files" curtain of any object. Have phd daemon running. Upload file, attach the file to a task, delete the file from the web interface: - no "Unknown Object" in "Referenced Files" curtain of that task. - the query `SELECT * FROM file_attachment WHERE filePHID = ''` returns empty result Upload file, attach the file to a task, delete the task from the `./bin/remove destroy` workflow: - the query `SELECT * FROM file_attachment WHERE objectPHID = ''` returns empty result Reviewers: O1 Blessed Committers, avivey, valerio.bozzolan Reviewed By: O1 Blessed Committers, avivey, valerio.bozzolan Subscribers: Ekubischta, speck, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Tags: #files Maniphest Tasks: T15110 Differential Revision: https://we.phorge.it/D25051 --- .../20230917.fileattachment.01.delete.sql | 6 +++++ src/__phutil_library_map__.php | 2 ++ ...leAttachmentDestructionEngineExtension.php | 22 +++++++++++++++++++ .../files/storage/PhabricatorFile.php | 8 +++++++ 4 files changed, 38 insertions(+) create mode 100644 resources/sql/autopatches/20230917.fileattachment.01.delete.sql create mode 100644 src/applications/files/engineextension/PhabricatorFileAttachmentDestructionEngineExtension.php diff --git a/resources/sql/autopatches/20230917.fileattachment.01.delete.sql b/resources/sql/autopatches/20230917.fileattachment.01.delete.sql new file mode 100644 index 0000000000..74adf25f52 --- /dev/null +++ b/resources/sql/autopatches/20230917.fileattachment.01.delete.sql @@ -0,0 +1,6 @@ +USE {$NAMESPACE}_file; + DELETE FROM file_attachment + WHERE NOT EXISTS + (SELECT * + FROM file + WHERE phid=file_attachment.filePHID) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 396bb1a308..5c7e655cd1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3453,6 +3453,7 @@ phutil_register_library_map(array( 'PhabricatorFileAES256StorageFormat' => 'applications/files/format/PhabricatorFileAES256StorageFormat.php', 'PhabricatorFileAltTextTransaction' => 'applications/files/xaction/PhabricatorFileAltTextTransaction.php', 'PhabricatorFileAttachment' => 'applications/files/storage/PhabricatorFileAttachment.php', + 'PhabricatorFileAttachmentDestructionEngineExtension' => 'applications/files/engineextension/PhabricatorFileAttachmentDestructionEngineExtension.php', 'PhabricatorFileAttachmentQuery' => 'applications/files/query/PhabricatorFileAttachmentQuery.php', 'PhabricatorFileBundleLoader' => 'applications/files/query/PhabricatorFileBundleLoader.php', 'PhabricatorFileChunk' => 'applications/files/storage/PhabricatorFileChunk.php', @@ -9924,6 +9925,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorExtendedPolicyInterface', ), + 'PhabricatorFileAttachmentDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'PhabricatorFileAttachmentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorFileBundleLoader' => 'Phobject', 'PhabricatorFileChunk' => array( diff --git a/src/applications/files/engineextension/PhabricatorFileAttachmentDestructionEngineExtension.php b/src/applications/files/engineextension/PhabricatorFileAttachmentDestructionEngineExtension.php new file mode 100644 index 0000000000..6d62dfa601 --- /dev/null +++ b/src/applications/files/engineextension/PhabricatorFileAttachmentDestructionEngineExtension.php @@ -0,0 +1,22 @@ +loadAllWhere( + 'objectPHID = %s', + $object->getPHID()); + foreach ($attachments as $attachment) { + $attachment->delete(); + } + } +} diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 3918adcec2..ffa29f1919 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -1785,6 +1785,14 @@ final class PhabricatorFile extends PhabricatorFileDAO PhabricatorDestructionEngine $engine) { $this->openTransaction(); + + $attachments = id(new PhabricatorFileAttachment())->loadAllWhere( + 'filePHID = %s', + $this->getPHID()); + foreach ($attachments as $attachment) { + $attachment->delete(); + } + $this->delete(); $this->saveTransaction(); } From 58f1c83a5ac56073a1960772c78c99ece72d4729 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Sun, 11 Aug 2024 16:59:32 +0200 Subject: [PATCH 107/118] Fix PHP 8.1 "strlen(null)" exception in DivinerFindController Summary: `strlen()` was used in Phabricator to check if a generic value is a non-empty string. This behavior is deprecated since PHP 8.1. Phorge adopts `phutil_nonempty_string()` as a replacement. Note: this may highlight other absurd input values that might be worth correcting instead of just ignoring. If phutil_nonempty_string() throws an exception in your instance, report it to Phorge to evaluate and fix that specific corner case. ``` ERROR 8192: strlen(): Passing null to parameter #1 ($string) of type string is deprecated at [/var/www/html/phorge/phorge/src/applications/diviner/controller/DivinerFindController.php:40] ``` Closes T15910 Test Plan: Go to this (or any other Diviner Find URL not passing a `type` URI parameter): now it works: http://phorge.localhost/diviner/find/?name=PhutilSafeHTML Go to this (or any other Diviner Find URL passing a `type` URI parameter): it still works: http://phorge.localhost/diviner/find/?name=PhutilSafeHTML&type=class Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15910 Differential Revision: https://we.phorge.it/D25768 --- src/applications/diviner/controller/DivinerFindController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diviner/controller/DivinerFindController.php b/src/applications/diviner/controller/DivinerFindController.php index 2e3b121706..d6ddd420fd 100644 --- a/src/applications/diviner/controller/DivinerFindController.php +++ b/src/applications/diviner/controller/DivinerFindController.php @@ -37,7 +37,7 @@ final class DivinerFindController extends DivinerController { } $type = $request->getStr('type'); - if (strlen($type)) { + if (phutil_nonempty_string($type)) { $query->withTypes(array($type)); } From b9ea6f1ce823dcd83a431f5913d9b1fe29dd25bd Mon Sep 17 00:00:00 2001 From: Yongmin Hong Date: Tue, 13 Aug 2024 11:24:19 +0200 Subject: [PATCH 108/118] docs/file_domain: remove cloudflare mention of autominify Summary: [Auto Minify](https://developers.cloudflare.com/speed/optimization/content/auto-minify/)[1][2] is deprecated and will be removed after 2024-08-05. (For distant future where Cloudflare deletes the docs) [1]: [web.archive.org](https://web.archive.org/web/20240507123639/https://developers.cloudflare.com/speed/optimization/content/auto-minify/) [2]: [archive.today](https://archive.md/wWPpM) Signed-off-by: Yongmin Hong Test Plan: 1. Wait for 2024-08-05 2. Browse to `https://dash.cloudflare.com///speed/optimization/content` 3. Verify "Auto Minify" is gone Reviewers: O1 Blessed Committers, avivey Reviewed By: O1 Blessed Committers, avivey Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25617 --- .../user/configuration/configuring_file_domain.diviner | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/docs/user/configuration/configuring_file_domain.diviner b/src/docs/user/configuration/configuring_file_domain.diviner index 1fc4ef3213..b06ff6cc6b 100644 --- a/src/docs/user/configuration/configuring_file_domain.diviner +++ b/src/docs/user/configuration/configuring_file_domain.diviner @@ -66,11 +66,10 @@ Approach: Cloudflare ======== WARNING: You should review all your Cloudflare settings, and be very -sure to turn off all JavaScript, HTML, CSS minification and -optimization features, including systems like "Rocket Loader". These -features will break Phorge in strange and mysterious ways that -are unpredictable. Only allow Cloudflare to cache files, and never -optimize them. +sure to turn off all optimization features, including systems like +"Rocket Loader". These features will break Phorge in strange and +mysterious ways that are unpredictable. Only allow Cloudflare to cache files, +and never optimize them. [[ https://www.cloudflare.com | Cloudflare ]] is a general-purpose CDN service. From edb2175a9bb5e6edc4348cad307c4f5303ab28c6 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 15 Aug 2024 16:02:33 +0200 Subject: [PATCH 109/118] Fix PhabricatorEmptyQueryException handling in DiffusionCommitQuery::loadPage() Summary: Add an additional check that `$empty_exception` is not `null` before try to `throw` it. Test Plan: Read the previous code; run static code analysis. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25781 --- src/applications/diffusion/query/DiffusionCommitQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php index e1bec46441..18b2dbacde 100644 --- a/src/applications/diffusion/query/DiffusionCommitQuery.php +++ b/src/applications/diffusion/query/DiffusionCommitQuery.php @@ -252,7 +252,7 @@ final class DiffusionCommitQuery $table->getTableName()); } - if (!$subqueries) { + if (!$subqueries && $empty_exception) { throw $empty_exception; } From 66bd13b193b666e77c00ccea0fea508134c5397a Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 16 Aug 2024 09:17:39 +0200 Subject: [PATCH 110/118] Remove unused PHUICalendarMonthView::getMaxDailyEventsForWeek() Summary: Call to this private function was removed in https://we.phorge.it/rPaee9d88c17e25b924ecd5293691cc6fe38d6c7d9. Test Plan: Grep the code; run static code analysis. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25788 --- src/view/phui/calendar/PHUICalendarMonthView.php | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index 752a9afa15..be5e50f667 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -193,18 +193,6 @@ final class PHUICalendarMonthView extends AphrontView { return $box; } - private function getMaxDailyEventsForWeek($week_of_cell_lists) { - $max_count = 0; - - foreach ($week_of_cell_lists as $cell_list) { - if ($cell_list['count'] > $max_count) { - $max_count = $cell_list['count']; - } - } - - return $max_count; - } - private function getEventListCell($event_list) { $list = $event_list['list']; $class = $event_list['class']; From 9c684c80d63d08194c6d1d175a64e8f91ec07f84 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Wed, 14 Aug 2024 21:52:57 +0200 Subject: [PATCH 111/118] Fix various potential PHP 8.1 "strlen(null)" exceptions Summary: `strlen()` was used in Phabricator to check if a generic value is a non-empty string. This behavior is deprecated since PHP 8.1. Phorge adopts `phutil_nonempty_string()` as a replacement. Note: this may highlight other absurd input values that might be worth correcting instead of just ignoring. If phutil_nonempty_string() throws an exception in your instance, report it to Phorge to evaluate and fix that specific corner case. All lines changed in this patch had `Parameter #1 $string of function strlen expects string, string|null given` reported by PHPStan. Thus these should be safe to replace with `phutil_nonempty_string` as no calls care about the actual `strlen()` return value (length of a string). Test Plan: Run static code analysis via `phpstan analyse -l 9` Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25778 --- .../auth/adapter/PhutilBitbucketAuthAdapter.php | 2 +- src/applications/auth/adapter/PhutilGitHubAuthAdapter.php | 2 +- src/applications/auth/adapter/PhutilTwitterAuthAdapter.php | 2 +- .../query/lowlevel/DiffusionLowLevelCommitQuery.php | 6 +++--- .../markup/rule/PhabricatorKeyboardRemarkupRule.php | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/applications/auth/adapter/PhutilBitbucketAuthAdapter.php b/src/applications/auth/adapter/PhutilBitbucketAuthAdapter.php index 1384548d5e..baf9d5407e 100644 --- a/src/applications/auth/adapter/PhutilBitbucketAuthAdapter.php +++ b/src/applications/auth/adapter/PhutilBitbucketAuthAdapter.php @@ -14,7 +14,7 @@ final class PhutilBitbucketAuthAdapter extends PhutilOAuth1AuthAdapter { public function getAccountURI() { $name = $this->getAccountID(); - if (strlen($name)) { + if (phutil_nonempty_string($name)) { return 'https://bitbucket.org/'.$name; } return null; diff --git a/src/applications/auth/adapter/PhutilGitHubAuthAdapter.php b/src/applications/auth/adapter/PhutilGitHubAuthAdapter.php index a9d4c849d5..5d85a2ac21 100644 --- a/src/applications/auth/adapter/PhutilGitHubAuthAdapter.php +++ b/src/applications/auth/adapter/PhutilGitHubAuthAdapter.php @@ -31,7 +31,7 @@ final class PhutilGitHubAuthAdapter extends PhutilOAuthAuthAdapter { public function getAccountURI() { $name = $this->getAccountName(); - if (strlen($name)) { + if (phutil_nonempty_string($name)) { return 'https://github.com/'.$name; } return null; diff --git a/src/applications/auth/adapter/PhutilTwitterAuthAdapter.php b/src/applications/auth/adapter/PhutilTwitterAuthAdapter.php index 6f738c75f6..02a1f59a67 100644 --- a/src/applications/auth/adapter/PhutilTwitterAuthAdapter.php +++ b/src/applications/auth/adapter/PhutilTwitterAuthAdapter.php @@ -17,7 +17,7 @@ final class PhutilTwitterAuthAdapter extends PhutilOAuth1AuthAdapter { public function getAccountURI() { $name = $this->getAccountName(); - if (strlen($name)) { + if (phutil_nonempty_string($name)) { return 'https://twitter.com/'.$name; } return null; diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitQuery.php index 8b9233f128..0b5df04a41 100644 --- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitQuery.php +++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitQuery.php @@ -126,11 +126,11 @@ final class DiffusionLowLevelCommitQuery $head = $parts[6]; $tail = $parts[7]; - if (strlen($head) && strlen($tail)) { + if (phutil_nonempty_string($head) && phutil_nonempty_string($tail)) { $body = $head."\n\n".$tail; - } else if (strlen($head)) { + } else if (phutil_nonempty_string($head)) { $body = $head; - } else if (strlen($tail)) { + } else if (phutil_nonempty_string($tail)) { $body = $tail; } else { $body = ''; diff --git a/src/infrastructure/markup/rule/PhabricatorKeyboardRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorKeyboardRemarkupRule.php index c5344a692c..a28735cfe9 100644 --- a/src/infrastructure/markup/rule/PhabricatorKeyboardRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorKeyboardRemarkupRule.php @@ -22,7 +22,7 @@ final class PhabricatorKeyboardRemarkupRule extends PhutilRemarkupRule { foreach ($keys as $k => $v) { $v = trim($v, " \n"); $v = preg_replace('/\\\\(.)/', '\\1', $v); - if (!strlen($v)) { + if ($v === '') { unset($keys[$k]); continue; } From cf5abd9e4b6bff2efc4bb7464cfbd5789934e1e0 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 16 Aug 2024 08:52:50 +0200 Subject: [PATCH 112/118] Remove unused HeraldAdapter:buildTokenizerFieldValue() Summary: This private function was added in https://we.phorge.it/rP715233fb611569b86f8fd4d8339a7e514c749d69. Its last use was removed in https://we.phorge.it/rP776caa507b15ef013d3d623d30b294181e2b4c11. Test Plan: Grep the code; run static code analysis. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25785 --- src/applications/herald/adapter/HeraldAdapter.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index ce6f371d91..c04a10ea51 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -843,16 +843,6 @@ abstract class HeraldAdapter extends Phobject { return $impl->getHeraldActionValueType(); } - private function buildTokenizerFieldValue( - PhabricatorTypeaheadDatasource $datasource) { - - $key = 'action.'.get_class($datasource); - - return id(new HeraldTokenizerFieldValue()) - ->setKey($key) - ->setDatasource($datasource); - } - /* -( Repetition )--------------------------------------------------------- */ From e1e1570c116594ac60f20a110c1c262a77097727 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 16 Aug 2024 08:45:05 +0200 Subject: [PATCH 113/118] Remove unused methods from ConpherenceDurableColumnView Summary: Calls to private methods `ConpherenceDurableColumnView::buildStatusText()` and `ConpherenceDurableColumnView::buildSendButton()` were removed in https://we.phorge.it/rP95d174956639aac7525d52368944edfff7cdb824 Test Plan: Grep the code; run static code analysis. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25784 --- .../view/ConpherenceDurableColumnView.php | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/applications/conpherence/view/ConpherenceDurableColumnView.php b/src/applications/conpherence/view/ConpherenceDurableColumnView.php index 83c40aa32e..69778c71ab 100644 --- a/src/applications/conpherence/view/ConpherenceDurableColumnView.php +++ b/src/applications/conpherence/view/ConpherenceDurableColumnView.php @@ -472,23 +472,4 @@ final class ConpherenceDurableColumnView extends AphrontTagView { )); } - private function buildStatusText() { - return null; - } - - private function buildSendButton() { - $conpherence = $this->getSelectedConpherence(); - if (!$conpherence) { - return null; - } - - return javelin_tag( - 'button', - array( - 'class' => 'grey', - 'sigil' => 'conpherence-send-message', - ), - pht('Send')); - } - } From 9f07e5083dfa523c81126c0edf67a3360906b74a Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 20 Aug 2024 13:05:49 +0200 Subject: [PATCH 114/118] Add some DateTime handling related PHPDocs Summary: As I was fiddling with an exception handling invalid DateTime, add some PHPDoc for the next time someone needs to understand this code. Test Plan: Use `get_class()` to check parameters and return values. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25793 --- .../query/PhabricatorCalendarEventSearchEngine.php | 4 ++++ .../form/control/AphrontFormDateControlValue.php | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index 3a6e931515..d553177e4f 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -525,6 +525,10 @@ final class PhabricatorCalendarEventSearchEngine } } + /** + * @param PhabricatorSavedQuery $saved + * @return AphrontFormDateControlValue + */ private function getQueryDateFrom(PhabricatorSavedQuery $saved) { if ($this->calendarYear && $this->calendarMonth) { $viewer = $this->requireViewer(); diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php index b4d3e08a0b..72221dd287 100644 --- a/src/view/form/control/AphrontFormDateControlValue.php +++ b/src/view/form/control/AphrontFormDateControlValue.php @@ -209,6 +209,12 @@ final class AphrontFormDateControlValue extends Phobject { ); } + /** + * Create a DateTime object including timezone + * @param string $date Date, like "2024-08-20" or "2024-07-1" or such + * @param string|null $time Time, like "12:00 AM" or such + * @return DateTime|null + */ private function newDateTime($date, $time) { $date = $this->getStandardDateFormat($date); $time = $this->getStandardTimeFormat($time); @@ -280,10 +286,16 @@ final class AphrontFormDateControlValue extends Phobject { } } + /** + * @return DateTime|null + */ public function getDateTime() { return $this->newDateTime($this->valueDate, $this->valueTime); } + /** + * @return DateTimeZone + */ private function getTimezone() { if ($this->zone) { return $this->zone; From b44b566b1334c474722a6acb5ffa4195f0fe8fa2 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Tue, 20 Aug 2024 18:04:23 +0200 Subject: [PATCH 115/118] Add missing variable names to PHPDoc @param of methods Summary: Add variable names (`$varname` in `@param type $varname explanation`) to PHPDoc method headers, for fun and profit. Closes T15923 Test Plan: * Read the method signatures and their corresponding PHPDoc headers at your fireplace * Still run `./bin/diviner generate` without explosions (though it is very lenient anyway?) Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15923 Differential Revision: https://we.phorge.it/D25794 --- src/aphront/AphrontRequest.php | 30 ++++--- .../AphrontApplicationConfiguration.php | 27 +++--- .../AphrontHTTPParameterType.php | 30 +++---- src/aphront/response/AphrontFileResponse.php | 2 +- .../response/AphrontRedirectResponse.php | 5 +- src/aphront/sink/AphrontHTTPSink.php | 9 +- src/aphront/site/AphrontRoutingMap.php | 8 +- src/aphront/writeguard/AphrontWriteGuard.php | 2 +- .../constants/PhabricatorCommonPasswords.php | 2 +- .../auth/constants/PhabricatorCookies.php | 14 +-- .../controller/PhabricatorAuthController.php | 5 +- .../engine/PhabricatorAuthSessionEngine.php | 89 ++++++++++--------- .../PhabricatorAuthPasswordHashInterface.php | 4 +- .../auth/provider/PhabricatorAuthProvider.php | 6 +- .../base/PhabricatorApplication.php | 14 +-- src/applications/cache/PhabricatorCaches.php | 4 +- .../celerity/CelerityResourceMap.php | 12 +-- .../celerity/CelerityResourceMapGenerator.php | 23 ++--- .../celerity/CelerityResourceTransformer.php | 3 +- src/applications/celerity/api.php | 8 +- .../controller/CelerityResourceController.php | 2 +- .../CelerityManagementMapWorkflow.php | 2 +- .../PhabricatorConduitAPIController.php | 5 +- .../protocol/exception/ConduitException.php | 2 +- .../PhabricatorCustomLogoConfigType.php | 2 +- .../config/issue/PhabricatorSetupIssue.php | 4 +- .../config/json/PhabricatorConfigJSON.php | 3 +- .../PhabricatorApplicationConfigOptions.php | 4 +- ...abricatorDashboardPanelRenderingEngine.php | 2 +- .../constants/DifferentialReviewerStatus.php | 2 +- .../parser/DifferentialChangesetParser.php | 23 ++--- .../parser/DifferentialLineAdjustmentMap.php | 4 +- .../query/DifferentialRevisionQuery.php | 30 +++---- .../DifferentialChangesetHTMLRenderer.php | 13 +-- .../render/DifferentialChangesetRenderer.php | 4 +- .../storage/DifferentialChangeset.php | 2 +- .../data/DiffusionBrowseResultSet.php | 2 +- .../diffusion/data/DiffusionGitBranch.php | 5 +- .../DiffusionMercurialCommandEngine.php | 2 +- .../query/pathid/DiffusionPathIDQuery.php | 6 +- .../diffusion/request/DiffusionRequest.php | 19 ++-- .../diffusion/view/DiffusionReadmeView.php | 2 +- .../diviner/query/DivinerAtomQuery.php | 18 ++-- .../engine/DoorkeeperFeedStoryPublisher.php | 3 +- .../DrydockBlueprintImplementation.php | 68 +++++++------- .../drydock/storage/DrydockAuthorization.php | 8 +- .../drydock/storage/DrydockSlotLock.php | 14 +-- .../worker/DrydockLeaseUpdateWorker.php | 50 ++++++----- .../worker/DrydockResourceUpdateWorker.php | 8 +- .../feed/PhabricatorFeedStoryPublisher.php | 2 +- .../feed/story/PhabricatorFeedStory.php | 5 +- .../files/PhabricatorImageTransformer.php | 10 +-- .../PhabricatorChunkedFileStorageEngine.php | 4 +- .../engine/PhabricatorFileStorageEngine.php | 14 +-- .../PhabricatorLocalDiskFileStorageEngine.php | 2 +- .../PhabricatorMySQLFileStorageEngine.php | 2 +- .../files/query/PhabricatorFileQuery.php | 3 +- .../files/storage/PhabricatorFile.php | 23 ++--- .../PhabricatorFileImageTransform.php | 8 +- .../constants/HarbormasterBuildStatus.php | 2 +- .../engine/HarbormasterBuildEngine.php | 9 +- .../engine/HarbormasterTargetEngine.php | 8 +- .../HarbormasterBuildStepImplementation.php | 8 +- .../storage/HarbormasterBuildable.php | 6 +- .../herald/adapter/HeraldAdapter.php | 3 +- .../constants/ManiphestTaskPriority.php | 4 +- ...abricatorApplicationConfigurationPanel.php | 2 +- .../PhabricatorMetaMTAEmailBodyParser.php | 2 +- .../PhabricatorObjectMailReceiver.php | 6 +- .../PhabricatorMailReplyHandler.php | 16 ++-- .../storage/PhabricatorMetaMTAMail.php | 19 ++-- .../metamta/util/PhabricatorMailUtil.php | 6 +- .../view/PhabricatorMetaMTAMailBody.php | 10 +-- .../query/PhabricatorNotificationQuery.php | 4 +- .../view/PassphraseCredentialControl.php | 6 +- .../people/storage/PhabricatorUser.php | 16 ++-- .../people/storage/PhabricatorUserEmail.php | 8 +- .../phid/PhabricatorObjectHandle.php | 2 +- .../phid/query/PhabricatorObjectQuery.php | 4 +- .../phid/type/PhabricatorPHIDType.php | 21 ++--- src/applications/phid/utils.php | 4 +- .../phrequent/storage/PhrequentTimeBlock.php | 3 +- .../policy/filter/PhabricatorPolicyFilter.php | 12 +-- .../PhabricatorExtendedPolicyInterface.php | 4 +- .../policy/rule/PhabricatorPolicyRule.php | 6 +- .../policy/storage/PhabricatorPolicy.php | 4 +- .../PhabricatorRepositoryPullLocalDaemon.php | 4 +- .../PhabricatorRepositoryDiscoveryEngine.php | 2 +- .../PhabricatorRepositoryPullEngine.php | 2 +- .../engine/PhabricatorRepositoryRefEngine.php | 2 +- .../PhabricatorRepositoryGraphCache.php | 25 +++--- .../storage/PhabricatorRepository.php | 12 +-- .../PhabricatorApplicationSearchEngine.php | 33 +++---- .../search/field/PhabricatorSearchField.php | 18 ++-- .../PhabricatorFulltextStorageEngine.php | 4 +- .../panel/PhabricatorSettingsPanel.php | 4 +- .../query/PhabricatorUserPreferencesQuery.php | 3 +- .../storage/PhabricatorUserPreferences.php | 4 +- .../query/PhabricatorSpacesNamespaceQuery.php | 2 +- .../editor/PhabricatorSubscriptionsEditor.php | 6 +- .../PhabricatorSubscribableInterface.php | 3 +- .../engine/PhabricatorSystemActionEngine.php | 8 +- .../editengine/PhabricatorEditEngine.php | 42 ++++----- .../editfield/PhabricatorEditField.php | 4 +- ...habricatorApplicationTransactionEditor.php | 38 ++++---- .../PhabricatorApplicationTransaction.php | 2 +- .../PhabricatorModularTransactionType.php | 5 +- .../PhabricatorTypeaheadDatasource.php | 2 +- .../cache/PhutilInRequestKeyValueCache.php | 2 +- .../cache/PhutilKeyValueCache.php | 22 ++--- .../cache/PhutilKeyValueCacheProfiler.php | 2 +- .../cache/PhutilKeyValueCacheStack.php | 5 +- .../cache/PhutilMemcacheKeyValueCache.php | 2 +- .../PhabricatorContentSource.php | 8 +- .../field/PhabricatorCustomField.php | 45 +++++----- .../field/PhabricatorCustomFieldList.php | 3 +- .../storage/PhabricatorCustomFieldStorage.php | 2 +- .../daemon/PhabricatorDaemon.php | 2 +- .../daemon/PhutilDaemonHandle.php | 4 +- .../daemon/PhutilDaemonOverseerModule.php | 4 +- .../workers/PhabricatorTriggerDaemon.php | 4 +- .../daemon/workers/PhabricatorWorker.php | 22 ++--- .../action/PhabricatorTriggerAction.php | 10 +-- .../workers/clock/PhabricatorTriggerClock.php | 9 +- .../query/PhabricatorWorkerLeaseQuery.php | 6 +- .../query/PhabricatorWorkerTriggerQuery.php | 2 +- .../storage/PhabricatorWorkerTrigger.php | 15 ++-- .../diff/PhabricatorDifferenceEngine.php | 12 +-- .../edges/editor/PhabricatorEdgeEditor.php | 14 +-- .../edges/query/PhabricatorEdgeQuery.php | 24 ++--- src/infrastructure/env/PhabricatorEnv.php | 18 ++-- .../env/PhabricatorScopedEnv.php | 4 +- .../markup/PhabricatorMarkupEngine.php | 32 +++---- .../markup/PhabricatorMarkupInterface.php | 12 +-- .../markup/PhutilMarkupEngine.php | 9 +- .../blockrule/PhutilRemarkupCodeBlockRule.php | 2 +- .../markup/markuprule/PhutilRemarkupRule.php | 15 ++-- src/infrastructure/markup/render.php | 6 +- .../rule/PhabricatorObjectRemarkupRule.php | 2 +- src/infrastructure/parser/PhutilURIHelper.php | 2 +- ...PhabricatorCursorPagedPolicyAwareQuery.php | 44 ++++----- .../policy/PhabricatorPolicyAwareQuery.php | 22 ++--- src/infrastructure/storage/lisk/LiskDAO.php | 76 +++++++++------- .../util/PhabricatorGlobalLock.php | 2 +- src/infrastructure/util/PhabricatorHash.php | 11 +-- .../password/PhabricatorPasswordHasher.php | 18 ++-- src/view/AphrontView.php | 6 +- src/view/control/AphrontTableView.php | 2 +- src/view/form/AphrontFormView.php | 2 +- src/view/form/control/AphrontFormControl.php | 2 +- src/view/page/PhabricatorStandardPageView.php | 2 +- src/view/phui/PHUIBoxView.php | 3 +- src/view/phui/PHUICrumbView.php | 2 +- src/view/phui/PHUICrumbsView.php | 4 +- src/view/phui/PHUIHeaderView.php | 3 +- src/view/phui/PHUIPagerView.php | 2 +- src/view/viewutils.php | 6 +- support/startup/PhabricatorClientLimit.php | 7 +- support/startup/PhabricatorStartup.php | 36 ++++---- support/startup/preamble-utils.php | 2 +- 160 files changed, 879 insertions(+), 793 deletions(-) diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php index 2561e397b6..8ce2f3e4af 100644 --- a/src/aphront/AphrontRequest.php +++ b/src/aphront/AphrontRequest.php @@ -56,8 +56,8 @@ final class AphrontRequest extends Phobject { * Applications like Paste, Diffusion, and Harbormaster use "$12-14" in the * URI to allow users to link to particular lines. * - * @param string URI data key to pull line range information from. - * @param int|null Maximum length of the range. + * @param string $key URI data key to pull line range information from. + * @param int|null $limit Maximum length of the range. * @return null|pair Null, or beginning and end of the range. */ public function getURILineRange($key, $limit) { @@ -537,8 +537,8 @@ final class AphrontRequest extends Phobject { * * To set a temporary cookie, see @{method:setTemporaryCookie}. * - * @param string Cookie name. - * @param string Cookie value. + * @param string $name Cookie name. + * @param string $value Cookie value. * @return this * @task cookie */ @@ -553,8 +553,8 @@ final class AphrontRequest extends Phobject { * * To set a durable cookie, see @{method:setCookie}. * - * @param string Cookie name. - * @param string Cookie value. + * @param string $name Cookie name. + * @param string $value Cookie value. * @return this * @task cookie */ @@ -566,9 +566,9 @@ final class AphrontRequest extends Phobject { /** * Set a cookie with a given expiration policy. * - * @param string Cookie name. - * @param string Cookie value. - * @param int Epoch timestamp for cookie expiration. + * @param string $name Cookie name. + * @param string $value Cookie value. + * @param int $expire Epoch timestamp for cookie expiration. * @return this * @task cookie */ @@ -748,7 +748,7 @@ final class AphrontRequest extends Phobject { * into a list of key-value pairs suitable for submitting via HTTP request * (with arrays flattened). * - * @param dict Data to flatten. + * @param dict $data Data to flatten. * @return dict Flat data suitable for inclusion in an HTTP * request. */ @@ -778,9 +778,11 @@ final class AphrontRequest extends Phobject { * and looks up the appropriate value in `$_SERVER` (in this case, * `"HTTP_ACCEPT_ENCODING"`). * - * @param string Canonical header name, like `"Accept-Encoding"`. - * @param wild Default value to return if header is not present. - * @param array? Read this instead of `$_SERVER`. + * @param string $name Canonical header name, like + `"Accept-Encoding"`. + * @param wild? $default Default value to return if header is not + present. + * @param array? $data Read this instead of `$_SERVER`. * @return string|wild Header value if present, or `$default` if not. */ public static function getHTTPHeader($name, $default = null, $data = null) { @@ -844,7 +846,7 @@ final class AphrontRequest extends Phobject { * This is not a general-purpose proxying method; it is a specialized * method with niche applications and severe security implications. * - * @param string URI identifying the host we are proxying the request to. + * @param string URI $uri identifying the host we are proxying the request to. * @return HTTPSFuture New proxy future. * * @phutil-external-symbol class PhabricatorStartup diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php index 3198bb9fd4..af05993e2b 100644 --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -510,8 +510,8 @@ final class AphrontApplicationConfiguration * Map a specific path to the corresponding controller. For a description * of routing, see @{method:buildController}. * - * @param list List of routing maps. - * @param string Path to route. + * @param list $maps List of routing maps. + * @param string $path Path to route. * @return pair Controller and dictionary of request * parameters. * @task routing @@ -562,7 +562,7 @@ final class AphrontApplicationConfiguration /** * Tests if a response is of a valid type. * - * @param wild Supposedly valid response. + * @param wild $response Supposedly valid response. * @return bool True if the object is of a valid type. * @task response */ @@ -583,8 +583,9 @@ final class AphrontApplicationConfiguration * Verifies that the return value from an @{class:AphrontController} is * of an allowed type. * - * @param AphrontController Controller which returned the response. - * @param wild Supposedly valid response. + * @param AphrontController $controller Controller which returned the + * response. + * @param wild $response Supposedly valid response. * @return void * @task response */ @@ -612,9 +613,9 @@ final class AphrontApplicationConfiguration * Verifies that the return value from an * @{class:AphrontResponseProducerInterface} is of an allowed type. * - * @param AphrontResponseProducerInterface Object which produced + * @param AphrontResponseProducerInterface $producer Object which produced * this response. - * @param wild Supposedly valid response. + * @param wild $response Supposedly valid response. * @return void * @task response */ @@ -642,9 +643,9 @@ final class AphrontApplicationConfiguration * Verifies that the return value from an * @{class:AphrontRequestExceptionHandler} is of an allowed type. * - * @param AphrontRequestExceptionHandler Object which produced this + * @param AphrontRequestExceptionHandler $handler Object which produced this * response. - * @param wild Supposedly valid response. + * @param wild $response Supposedly valid response. * @return void * @task response */ @@ -678,9 +679,9 @@ final class AphrontApplicationConfiguration * If a controller returns a response producer, invoke it now and produce * the real response. * - * @param AphrontRequest Request being handled. - * @param AphrontResponse|AphrontResponseProducerInterface Response, or - * response producer. + * @param AphrontRequest $request Request being handled. + * @param AphrontResponse|AphrontResponseProducerInterface $response + * Response, or response producer. * @return AphrontResponse Response after any required production. * @task response */ @@ -738,7 +739,7 @@ final class AphrontApplicationConfiguration * This method delegates exception handling to available subclasses of * @{class:AphrontRequestExceptionHandler}. * - * @param Throwable Exception which needs to be handled. + * @param Throwable $throwable Exception which needs to be handled. * @return wild Response or response producer, or null if no available * handler can produce a response. * @task exception diff --git a/src/aphront/httpparametertype/AphrontHTTPParameterType.php b/src/aphront/httpparametertype/AphrontHTTPParameterType.php index 78a62a663c..a31101a9fc 100644 --- a/src/aphront/httpparametertype/AphrontHTTPParameterType.php +++ b/src/aphront/httpparametertype/AphrontHTTPParameterType.php @@ -31,7 +31,7 @@ abstract class AphrontHTTPParameterType extends Phobject { * example, a type might lookup usernames or project names. These types need * to use the current viewer to execute queries. * - * @param PhabricatorUser Current viewer. + * @param PhabricatorUser $viewer Current viewer. * @return this * @task read */ @@ -58,8 +58,8 @@ abstract class AphrontHTTPParameterType extends Phobject { /** * Test if a value is present in a request. * - * @param AphrontRequest The incoming request. - * @param string The key to examine. + * @param AphrontRequest $request The incoming request. + * @param string $key The key to examine. * @return bool True if a readable value is present in the request. * @task read */ @@ -74,8 +74,8 @@ abstract class AphrontHTTPParameterType extends Phobject { * If the value is not present, a default value is returned (usually `null`). * Use @{method:getExists} to test if a value is present. * - * @param AphrontRequest The incoming request. - * @param string The key to examine. + * @param AphrontRequest $request The incoming request. + * @param string $key The key to examine. * @return wild Value, or default if value is not present. * @task read */ @@ -165,9 +165,9 @@ abstract class AphrontHTTPParameterType extends Phobject { * existence check that a simpler "list of strings" type has, and can just * call the simpler type to reuse its behavior. * - * @param AphrontHTTPParameterType The other type. - * @param AphrontRequest Incoming request. - * @param string Key to examine. + * @param AphrontHTTPParameterType $type The other type. + * @param AphrontRequest $request Incoming request. + * @param string $key Key to examine. * @return bool True if the parameter exists. * @task util */ @@ -189,9 +189,9 @@ abstract class AphrontHTTPParameterType extends Phobject { * type. For example, a "list of users" type may start by running the same * basic parsing that a simpler "list of strings" type does. * - * @param AphrontHTTPParameterType The other type. - * @param AphrontRequest Incoming request. - * @param string Key to examine. + * @param AphrontHTTPParameterType $type The other type. + * @param AphrontRequest $request Incoming request. + * @param string $key Key to examine. * @return wild Parsed value. * @task util */ @@ -233,8 +233,8 @@ abstract class AphrontHTTPParameterType extends Phobject { * To call another type's behavior in order to perform this check, use * @{method:getExistsWithType}. * - * @param AphrontRequest The incoming request. - * @param string The key to examine. + * @param AphrontRequest $request The incoming request. + * @param string $key The key to examine. * @return bool True if a readable value is present in the request. * @task impl */ @@ -253,8 +253,8 @@ abstract class AphrontHTTPParameterType extends Phobject { * To call another type's behavior in order to parse a value, use * @{method:getValueWithType}. * - * @param AphrontRequest The incoming request. - * @param string The key to examine. + * @param AphrontRequest $request The incoming request. + * @param string $key The key to examine. * @return wild Parsed value. * @task impl */ diff --git a/src/aphront/response/AphrontFileResponse.php b/src/aphront/response/AphrontFileResponse.php index 6508ad6020..522b111a04 100644 --- a/src/aphront/response/AphrontFileResponse.php +++ b/src/aphront/response/AphrontFileResponse.php @@ -30,7 +30,7 @@ final class AphrontFileResponse extends AphrontResponse { /** * Set a download filename * - * @param $download string + * @param string $download * @return self */ public function setDownload($download) { diff --git a/src/aphront/response/AphrontRedirectResponse.php b/src/aphront/response/AphrontRedirectResponse.php index 390ad193c9..5494d3e0e3 100644 --- a/src/aphront/response/AphrontRedirectResponse.php +++ b/src/aphront/response/AphrontRedirectResponse.php @@ -117,8 +117,9 @@ class AphrontRedirectResponse extends AphrontResponse { * ambiguity. For example, Chrome interprets "Location: /\evil.com" to mean * "perform a protocol-relative redirect to evil.com". * - * @param string URI to redirect to. - * @param bool True if this URI identifies a remote resource. + * @param string $uri URI to redirect to. + * @param bool $is_external True if this URI identifies a remote + * resource. * @return string URI for use in a "Location:" header. */ public static function getURIForRedirect($uri, $is_external) { diff --git a/src/aphront/sink/AphrontHTTPSink.php b/src/aphront/sink/AphrontHTTPSink.php index 9e43e4a687..f2a2f50860 100644 --- a/src/aphront/sink/AphrontHTTPSink.php +++ b/src/aphront/sink/AphrontHTTPSink.php @@ -28,7 +28,8 @@ abstract class AphrontHTTPSink extends Phobject { /** * Write an HTTP status code to the output. * - * @param int Numeric HTTP status code. + * @param int $code Numeric HTTP status code. + * @param string? $message * @return void */ final public function writeHTTPStatus($code, $message = '') { @@ -44,7 +45,7 @@ abstract class AphrontHTTPSink extends Phobject { /** * Write HTTP headers to the output. * - * @param list List of pairs. + * @param list $headers List of pairs. * @return void */ final public function writeHeaders(array $headers) { @@ -89,7 +90,7 @@ abstract class AphrontHTTPSink extends Phobject { /** * Write HTTP body data to the output. * - * @param string Body data. + * @param string $data Body data. * @return void */ final public function writeData($data) { @@ -100,7 +101,7 @@ abstract class AphrontHTTPSink extends Phobject { /** * Write an entire @{class:AphrontResponse} to the output. * - * @param AphrontResponse The response object to write. + * @param AphrontResponse $response The response object to write. * @return void */ final public function writeResponse(AphrontResponse $response) { diff --git a/src/aphront/site/AphrontRoutingMap.php b/src/aphront/site/AphrontRoutingMap.php index bda98429f9..687aae93bb 100644 --- a/src/aphront/site/AphrontRoutingMap.php +++ b/src/aphront/site/AphrontRoutingMap.php @@ -50,7 +50,7 @@ final class AphrontRoutingMap extends Phobject { /** * Find the route matching a path, if one exists. * - * @param string Path to route. + * @param string $path Path to route. * @return AphrontRoutingResult|null Routing result, if path matches map. * @task routing */ @@ -84,9 +84,9 @@ final class AphrontRoutingMap extends Phobject { /** * Test a sub-map to see if any routes match a path. * - * @param string Path to route. - * @param string Pattern from the map. - * @param string Value from the map. + * @param string $route Pattern from the map. + * @param string $value Value from the map. + * @param string $path Path to route. * @return dict|null Match details, if path matches sub-map. * @task routing */ diff --git a/src/aphront/writeguard/AphrontWriteGuard.php b/src/aphront/writeguard/AphrontWriteGuard.php index 589a0db37b..6a3d053cf1 100644 --- a/src/aphront/writeguard/AphrontWriteGuard.php +++ b/src/aphront/writeguard/AphrontWriteGuard.php @@ -57,7 +57,7 @@ final class AphrontWriteGuard extends Phobject { * the request, or abort the request (e.g., by throwing an exception) if a * valid token isn't present. * - * @param callable CSRF callback. + * @param $callback Callable CSRF callback. * @return this * @task manage */ diff --git a/src/applications/auth/constants/PhabricatorCommonPasswords.php b/src/applications/auth/constants/PhabricatorCommonPasswords.php index 1313257553..e74a255b09 100644 --- a/src/applications/auth/constants/PhabricatorCommonPasswords.php +++ b/src/applications/auth/constants/PhabricatorCommonPasswords.php @@ -16,7 +16,7 @@ final class PhabricatorCommonPasswords extends Phobject { /** * Check if a password is extremely common. * - * @param string Password to test. + * @param string $password Password to test. * @return bool True if the password is pathologically weak. * * @task common diff --git a/src/applications/auth/constants/PhabricatorCookies.php b/src/applications/auth/constants/PhabricatorCookies.php index 9dc9d823b2..607240c97b 100644 --- a/src/applications/auth/constants/PhabricatorCookies.php +++ b/src/applications/auth/constants/PhabricatorCookies.php @@ -77,7 +77,7 @@ final class PhabricatorCookies extends Phobject { * Set the client ID cookie. This is a random cookie used like a CSRF value * during authentication workflows. * - * @param AphrontRequest Request to modify. + * @param AphrontRequest $request Request to modify. * @return void * @task clientid */ @@ -105,10 +105,10 @@ final class PhabricatorCookies extends Phobject { * written, to avoid writing over a real URI with a bunch of "humans.txt" * stuff. See T3793 for discussion. * - * @param AphrontRequest Request to write to. - * @param string URI to write. - * @param bool Write this cookie even if we have a fresh - * cookie already. + * @param AphrontRequest $request Request to write to. + * @param string $next_uri URI to write. + * @param bool? $force Write this cookie even if we have a + * fresh cookie already. * @return void * * @task next @@ -139,7 +139,7 @@ final class PhabricatorCookies extends Phobject { /** * Read the URI out of the Next URI cookie. * - * @param AphrontRequest Request to examine. + * @param AphrontRequest $request Request to examine. * @return string|null Next URI cookie's URI value. * * @task next @@ -155,7 +155,7 @@ final class PhabricatorCookies extends Phobject { /** * Parse a Next URI cookie into its components. * - * @param string Raw cookie value. + * @param string $cookie Raw cookie value. * @return list List of timestamp and URI. * * @task next diff --git a/src/applications/auth/controller/PhabricatorAuthController.php b/src/applications/auth/controller/PhabricatorAuthController.php index 8bba90a1e6..bb95c2ec72 100644 --- a/src/applications/auth/controller/PhabricatorAuthController.php +++ b/src/applications/auth/controller/PhabricatorAuthController.php @@ -44,8 +44,9 @@ abstract class PhabricatorAuthController extends PhabricatorController { * the user's cookies are set. However, event listeners can intercept this * event and do something else if they prefer. * - * @param PhabricatorUser User to log the viewer in as. - * @param bool True to issue a full session immediately, bypassing MFA. + * @param PhabricatorUser $user User to log the viewer in as. + * @param bool? $force_full_session True to issue a full session immediately, + * bypassing MFA. * @return AphrontResponse Response which continues the login process. */ protected function loginUser( diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php index 251c8284ef..30d85b6fac 100644 --- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -75,7 +75,7 @@ final class PhabricatorAuthSessionEngine extends Phobject { * Get the session kind (e.g., anonymous, user, external account) from a * session token. Returns a `KIND_` constant. * - * @param string Session token. + * @param string $session_token Session token. * @return const Session kind constant. */ public static function getSessionKindFromToken($session_token) { @@ -109,8 +109,8 @@ final class PhabricatorAuthSessionEngine extends Phobject { * loading context. This prevents use of a Conduit sesssion as a Web * session, for example. * - * @param const The type of session to load. - * @param string The session token. + * @param const $session_type The type of session to load. + * @param string $session_token The session token. * @return PhabricatorUser|null * @task use */ @@ -250,11 +250,12 @@ final class PhabricatorAuthSessionEngine extends Phobject { * You can configure the maximum number of concurrent sessions for various * session types in the Phabricator configuration. * - * @param const Session type constant (see + * @param const $session_type Session type constant (see * @{class:PhabricatorAuthSession}). - * @param phid|null Identity to establish a session for, usually a user - * PHID. With `null`, generates an anonymous session. - * @param bool True to issue a partial session. + * @param phid|null $identity_phid Identity to establish a session for, + * usually a user PHID. With `null`, generates an + * anonymous session. + * @param bool $partial True to issue a partial session. * @return string Newly generated session key. */ public function establishSession($session_type, $identity_phid, $partial) { @@ -325,9 +326,9 @@ final class PhabricatorAuthSessionEngine extends Phobject { * This is used when users change passwords, linked accounts, or add * multifactor authentication. * - * @param PhabricatorUser User whose sessions should be terminated. - * @param string|null Optionally, one session to keep. Normally, the current - * login session. + * @param PhabricatorUser $user User whose sessions should be terminated. + * @param string|null? $except_session Optionally, one session to keep. + * Normally, the current login session. * * @return void */ @@ -388,9 +389,11 @@ final class PhabricatorAuthSessionEngine extends Phobject { * does not upgrade the user's session as a side effect. This method is * appropriate for one-time checks. * - * @param PhabricatorUser User whose session needs to be in high security. - * @param AphrontRequest Current request. - * @param string URI to return the user to if they cancel. + * @param PhabricatorUser $viewer User whose session needs to be in high + * security. + * @param AphrontRequest $request Current request. + * @param string $cancel_uri URI to return the user to if they + * cancel. * @return PhabricatorAuthHighSecurityToken Security token. * @task hisec */ @@ -420,12 +423,14 @@ final class PhabricatorAuthSessionEngine extends Phobject { * take multiple high security actions. To perform a one-time check instead, * use @{method:requireHighSecurityToken}. * - * @param PhabricatorUser User whose session needs to be in high security. - * @param AphrontRequest Current request. - * @param string URI to return the user to if they cancel. - * @param bool True to jump partial sessions directly into high - * security instead of just upgrading them to full - * sessions. + * @param PhabricatorUser $viewer User whose session needs to be in high + * security. + * @param AphrontRequest $request Current request. + * @param string $cancel_uri URI to return the user to if they + * cancel. + * @param bool? $jump_into_hisec True to jump partial sessions + * directly into high security instead of just + * upgrading them to full sessions. * @return PhabricatorAuthHighSecurityToken Security token. * @task hisec */ @@ -736,8 +741,8 @@ final class PhabricatorAuthSessionEngine extends Phobject { /** * Issue a high security token for a session, if authorized. * - * @param PhabricatorAuthSession Session to issue a token for. - * @param bool Force token issue. + * @param PhabricatorAuthSession $session Session to issue a token for. + * @param bool? $force Force token issue. * @return PhabricatorAuthHighSecurityToken|null Token, if authorized. * @task hisec */ @@ -756,8 +761,10 @@ final class PhabricatorAuthSessionEngine extends Phobject { /** * Render a form for providing relevant multi-factor credentials. * - * @param PhabricatorUser Viewing user. - * @param AphrontRequest Current request. + * @param array $factors + * @param array $validation_results + * @param PhabricatorUser $viewer Viewing user. + * @param AphrontRequest $request Current request. * @return AphrontFormView Renderable form. * @task hisec */ @@ -810,8 +817,9 @@ final class PhabricatorAuthSessionEngine extends Phobject { * * Kicks a session out of high security and logs the exit. * - * @param PhabricatorUser Acting user. - * @param PhabricatorAuthSession Session to return to normal security. + * @param PhabricatorUser $viewer Acting user. + * @param PhabricatorAuthSession $session Session to return to normal + * security. * @return void * @task hisec */ @@ -843,7 +851,7 @@ final class PhabricatorAuthSessionEngine extends Phobject { /** * Upgrade a partial session to a full session. * - * @param PhabricatorAuthSession Session to upgrade. + * @param PhabricatorUser $viewer Viewer whose session should upgrade. * @return void * @task partial */ @@ -885,8 +893,8 @@ final class PhabricatorAuthSessionEngine extends Phobject { /** * Upgrade a session to have all legalpad documents signed. * - * @param PhabricatorUser User whose session should upgrade. - * @param array LegalpadDocument objects + * @param PhabricatorUser $viewer User whose session should upgrade. + * @param array $docs LegalpadDocument objects * @return void * @task partial */ @@ -934,13 +942,14 @@ final class PhabricatorAuthSessionEngine extends Phobject { * These URIs are used for password recovery and to regain access to accounts * which users have been locked out of. * - * @param PhabricatorUser User to generate a URI for. - * @param PhabricatorUserEmail Optionally, email to verify when + * @param PhabricatorUser $user User to generate a URI for. + * @param PhabricatorUserEmail? $email Optionally, email to verify when * link is used. - * @param string Optional context string for the URI. This is purely cosmetic - * and used only to customize workflow and error messages. - * @param bool True to generate a URI which forces an immediate upgrade to - * a full session, bypassing MFA and other login checks. + * @param string? $type Optional context string for the URI. This is purely + * cosmetic and used only to customize workflow and error messages. + * @param bool? $force_full_session True to generate a URI which forces an + * immediate upgrade to a full session, bypassing MFA and other login + * checks. * @return string Login URI. * @task onetime */ @@ -984,10 +993,10 @@ final class PhabricatorAuthSessionEngine extends Phobject { /** * Load the temporary token associated with a given one-time login key. * - * @param PhabricatorUser User to load the token for. - * @param PhabricatorUserEmail Optionally, email to verify when + * @param PhabricatorUser $user User to load the token for. + * @param PhabricatorUserEmail? $email Optionally, email to verify when * link is used. - * @param string Key user is presenting as a valid one-time login key. + * @param string? $key Key user is presenting as a valid one-time login key. * @return PhabricatorAuthTemporaryToken|null Token, if one exists. * @task onetime */ @@ -1012,10 +1021,10 @@ final class PhabricatorAuthSessionEngine extends Phobject { /** * Hash a one-time login key for storage as a temporary token. * - * @param PhabricatorUser User this key is for. - * @param PhabricatorUserEmail Optionally, email to verify when + * @param PhabricatorUser $user User this key is for. + * @param PhabricatorUserEmail? $email Optionally, email to verify when * link is used. - * @param string The one time login key. + * @param string? $key The one time login key. * @return string Hash of the key. * task onetime */ diff --git a/src/applications/auth/password/PhabricatorAuthPasswordHashInterface.php b/src/applications/auth/password/PhabricatorAuthPasswordHashInterface.php index af20db3ed4..f5317bef6f 100644 --- a/src/applications/auth/password/PhabricatorAuthPasswordHashInterface.php +++ b/src/applications/auth/password/PhabricatorAuthPasswordHashInterface.php @@ -14,8 +14,8 @@ interface PhabricatorAuthPasswordHashInterface { * as their password or picking other passwords which are trivially similar * to an account or object identifier. * - * @param PhabricatorUser The user selecting the password. - * @param PhabricatorAuthPasswordEngine The password engine updating a + * @param PhabricatorUser $viewer The user selecting the password. + * @param PhabricatorAuthPasswordEngine $engine The password engine updating a * password. * @return list Blocklist of nonsecret identifiers which the password * should not be similar to. diff --git a/src/applications/auth/provider/PhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorAuthProvider.php index 735dd42b4b..4b90daa2aa 100644 --- a/src/applications/auth/provider/PhabricatorAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorAuthProvider.php @@ -460,9 +460,9 @@ abstract class PhabricatorAuthProvider extends Phobject { * - `uri`: URI the button should take the user to when clicked. * - `method`: Optional HTTP method the button should use, defaults to GET. * - * @param AphrontRequest HTTP request. - * @param string Request mode string. - * @param map Additional parameters, see above. + * @param AphrontRequest $request HTTP request. + * @param string $mode Request mode string. + * @param map? $attributes Additional parameters, see above. * @return wild Log in button. */ protected function renderStandardLoginButton( diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 67294db263..4433306e78 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -141,7 +141,7 @@ abstract class PhabricatorApplication * * Users who have not yet set preferences see a default list of applications. * - * @param PhabricatorUser User viewing the pinned application list. + * @param PhabricatorUser $viewer User viewing the pinned application list. * @return bool True if this application should be pinned by default. */ public function isPinnedByDefault(PhabricatorUser $viewer) { @@ -330,9 +330,9 @@ abstract class PhabricatorApplication /** * Build items for the main menu. * - * @param PhabricatorUser The viewing user. - * @param AphrontController The current controller. May be null for special - * pages like 404, exception handlers, etc. + * @param PhabricatorUser $user The viewing user. + * @param AphrontController? $controller The current controller. May be null + * for special pages like 404, exception handlers, etc. * @return list List of menu items. * @task ui */ @@ -409,7 +409,7 @@ abstract class PhabricatorApplication * To check if an application is installed //and// available to a particular * viewer, user @{method:isClassInstalledForViewer}. * - * @param string Application class name. + * @param string $class Application class name. * @return bool True if the class is installed. * @task meta */ @@ -425,8 +425,8 @@ abstract class PhabricatorApplication * To check if an application is installed at all, use * @{method:isClassInstalled}. * - * @param string Application class name. - * @param PhabricatorUser Viewing user. + * @param string $class Application class name. + * @param PhabricatorUser $viewer Viewing user. * @return bool True if the class is installed for the viewer. * @task meta */ diff --git a/src/applications/cache/PhabricatorCaches.php b/src/applications/cache/PhabricatorCaches.php index 9c22bde672..8e45741fe6 100644 --- a/src/applications/cache/PhabricatorCaches.php +++ b/src/applications/cache/PhabricatorCaches.php @@ -416,7 +416,7 @@ final class PhabricatorCaches extends Phobject { * * Data can later be inflated with @{method:inflateData}. * - * @param string String to attempt to deflate. + * @param string $value String to attempt to deflate. * @return string|null Deflated string, or null if it was not deflated. * @task compress */ @@ -447,7 +447,7 @@ final class PhabricatorCaches extends Phobject { /** * Inflate data previously deflated by @{method:maybeDeflateData}. * - * @param string Deflated data, from @{method:maybeDeflateData}. + * @param string $value Deflated data, from @{method:maybeDeflateData}. * @return string Original, uncompressed data. * @task compress */ diff --git a/src/applications/celerity/CelerityResourceMap.php b/src/applications/celerity/CelerityResourceMap.php index e53b656aaf..58036d9016 100644 --- a/src/applications/celerity/CelerityResourceMap.php +++ b/src/applications/celerity/CelerityResourceMap.php @@ -160,7 +160,7 @@ final class CelerityResourceMap extends Phobject { /** * Get the epoch timestamp of the last modification time of a symbol. * - * @param string Resource symbol to lookup. + * @param string $name Resource symbol to lookup. * @return int Epoch timestamp of last resource modification. */ public function getModifiedTimeForName($name) { @@ -186,7 +186,7 @@ final class CelerityResourceMap extends Phobject { * Return the absolute URI for the resource associated with a symbol. This * method is fairly low-level and ignores packaging. * - * @param string Resource symbol to lookup. + * @param string $symbol Resource symbol to lookup. * @return string|null Resource URI, or null if the symbol is unknown. */ public function getURIForSymbol($symbol) { @@ -199,7 +199,7 @@ final class CelerityResourceMap extends Phobject { * Return the absolute URI for the resource associated with a resource name. * This method is fairly low-level and ignores packaging. * - * @param string Resource name to lookup. + * @param string $name Resource name to lookup. * @return string|null Resource URI, or null if the name is unknown. */ public function getURIForName($name) { @@ -217,7 +217,7 @@ final class CelerityResourceMap extends Phobject { * Return the absolute URI for a resource, identified by hash. * This method is fairly low-level and ignores packaging. * - * @param string Resource hash to lookup. + * @param string $hash Resource hash to lookup. * @return string|null Resource URI, or null if the hash is unknown. */ private function getURIForHash($hash) { @@ -231,7 +231,7 @@ final class CelerityResourceMap extends Phobject { /** * Return the resource symbols required by a named resource. * - * @param string Resource name to lookup. + * @param string $name Resource name to lookup. * @return list|null List of required symbols, or null if the name * is unknown. */ @@ -247,7 +247,7 @@ final class CelerityResourceMap extends Phobject { /** * Return the resource name for a given symbol. * - * @param string Resource symbol to lookup. + * @param string $symbol Resource symbol to lookup. * @return string|null Resource name, or null if the symbol is unknown. */ public function getResourceNameForSymbol($symbol) { diff --git a/src/applications/celerity/CelerityResourceMapGenerator.php b/src/applications/celerity/CelerityResourceMapGenerator.php index 9280c9ecc2..d54aa572ed 100644 --- a/src/applications/celerity/CelerityResourceMapGenerator.php +++ b/src/applications/celerity/CelerityResourceMapGenerator.php @@ -146,7 +146,8 @@ EOFILE; * Find binary resources (like PNG and SWF) and return information about * them. * - * @param CelerityPhysicalResources Resource map to find binary resources for. + * @param CelerityPhysicalResources $resources Resource map to find binary + * resources for. * @return map> Resource information map. */ private function rebuildBinaryResources( @@ -170,8 +171,10 @@ EOFILE; /** * Find text resources (like JS and CSS) and return information about them. * - * @param CelerityPhysicalResources Resource map to find text resources for. - * @param CelerityResourceTransformer Configured resource transformer. + * @param CelerityPhysicalResources $resources Resource map to find text + * resources for. + * @param CelerityResourceTransformer $xformer Configured resource + * transformer. * @return map> Resource information map. */ private function rebuildTextResources( @@ -211,8 +214,8 @@ EOFILE; * Parse the `@provides` and `@requires` symbols out of a text resource, like * JS or CSS. * - * @param string Resource name. - * @param string Resource data. + * @param string $name Resource name. + * @param string $data Resource data. * @return pair|null> The `@provides` symbol and * the list of `@requires` symbols. If the resource is not part of the * dependency graph, both are null. @@ -254,8 +257,8 @@ EOFILE; * Check for dependency cycles in the resource graph. Raises an exception if * a cycle is detected. * - * @param map> Map of `@provides` symbols to their - * `@requires` symbols. + * @param map> $nodes Map of `@provides` symbols to + * their `@requires` symbols. * @return void */ private function detectGraphCycles(array $nodes) { @@ -278,9 +281,9 @@ EOFILE; /** * Build package specifications for a given resource source. * - * @param CelerityPhysicalResources Resource source to rebuild. - * @param map Map of `@provides` to hashes. - * @param map Map of hashes to resource names. + * @param CelerityPhysicalResources $resources Resource source to rebuild. + * @param map $symbol_map Map of `@provides` to hashes. + * @param map $reverse_map Map of hashes to resource names. * @return map> Package information maps. */ private function rebuildPackages( diff --git a/src/applications/celerity/CelerityResourceTransformer.php b/src/applications/celerity/CelerityResourceTransformer.php index 6d86a8806a..8c49a83df7 100644 --- a/src/applications/celerity/CelerityResourceTransformer.php +++ b/src/applications/celerity/CelerityResourceTransformer.php @@ -228,7 +228,8 @@ final class CelerityResourceTransformer extends Phobject { * small enough. Otherwise, this method will return `null` and we'll end up * using a normal URI instead. * - * @param string Resource name to attempt to generate a data URI for. + * @param string $resource_name Resource name to attempt to generate a data + * URI for. * @return string|null Data URI, or null if we declined to generate one. */ private function generateDataURI($resource_name) { diff --git a/src/applications/celerity/api.php b/src/applications/celerity/api.php index 4340b57df0..98802b4598 100644 --- a/src/applications/celerity/api.php +++ b/src/applications/celerity/api.php @@ -9,8 +9,9 @@ * * For more information, see @{article:Adding New CSS and JS}. * - * @param string Name of the celerity module to include. This is whatever you - * annotated as "@provides" in the file. + * @param string $symbol Name of the celerity module to include. This is + * whatever you annotated as "@provides" in the file. + * @param string? $source_name * @return void */ function require_celerity_resource($symbol, $source_name = 'phabricator') { @@ -40,7 +41,8 @@ function celerity_generate_unique_node_id() { /** * Get the versioned URI for a raw resource, like an image. * - * @param string Path to the raw image. + * @param string $resource Path to the raw image. + * @param string? $source * @return string Versioned path to the image, if one is available. */ function celerity_get_resource_uri($resource, $source = 'phabricator') { diff --git a/src/applications/celerity/controller/CelerityResourceController.php b/src/applications/celerity/controller/CelerityResourceController.php index 1908c4583a..26d8d05701 100644 --- a/src/applications/celerity/controller/CelerityResourceController.php +++ b/src/applications/celerity/controller/CelerityResourceController.php @@ -194,7 +194,7 @@ abstract class CelerityResourceController extends PhabricatorController { * are cached, while other types of resources (which are large, and cheap * to process) are not. * - * @param string Resource type. + * @param string $type Resource type. * @return bool True to enable caching. */ private function isLocallyCacheableResourceType($type) { diff --git a/src/applications/celerity/management/CelerityManagementMapWorkflow.php b/src/applications/celerity/management/CelerityManagementMapWorkflow.php index e838de58f4..22fdebd686 100644 --- a/src/applications/celerity/management/CelerityManagementMapWorkflow.php +++ b/src/applications/celerity/management/CelerityManagementMapWorkflow.php @@ -32,7 +32,7 @@ final class CelerityManagementMapWorkflow /** * Rebuild the resource map for a resource source. * - * @param CelerityPhysicalResources Resource source to rebuild. + * @param $resources CelerityPhysicalResources Resource source to rebuild. * @return void */ private function rebuildResources(CelerityPhysicalResources $resources) { diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php index 994f282f44..8eb19a2dc9 100644 --- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php @@ -159,8 +159,9 @@ final class PhabricatorConduitAPIController /** * Authenticate the client making the request to a Phabricator user account. * - * @param ConduitAPIRequest Request being executed. - * @param dict Request metadata. + * @param ConduitAPIRequest $api_request Request being executed. + * @param dict $metadata Request metadata. + * @param wild $method * @return null|pair Null to indicate successful authentication, or * an error code and error message pair. */ diff --git a/src/applications/conduit/protocol/exception/ConduitException.php b/src/applications/conduit/protocol/exception/ConduitException.php index c30931de04..d604de65ef 100644 --- a/src/applications/conduit/protocol/exception/ConduitException.php +++ b/src/applications/conduit/protocol/exception/ConduitException.php @@ -12,7 +12,7 @@ class ConduitException extends Exception { * will be used instead. This is useful to provide specific information about * an exception (e.g., which values were wrong in an invalid request). * - * @param string Detailed error description. + * @param string $error_description Detailed error description. * @return this */ final public function setErrorDescription($error_description) { diff --git a/src/applications/config/custom/PhabricatorCustomLogoConfigType.php b/src/applications/config/custom/PhabricatorCustomLogoConfigType.php index 6811f618cf..6443864bc9 100644 --- a/src/applications/config/custom/PhabricatorCustomLogoConfigType.php +++ b/src/applications/config/custom/PhabricatorCustomLogoConfigType.php @@ -15,7 +15,7 @@ final class PhabricatorCustomLogoConfigType /** * Return the full URI of the Phorge logo - * @param PhabricatorUser Current viewer + * @param PhabricatorUser $viewer Current viewer * @return string Full URI of the Phorge logo */ public static function getLogoURI(PhabricatorUser $viewer) { diff --git a/src/applications/config/issue/PhabricatorSetupIssue.php b/src/applications/config/issue/PhabricatorSetupIssue.php index cadedfc7da..3b6503113d 100644 --- a/src/applications/config/issue/PhabricatorSetupIssue.php +++ b/src/applications/config/issue/PhabricatorSetupIssue.php @@ -145,8 +145,8 @@ final class PhabricatorSetupIssue extends Phobject { * we alter `memory_limit` during startup, so if the original value is not * provided it will look like it is always set to `-1`. * - * @param string PHP configuration option to provide a value for. - * @param string Explicit value to show in the UI. + * @param string $php_config PHP configuration option to provide a value for. + * @param string $value Explicit value to show in the UI. * @return this */ public function addPHPConfigOriginalValue($php_config, $value) { diff --git a/src/applications/config/json/PhabricatorConfigJSON.php b/src/applications/config/json/PhabricatorConfigJSON.php index 0433e4a1dc..bbd84fc2bc 100644 --- a/src/applications/config/json/PhabricatorConfigJSON.php +++ b/src/applications/config/json/PhabricatorConfigJSON.php @@ -4,7 +4,8 @@ final class PhabricatorConfigJSON extends Phobject { /** * Properly format a JSON value. * - * @param wild Any value, but should be a raw value, not a string of JSON. + * @param wild $value Any value, but should be a raw value, not a string of + * JSON. * @return string */ public static function prettyPrintJSON($value) { diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 49cd9eb17e..e8aea20ba7 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -65,8 +65,8 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { * or other context. For example, this is used to show workspace IDs when * configuring `asana.workspace-id`. * - * @param PhabricatorConfigOption Option being rendered. - * @param AphrontRequest Active request. + * @param PhabricatorConfigOption $option Option being rendered. + * @param AphrontRequest $request Active request. * @return wild Additional contextual description * information. */ diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php index 3b9c3ad192..655d3be51b 100644 --- a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php +++ b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php @@ -427,7 +427,7 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { * panel. Generally, all of this stuff is ridiculous and we just want to * shut it down. * - * @param PhabricatorDashboardPanel Panel being rendered. + * @param PhabricatorDashboardPanel $panel Panel being rendered. * @return void */ private function detectRenderingCycle(PhabricatorDashboardPanel $panel) { diff --git a/src/applications/differential/constants/DifferentialReviewerStatus.php b/src/applications/differential/constants/DifferentialReviewerStatus.php index 203365efc6..c0b508f464 100644 --- a/src/applications/differential/constants/DifferentialReviewerStatus.php +++ b/src/applications/differential/constants/DifferentialReviewerStatus.php @@ -19,7 +19,7 @@ final class DifferentialReviewerStatus extends Phobject { * will attempt to update you to both "commented" and "accepted". We want * "accepted" to win, because it's the stronger of the two. * - * @param const Reviewer status constant. + * @param const $constant Reviewer status constant. * @return int Relative strength (higher is stronger). */ public static function getStatusStrength($constant) { diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index 5b39269bdd..3548e02258 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -240,12 +240,12 @@ final class DifferentialChangesetParser extends Phobject { * the left and right halves of the displayed diff to be correctly mapped to * storage changesets. * - * @param id The Differential Changeset ID that comments added to the right - * side of the visible diff should be attached to. - * @param bool If true, attach new comments to the right side of the storage - * changeset. Note that this may be false, if the left side of - * some storage changeset is being shown as the right side of - * a display diff. + * @param id $id The Differential Changeset ID that comments added to the + * right side of the visible diff should be attached to. + * @param bool $is_new If true, attach new comments to the right side of the + * storage changeset. Note that this may be false, if the left + * side of some storage changeset is being shown as the right + * side of a display diff. * @return this */ public function setRightSideCommentMapping($id, $is_new) { @@ -295,7 +295,8 @@ final class DifferentialChangesetParser extends Phobject { * By default, there is no render cache key and parsers do not use the cache. * This is appropriate for rarely-viewed changesets. * - * @param string Key for identifying this changeset in the render cache. + * @param string $key Key for identifying this changeset in the render + * cache. * @return this */ public function setRenderCacheKey($key) { @@ -1238,7 +1239,7 @@ final class DifferentialChangesetParser extends Phobject { * taking into consideration which halves of which changesets will actually * be shown. * - * @param PhabricatorInlineComment Comment to test for visibility. + * @param PhabricatorInlineComment $comment Comment to test for visibility. * @return bool True if the comment is visible on the rendered diff. */ private function isCommentVisibleOnRenderedDiff( @@ -1266,7 +1267,7 @@ final class DifferentialChangesetParser extends Phobject { * Note that the comment must appear somewhere on the rendered changeset, as * per isCommentVisibleOnRenderedDiff(). * - * @param PhabricatorInlineComment Comment to test for display + * @param PhabricatorInlineComment $comment Comment to test for display * location. * @return bool True for right, false for left. */ @@ -1298,8 +1299,8 @@ final class DifferentialChangesetParser extends Phobject { * list($start, $end, $mask) = $parsed; * $parser->render($start, $end, $mask); * - * @param string Range specification, indicating the range of the diff that - * should be rendered. + * @param string $spec Range specification, indicating the range of the diff + * that should be rendered. * @return tuple List of suitable for passing to * @{method:render}. */ diff --git a/src/applications/differential/parser/DifferentialLineAdjustmentMap.php b/src/applications/differential/parser/DifferentialLineAdjustmentMap.php index 2b4033c798..6c9bfad98b 100644 --- a/src/applications/differential/parser/DifferentialLineAdjustmentMap.php +++ b/src/applications/differential/parser/DifferentialLineAdjustmentMap.php @@ -58,8 +58,8 @@ final class DifferentialLineAdjustmentMap extends Phobject { /** * Map a line across a change, or a series of changes. * - * @param int Line to map - * @param bool True to map it as the end of a range. + * @param int $line Line to map + * @param bool $is_end True to map it as the end of a range. * @return wild Spooky magic. */ public function mapLine($line, $is_end) { diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index d6b249cac5..87ebafd97f 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -45,7 +45,7 @@ final class DifferentialRevisionQuery /** * Find revisions affecting one or more items in a list of paths. * - * @param list List of file paths. + * @param list $paths List of file paths. * @return this * @task config */ @@ -59,7 +59,7 @@ final class DifferentialRevisionQuery * this function will clear anything set by previous calls to * @{method:withAuthors}. * - * @param array List of PHIDs of authors + * @param array $author_phids List of PHIDs of authors * @return this * @task config */ @@ -72,7 +72,7 @@ final class DifferentialRevisionQuery * Filter results to revisions which CC one of the listed people. Calling this * function will clear anything set by previous calls to @{method:withCCs}. * - * @param array List of PHIDs of subscribers. + * @param array $cc_phids List of PHIDs of subscribers. * @return this * @task config */ @@ -86,7 +86,7 @@ final class DifferentialRevisionQuery * reviewers. Calling this function will clear anything set by previous calls * to @{method:withReviewers}. * - * @param array List of PHIDs of reviewers + * @param array $reviewer_phids List of PHIDs of reviewers * @return this * @task config */ @@ -124,7 +124,7 @@ final class DifferentialRevisionQuery * Calling this function will clear anything set by previous calls to * @{method:withCommitHashes}. * - * @param array List of pairs * @return this @@ -149,7 +149,7 @@ final class DifferentialRevisionQuery /** * Filter results to revisions on given branches. * - * @param list List of branch names. + * @param list $branches List of branch names. * @return this * @task config */ @@ -162,7 +162,7 @@ final class DifferentialRevisionQuery /** * Filter results to only return revisions whose ids are in the given set. * - * @param array List of revision ids + * @param array $ids List of revision ids * @return this * @task config */ @@ -175,7 +175,7 @@ final class DifferentialRevisionQuery /** * Filter results to only return revisions whose PHIDs are in the given set. * - * @param array List of revision PHIDs + * @param array $phids List of revision PHIDs * @return this * @task config */ @@ -189,7 +189,7 @@ final class DifferentialRevisionQuery * Given a set of users, filter results to return only revisions they are * responsible for (i.e., they are either authors or reviewers). * - * @param array List of user PHIDs. + * @param array $responsible_phids List of user PHIDs. * @return this * @task config */ @@ -221,7 +221,7 @@ final class DifferentialRevisionQuery * Set whether or not the query should load the active diff for each * revision. * - * @param bool True to load and attach diffs. + * @param bool $need_active_diffs True to load and attach diffs. * @return this * @task config */ @@ -235,7 +235,7 @@ final class DifferentialRevisionQuery * Set whether or not the query should load the associated commit PHIDs for * each revision. * - * @param bool True to load and attach diffs. + * @param bool $need_commit_phids True to load and attach diffs. * @return this * @task config */ @@ -249,7 +249,7 @@ final class DifferentialRevisionQuery * Set whether or not the query should load associated diff IDs for each * revision. * - * @param bool True to load and attach diff IDs. + * @param bool $need_diff_ids True to load and attach diff IDs. * @return this * @task config */ @@ -263,7 +263,7 @@ final class DifferentialRevisionQuery * Set whether or not the query should load associated commit hashes for each * revision. * - * @param bool True to load and attach commit hashes. + * @param bool $need_hashes True to load and attach commit hashes. * @return this * @task config */ @@ -276,7 +276,7 @@ final class DifferentialRevisionQuery /** * Set whether or not the query should load associated reviewers. * - * @param bool True to load and attach reviewers. + * @param bool $need_reviewers True to load and attach reviewers. * @return this * @task config */ @@ -291,7 +291,7 @@ final class DifferentialRevisionQuery * reviewer. In particular, they have authority to act on behalf of projects * they are a member of. * - * @param bool True to load and attach authority. + * @param bool $need_reviewer_authority True to load and attach authority. * @return this * @task config */ diff --git a/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php b/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php index 7612f9e876..f6832c4fc9 100644 --- a/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php @@ -491,9 +491,10 @@ abstract class DifferentialChangesetHTMLRenderer /** * Build links which users can click to show more context in a changeset. * - * @param int Beginning of the line range to build links for. - * @param int Length of the line range to build links for. - * @param int Total number of lines in the changeset. + * @param int $top Beginning of the line range to build links for. + * @param int $len Length of the line range to build links for. + * @param int $changeset_length Total number of lines in the changeset. + * @param bool? $is_blocks * @return markup Rendered links. */ protected function renderShowContextLinks( @@ -580,9 +581,9 @@ abstract class DifferentialChangesetHTMLRenderer * * See @{method:renderShowContextLinks}. * - * @param bool Does this link show all context when clicked? - * @param string Range specification for lines to show. - * @param string Text of the link. + * @param bool $is_all Does this link show all context when clicked? + * @param string $range Range specification for lines to show. + * @param string $text Text of the link. * @return markup Rendered link. */ private function renderShowContextLink($is_all, $range, $text) { diff --git a/src/applications/differential/render/DifferentialChangesetRenderer.php b/src/applications/differential/render/DifferentialChangesetRenderer.php index 711d7d574b..676a6bcf05 100644 --- a/src/applications/differential/render/DifferentialChangesetRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetRenderer.php @@ -442,8 +442,8 @@ abstract class DifferentialChangesetRenderer extends Phobject { * when a file is not changed. * - `"none"`: Don't show the link (e.g., text not available). * - * @param string Message explaining why the diff is hidden. - * @param string|null Force mode, see above. + * @param string $message Message explaining why the diff is hidden. + * @param string|null $force Force mode, see above. * @return string Shield markup. */ abstract public function renderShield($message, $force = 'default'); diff --git a/src/applications/differential/storage/DifferentialChangeset.php b/src/applications/differential/storage/DifferentialChangeset.php index d92b27e574..7b20c22a34 100644 --- a/src/applications/differential/storage/DifferentialChangeset.php +++ b/src/applications/differential/storage/DifferentialChangeset.php @@ -213,7 +213,7 @@ final class DifferentialChangeset * Test if this changeset and some other changeset put the affected file in * the same state. * - * @param DifferentialChangeset Changeset to compare against. + * @param DifferentialChangeset $other Changeset to compare against. * @return bool True if the two changesets have the same effect. */ public function hasSameEffectAs(DifferentialChangeset $other) { diff --git a/src/applications/diffusion/data/DiffusionBrowseResultSet.php b/src/applications/diffusion/data/DiffusionBrowseResultSet.php index 22d9e08251..f112ab49ed 100644 --- a/src/applications/diffusion/data/DiffusionBrowseResultSet.php +++ b/src/applications/diffusion/data/DiffusionBrowseResultSet.php @@ -123,7 +123,7 @@ final class DiffusionBrowseResultSet extends Phobject { * When a directory contains several README files, this function scores them * so the caller can select a preferred file. See @{method:getReadmePath}. * - * @param string Local README path, like "README.txt". + * @param string $path Local README path, like "README.txt". * @return int Priority score, with higher being more preferred. */ public static function getReadmePriority($path) { diff --git a/src/applications/diffusion/data/DiffusionGitBranch.php b/src/applications/diffusion/data/DiffusionGitBranch.php index 7fe8f5c6c0..75c17b67c1 100644 --- a/src/applications/diffusion/data/DiffusionGitBranch.php +++ b/src/applications/diffusion/data/DiffusionGitBranch.php @@ -19,8 +19,9 @@ final class DiffusionGitBranch extends Phobject { * 'master' => '99a9c082f9a1b68c7264e26b9e552484a5ae5f25', * ); * - * @param string stdout of git branch command. - * @param string Filter branches to those on a specific remote. + * @param string $stdout stdout of git branch command. + * @param string? $only_this_remote Filter branches to those on a specific + * remote. * @return map Map of 'branch' or 'remote/branch' to hash at HEAD. */ public static function parseRemoteBranchOutput( diff --git a/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php b/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php index dc898f81d4..ee70ce217d 100644 --- a/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php @@ -58,7 +58,7 @@ final class DiffusionMercurialCommandEngine * Sanitize output of an `hg` command invoked with the `--debug` flag to make * it usable. * - * @param string Output from `hg --debug ...` + * @param string $stdout Output from `hg --debug ...` * @return string Usable output. */ public static function filterMercurialDebugOutput($stdout) { diff --git a/src/applications/diffusion/query/pathid/DiffusionPathIDQuery.php b/src/applications/diffusion/query/pathid/DiffusionPathIDQuery.php index d994410034..69bb89502a 100644 --- a/src/applications/diffusion/query/pathid/DiffusionPathIDQuery.php +++ b/src/applications/diffusion/query/pathid/DiffusionPathIDQuery.php @@ -42,7 +42,7 @@ final class DiffusionPathIDQuery extends Phobject { /** * Convert a path to the canonical, absolute representation used by Diffusion. * - * @param string Some repository path. + * @param string $path Some repository path. * @return string Canonicalized Diffusion path. * @task pathutil */ @@ -62,7 +62,7 @@ final class DiffusionPathIDQuery extends Phobject { * Return the canonical parent directory for a path. Note, returns "/" when * passed "/". * - * @param string Some repository path. + * @param string $path Some repository path. * @return string That path's canonical parent directory. * @task pathutil */ @@ -80,7 +80,7 @@ final class DiffusionPathIDQuery extends Phobject { * Generate a list of parents for a repository path. The path itself is * included. * - * @param string Some repository path. + * @param string $path Some repository path. * @return list List of canonical paths between the path and the root. * @task pathutil */ diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php index c301b89b28..d9bd30afea 100644 --- a/src/applications/diffusion/request/DiffusionRequest.php +++ b/src/applications/diffusion/request/DiffusionRequest.php @@ -56,7 +56,7 @@ abstract class DiffusionRequest extends Phobject { * - `commit` Optional, commit identifier. * - `line` Optional, line range. * - * @param map See documentation. + * @param map $data See documentation. * @return DiffusionRequest New request object. * @task new */ @@ -133,8 +133,9 @@ abstract class DiffusionRequest extends Phobject { /** * Internal. Use @{method:newFromDictionary}, not this method. * - * @param string Repository identifier. - * @param PhabricatorUser Viewing user. + * @param string $identifier Repository identifier. + * @param PhabricatorUser $viewer Viewing user. + * @param bool? $need_edit * @return DiffusionRequest New request object. * @task new */ @@ -170,7 +171,7 @@ abstract class DiffusionRequest extends Phobject { /** * Internal. Use @{method:newFromDictionary}, not this method. * - * @param PhabricatorRepository Repository object. + * @param PhabricatorRepository $repository Repository object. * @return DiffusionRequest New request object. * @task new */ @@ -201,7 +202,7 @@ abstract class DiffusionRequest extends Phobject { /** * Internal. Use @{method:newFromDictionary}, not this method. * - * @param map Map of parsed data. + * @param map $data Map of parsed data. * @return void * @task new */ @@ -294,7 +295,7 @@ abstract class DiffusionRequest extends Phobject { /** * Modify the request to move the symbolic commit elsewhere. * - * @param string New symbolic commit. + * @param string $symbol New symbolic commit. * @return this */ public function updateSymbolicCommit($symbol) { @@ -464,8 +465,8 @@ abstract class DiffusionRequest extends Phobject { * * Parse the request URI into components. * - * @param string URI blob. - * @param bool True if this VCS supports branches. + * @param string $blob URI blob. + * @param bool $supports_branches True if this VCS supports branches. * @return map Parsed URI. * * @task uri @@ -535,7 +536,7 @@ abstract class DiffusionRequest extends Phobject { /** * Check that the working copy of the repository is present and readable. * - * @param string Path to the working copy. + * @param string $path Path to the working copy. */ protected function validateWorkingCopy($path) { if (!is_readable(dirname($path))) { diff --git a/src/applications/diffusion/view/DiffusionReadmeView.php b/src/applications/diffusion/view/DiffusionReadmeView.php index 2f65f5730f..72b7a94f5b 100644 --- a/src/applications/diffusion/view/DiffusionReadmeView.php +++ b/src/applications/diffusion/view/DiffusionReadmeView.php @@ -26,7 +26,7 @@ final class DiffusionReadmeView extends DiffusionView { /** * Get the markup language a README should be interpreted as. * - * @param string Local README path, like "README.txt". + * @param string $path Local README path, like "README.txt". * @return string Best markup interpreter (like "remarkup") for this file. */ private function getReadmeLanguage($path) { diff --git a/src/applications/diviner/query/DivinerAtomQuery.php b/src/applications/diviner/query/DivinerAtomQuery.php index 1ca7aad984..278b0a4234 100644 --- a/src/applications/diviner/query/DivinerAtomQuery.php +++ b/src/applications/diviner/query/DivinerAtomQuery.php @@ -93,7 +93,7 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { * have been generated incorrectly by accident. In these cases, we can * restore the original data. * - * @param bool + * @param bool $ghosts * @return this */ public function withGhosts($ghosts) { @@ -437,9 +437,10 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { * children. When recursing, also walk up the tree and collect children of * atoms they extend. * - * @param list List of symbols to collect child hashes of. - * @param bool True to collect children of extended atoms, - * as well. + * @param list $symbols List of symbols to collect child + * hashes of. + * @param bool $recurse_up True to collect children of + * extended atoms, as well. * @return map Hashes of atoms' children. */ private function getAllChildHashes(array $symbols, $recurse_up) { @@ -469,9 +470,12 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery { * Attach child atoms to existing atoms. In recursive mode, also attach child * atoms to atoms that these atoms extend. * - * @param list List of symbols to attach children to. - * @param map Map of symbols, keyed by node hash. - * @param bool True to attach children to extended atoms, as well. + * @param list $symbols List of symbols to attach children + * to. + * @param map $children Map of symbols, keyed by + * node hash. + * @param bool $recurse_up True to attach children to extended atoms, as + * well. * @return void */ private function attachAllChildren( diff --git a/src/applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php b/src/applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php index 46ca3f3ac9..ddd45ce802 100644 --- a/src/applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php +++ b/src/applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php @@ -31,7 +31,8 @@ abstract class DoorkeeperFeedStoryPublisher extends Phobject { * JIRA issues which can have several linked revisions), it's generally * more useful not to assume context. * - * @param bool True to assume object context when rendering. + * @param bool $render_with_implied_context True to assume object context + * when rendering. * @return this * @task config */ diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index 27ba6e623f..bfa039d39f 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -63,9 +63,10 @@ abstract class DrydockBlueprintImplementation extends Phobject { * This method should not acquire locks or expect anything to be locked. This * is a coarse compatibility check between a lease and a resource. * - * @param DrydockBlueprint Concrete blueprint to allocate for. - * @param DrydockResource Candidate resource to allocate the lease on. - * @param DrydockLease Pending lease that wants to allocate here. + * @param DrydockBlueprint $blueprint Concrete blueprint to allocate for. + * @param DrydockResource $resource Candidate resource to allocate the lease + * on. + * @param DrydockLease $lease Pending lease that wants to allocate here. * @return bool True if the resource and lease are compatible. * @task lease */ @@ -81,9 +82,9 @@ abstract class DrydockBlueprintImplementation extends Phobject { * * If acquisition fails, throw an exception. * - * @param DrydockBlueprint Blueprint which built the resource. - * @param DrydockResource Resource to acquire a lease on. - * @param DrydockLease Requested lease. + * @param DrydockBlueprint $blueprint Blueprint which built the resource. + * @param DrydockResource $resource Resource to acquire a lease on. + * @param DrydockLease $lease Requested lease. * @return void * @task lease */ @@ -111,9 +112,9 @@ abstract class DrydockBlueprintImplementation extends Phobject { * This callback is primarily useful for automatically releasing resources * once all leases are released. * - * @param DrydockBlueprint Blueprint which built the resource. - * @param DrydockResource Resource a lease was released on. - * @param DrydockLease Recently released lease. + * @param DrydockBlueprint $blueprint Blueprint which built the resource. + * @param DrydockResource $resource Resource a lease was released on. + * @param DrydockLease $lease Recently released lease. * @return void * @task lease */ @@ -128,9 +129,9 @@ abstract class DrydockBlueprintImplementation extends Phobject { * * If a lease creates temporary state while held, destroy it here. * - * @param DrydockBlueprint Blueprint which built the resource. - * @param DrydockResource Resource the lease is acquired on. - * @param DrydockLease The lease being destroyed. + * @param DrydockBlueprint $blueprint Blueprint which built the resource. + * @param DrydockResource $resource Resource the lease is acquired on. + * @param DrydockLease $lease The lease being destroyed. * @return void * @task lease */ @@ -156,11 +157,11 @@ abstract class DrydockBlueprintImplementation extends Phobject { * has a flexible pool of expensive resources and you want to pack leases * onto them as tightly as possible. * - * @param DrydockBlueprint The blueprint for an existing resource being - * acquired. - * @param DrydockResource The resource being acquired, which we may want to - * build a supplemental resource for. - * @param DrydockLease The current lease performing acquisition. + * @param DrydockBlueprint $blueprint The blueprint for an existing resource + * being acquired. + * @param DrydockResource $resource The resource being acquired, which we may + * want to build a supplemental resource for. + * @param DrydockLease $lease The current lease performing acquisition. * @return bool True to prefer allocating a supplemental resource. * * @task lease @@ -190,7 +191,7 @@ abstract class DrydockBlueprintImplementation extends Phobject { * really exists, only if some blueprint may conceivably exist which could * plausibly be able to build a suitable resource. * - * @param DrydockLease Requested lease. + * @param DrydockLease $lease Requested lease. * @return bool True if some concrete blueprint of this implementation's * type might ever be able to build a resource for the lease. * @task resource @@ -211,9 +212,9 @@ abstract class DrydockBlueprintImplementation extends Phobject { * if the blueprint as configured may, at some time, be able to build a * suitable resource. * - * @param DrydockBlueprint Blueprint which may be asked to allocate a - * resource. - * @param DrydockLease Requested lease. + * @param DrydockBlueprint $blueprint Blueprint which may be asked to + * allocate a resource. + * @param DrydockLease $lease Requested lease. * @return bool True if this blueprint can eventually build a suitable * resource for the lease, as currently configured. * @task resource @@ -240,9 +241,9 @@ abstract class DrydockBlueprintImplementation extends Phobject { * eaten up free capacity by the time it actually tries to build a resource. * This is normal and the allocator will recover from it. * - * @param DrydockBlueprint The blueprint which may be asked to allocate a - * resource. - * @param DrydockLease Requested lease. + * @param DrydockBlueprint $blueprint The blueprint which may be asked to + * allocate a resource. + * @param DrydockLease $lease Requested lease. * @return bool True if this blueprint appears likely to be able to allocate * a suitable resource. * @task resource @@ -260,8 +261,9 @@ abstract class DrydockBlueprintImplementation extends Phobject { * called. Blueprints are entirely responsible for any lock handling they * need to perform. * - * @param DrydockBlueprint The blueprint which should allocate a resource. - * @param DrydockLease Requested lease. + * @param DrydockBlueprint $blueprint The blueprint which should allocate a + * resource. + * @param DrydockLease $lease Requested lease. * @return DrydockResource Allocated resource. * @task resource */ @@ -287,8 +289,8 @@ abstract class DrydockBlueprintImplementation extends Phobject { * here. For example, you might shut down a virtual host or destroy a working * copy on disk. * - * @param DrydockBlueprint Blueprint which built the resource. - * @param DrydockResource Resource being destroyed. + * @param DrydockBlueprint $blueprint Blueprint which built the resource. + * @param DrydockResource $resource Resource being destroyed. * @return void * @task resource */ @@ -300,8 +302,8 @@ abstract class DrydockBlueprintImplementation extends Phobject { /** * Get a human readable name for a resource. * - * @param DrydockBlueprint Blueprint which built the resource. - * @param DrydockResource Resource to get the name of. + * @param DrydockBlueprint $blueprint Blueprint which built the resource. + * @param DrydockResource $resource Resource to get the name of. * @return string Human-readable resource name. * @task resource */ @@ -338,7 +340,7 @@ abstract class DrydockBlueprintImplementation extends Phobject { * build a resource which can satisfy the lease. They may not be able to * build that resource right now. * - * @param DrydockLease Requested lease. + * @param DrydockLease $lease Requested lease. * @return list List of qualifying blueprint * implementations. */ @@ -429,7 +431,7 @@ abstract class DrydockBlueprintImplementation extends Phobject { /** * Get the effective concurrent resource limit for this blueprint. * - * @param DrydockBlueprint Blueprint to get the limit for. + * @param DrydockBlueprint $blueprint Blueprint to get the limit for. * @return int|null Limit, or `null` for no limit. */ protected function getConcurrentResourceLimit(DrydockBlueprint $blueprint) { @@ -500,7 +502,7 @@ abstract class DrydockBlueprintImplementation extends Phobject { /** * Apply standard limits on resource allocation rate. * - * @param DrydockBlueprint The blueprint requesting an allocation. + * @param DrydockBlueprint $blueprint The blueprint requesting an allocation. * @return bool True if further allocations should be limited. */ protected function shouldLimitAllocatingPoolSize( diff --git a/src/applications/drydock/storage/DrydockAuthorization.php b/src/applications/drydock/storage/DrydockAuthorization.php index 32e694918c..71f8c36cf8 100644 --- a/src/applications/drydock/storage/DrydockAuthorization.php +++ b/src/applications/drydock/storage/DrydockAuthorization.php @@ -103,10 +103,10 @@ final class DrydockAuthorization extends DrydockDAO * Apply external authorization effects after a user changes the value of a * blueprint selector control an object. * - * @param PhabricatorUser User applying the change. - * @param phid Object PHID change is being applied to. - * @param list Old blueprint PHIDs. - * @param list New blueprint PHIDs. + * @param PhabricatorUser $viewer User applying the change. + * @param phid $object_phid Object PHID change is being applied to. + * @param list $old Old blueprint PHIDs. + * @param list $new New blueprint PHIDs. * @return void */ public static function applyAuthorizationChanges( diff --git a/src/applications/drydock/storage/DrydockSlotLock.php b/src/applications/drydock/storage/DrydockSlotLock.php index 7a9bce8b3f..3ba5163342 100644 --- a/src/applications/drydock/storage/DrydockSlotLock.php +++ b/src/applications/drydock/storage/DrydockSlotLock.php @@ -43,7 +43,7 @@ final class DrydockSlotLock extends DrydockDAO { /** * Load all locks held by a particular owner. * - * @param phid Owner PHID. + * @param phid $owner_phid Owner PHID. * @return list All held locks. * @task info */ @@ -57,7 +57,7 @@ final class DrydockSlotLock extends DrydockDAO { /** * Test if a lock is currently free. * - * @param string Lock key to test. + * @param string $lock Lock key to test. * @return bool True if the lock is currently free. * @task info */ @@ -69,7 +69,7 @@ final class DrydockSlotLock extends DrydockDAO { /** * Test if a list of locks are all currently free. * - * @param list List of lock keys to test. + * @param list $locks List of lock keys to test. * @return bool True if all locks are currently free. * @task info */ @@ -82,7 +82,7 @@ final class DrydockSlotLock extends DrydockDAO { /** * Load named locks. * - * @param list List of lock keys to load. + * @param list $locks List of lock keys to load. * @return list List of held locks. * @task info */ @@ -114,8 +114,8 @@ final class DrydockSlotLock extends DrydockDAO { * This method either acquires all the locks or throws an exception (usually * because one or more locks are held). * - * @param phid Lock owner PHID. - * @param list List of locks to acquire. + * @param phid $owner_phid Lock owner PHID. + * @param list $locks List of locks to acquire. * @return void * @task locks */ @@ -158,7 +158,7 @@ final class DrydockSlotLock extends DrydockDAO { /** * Release all locks held by an owner. * - * @param phid Lock owner PHID. + * @param phid $owner_phid Lock owner PHID. * @return void * @task locks */ diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php index 0402276696..54a44660ec 100644 --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -168,7 +168,7 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { * Find or build a resource which can satisfy a given lease request, then * acquire the lease. * - * @param DrydockLease Requested lease. + * @param DrydockLease $lease Requested lease. * @return void * @task allocator */ @@ -573,7 +573,7 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { * Get all the concrete @{class:DrydockBlueprint}s which can possibly * build a resource to satisfy a lease. * - * @param DrydockLease Requested lease. + * @param DrydockLease $lease Requested lease. * @return list List of qualifying blueprints. * @task allocator */ @@ -645,9 +645,9 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { * Load a list of all resources which a given lease can possibly be * allocated against. * - * @param list Blueprints which may produce suitable - * resources. - * @param DrydockLease Requested lease. + * @param list $blueprints Blueprints which may produce + * suitable resources. + * @param DrydockLease $lease Requested lease. * @return list Resources which may be able to allocate * the lease. * @task allocator @@ -675,8 +675,8 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { /** * Remove resources which can not be acquired by a given lease from a list. * - * @param list Candidate resources. - * @param DrydockLease Acquiring lease. + * @param list $resources Candidate resources. + * @param DrydockLease $lease Acquiring lease. * @return list Resources which the lease may be able to * acquire. * @task allocator @@ -703,9 +703,9 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { * Remove blueprints which are too heavily allocated to build a resource for * a lease from a list of blueprints. * - * @param list List of blueprints. - * @return list List with blueprints that can not allocate - * a resource for the lease right now removed. + * @param list $blueprints List of blueprints. + * @return list $lease List with blueprints that can not + * allocate a resource for the lease right now removed. * @task allocator */ private function removeOverallocatedBlueprints( @@ -731,8 +731,8 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { * Rank blueprints by suitability for building a new resource for a * particular lease. * - * @param list List of blueprints. - * @param DrydockLease Requested lease. + * @param list $blueprints List of blueprints. + * @param DrydockLease $lease Requested lease. * @return list Ranked list of blueprints. * @task allocator */ @@ -750,8 +750,8 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { /** * Rank resources by suitability for allocating a particular lease. * - * @param list List of resources. - * @param DrydockLease Requested lease. + * @param list $resources List of resources. + * @param DrydockLease $lease Requested lease. * @return list Ranked list of resources. * @task allocator */ @@ -769,8 +769,9 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { /** * Perform an actual resource allocation with a particular blueprint. * - * @param DrydockBlueprint The blueprint to allocate a resource from. - * @param DrydockLease Requested lease. + * @param DrydockBlueprint $blueprint The blueprint to allocate a resource + * from. + * @param DrydockLease $lease Requested lease. * @return DrydockResource Allocated resource. * @task allocator */ @@ -815,9 +816,10 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { * Check that the resource a blueprint allocated is roughly the sort of * object we expect. * - * @param DrydockBlueprint Blueprint which built the resource. - * @param wild Thing which the blueprint claims is a valid resource. - * @param DrydockLease Lease the resource was allocated for. + * @param DrydockBlueprint $blueprint Blueprint which built the resource. + * @param wild $resource Thing which the blueprint claims is a valid + * resource. + * @param DrydockLease $lease Lease the resource was allocated for. * @return void * @task allocator */ @@ -899,8 +901,8 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { /** * Perform an actual lease acquisition on a particular resource. * - * @param DrydockResource Resource to acquire a lease on. - * @param DrydockLease Lease to acquire. + * @param DrydockResource $resource Resource to acquire a lease on. + * @param DrydockLease $lease Lease to acquire. * @return void * @task acquire */ @@ -931,9 +933,9 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { /** * Make sure that a lease was really acquired properly. * - * @param DrydockBlueprint Blueprint which created the resource. - * @param DrydockResource Resource which was acquired. - * @param DrydockLease The lease which was supposedly acquired. + * @param DrydockBlueprint $blueprint Blueprint which created the resource. + * @param DrydockResource $resource Resource which was acquired. + * @param DrydockLease $lease The lease which was supposedly acquired. * @return void * @task acquire */ diff --git a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php index 14ef8e4936..6316116c53 100644 --- a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php @@ -38,7 +38,7 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { /** * Update a resource, handling exceptions thrown during the update. * - * @param DrydockReosource Resource to update. + * @param DrydockResource $resource Resource to update. * @return void * @task update */ @@ -58,7 +58,7 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { /** * Update a resource. * - * @param DrydockResource Resource to update. + * @param DrydockResource $resource Resource to update. * @return void * @task update */ @@ -89,8 +89,8 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { /** * Convert a temporary exception into a yield. * - * @param DrydockResource Resource to yield. - * @param Exception Temporary exception worker encountered. + * @param DrydockResource $resource Resource to yield. + * @param Exception $ex Temporary exception worker encountered. * @task update */ private function yieldResource(DrydockResource $resource, Exception $ex) { diff --git a/src/applications/feed/PhabricatorFeedStoryPublisher.php b/src/applications/feed/PhabricatorFeedStoryPublisher.php index 70d9a2f61c..81c3dceef0 100644 --- a/src/applications/feed/PhabricatorFeedStoryPublisher.php +++ b/src/applications/feed/PhabricatorFeedStoryPublisher.php @@ -214,7 +214,7 @@ final class PhabricatorFeedStoryPublisher extends Phobject { /** * Remove PHIDs who should not receive notifications from a subscriber list. * - * @param list List of potential subscribers. + * @param list $phids List of potential subscribers. * @return list List of actual subscribers. */ private function filterSubscribedPHIDs(array $phids) { diff --git a/src/applications/feed/story/PhabricatorFeedStory.php b/src/applications/feed/story/PhabricatorFeedStory.php index e0c65c7dc2..2d0ed8836b 100644 --- a/src/applications/feed/story/PhabricatorFeedStory.php +++ b/src/applications/feed/story/PhabricatorFeedStory.php @@ -32,8 +32,9 @@ abstract class PhabricatorFeedStory * construct appropriate @{class:PhabricatorFeedStory} wrappers for each * data row. * - * @param list List of @{class:PhabricatorFeedStoryData} rows from the - * database. + * @param list $rows List of @{class:PhabricatorFeedStoryData} rows + * from the database. + * @param PhabricatorUser $viewer * @return list List of @{class:PhabricatorFeedStory} * objects. * @task load diff --git a/src/applications/files/PhabricatorImageTransformer.php b/src/applications/files/PhabricatorImageTransformer.php index 1075969a42..0ca475e90c 100644 --- a/src/applications/files/PhabricatorImageTransformer.php +++ b/src/applications/files/PhabricatorImageTransformer.php @@ -20,8 +20,8 @@ final class PhabricatorImageTransformer extends Phobject { * Phabricator can not encode images in the given format (based on available * extensions), but can save images in another format. * - * @param resource GD image resource. - * @param string? Optionally, preferred mime type. + * @param resource $data GD image resource. + * @param string? $preferred_mime Optionally, preferred mime type. * @return string Bytes of an image file. * @task save */ @@ -62,7 +62,7 @@ final class PhabricatorImageTransformer extends Phobject { /** * Save an image in PNG format, returning the file data as a string. * - * @param resource GD image resource. + * @param resource $image GD image resource. * @return string|null PNG file as a string, or null on failure. * @task save */ @@ -90,7 +90,7 @@ final class PhabricatorImageTransformer extends Phobject { /** * Save an image in GIF format, returning the file data as a string. * - * @param resource GD image resource. + * @param resource $image GD image resource. * @return string|null GIF file as a string, or null on failure. * @task save */ @@ -114,7 +114,7 @@ final class PhabricatorImageTransformer extends Phobject { /** * Save an image in JPG format, returning the file data as a string. * - * @param resource GD image resource. + * @param resource $image GD image resource. * @return string|null JPG file as a string, or null on failure. * @task save */ diff --git a/src/applications/files/engine/PhabricatorChunkedFileStorageEngine.php b/src/applications/files/engine/PhabricatorChunkedFileStorageEngine.php index 30de63b904..596bdc1c60 100644 --- a/src/applications/files/engine/PhabricatorChunkedFileStorageEngine.php +++ b/src/applications/files/engine/PhabricatorChunkedFileStorageEngine.php @@ -87,8 +87,8 @@ final class PhabricatorChunkedFileStorageEngine * Ideally, we'd like to be able to verify hashes, but this is complicated * and time consuming and gives us a fairly small benefit. * - * @param PhabricatorUser Viewing user. - * @param string Claimed file hash. + * @param PhabricatorUser $viewer Viewing user. + * @param string $hash Claimed file hash. * @return string Rehashed file hash. */ public static function getChunkedHash(PhabricatorUser $viewer, $hash) { diff --git a/src/applications/files/engine/PhabricatorFileStorageEngine.php b/src/applications/files/engine/PhabricatorFileStorageEngine.php index a1e5131614..32c8ed13f8 100644 --- a/src/applications/files/engine/PhabricatorFileStorageEngine.php +++ b/src/applications/files/engine/PhabricatorFileStorageEngine.php @@ -160,8 +160,8 @@ abstract class PhabricatorFileStorageEngine extends Phobject { * throw an exception. If there are other satisfactory but less-preferred * storage engines available, they will be tried. * - * @param string The file data to write. - * @param array File metadata (name, author), if available. + * @param string $data The file data to write. + * @param array $params File metadata (name, author), if available. * @return string Unique string which identifies the stored file, max length * 255. * @task file @@ -172,8 +172,8 @@ abstract class PhabricatorFileStorageEngine extends Phobject { /** * Read the contents of a file previously written by @{method:writeFile}. * - * @param string The handle returned from @{method:writeFile} when the - * file was written. + * @param string $handle The handle returned from @{method:writeFile} + * when the file was written. * @return string File contents. * @task file */ @@ -183,8 +183,8 @@ abstract class PhabricatorFileStorageEngine extends Phobject { /** * Delete the data for a file previously written by @{method:writeFile}. * - * @param string The handle returned from @{method:writeFile} when the - * file was written. + * @param string $handle The handle returned from @{method:writeFile} + * when the file was written. * @return void * @task file */ @@ -200,7 +200,7 @@ abstract class PhabricatorFileStorageEngine extends Phobject { * select the MySQL and Local Disk storage engines if they are configured * to allow a given file. * - * @param int File size in bytes. + * @param int $length File size in bytes. * @task load */ public static function loadStorageEngines($length) { diff --git a/src/applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php b/src/applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php index afed9f20b3..04de4a14a1 100644 --- a/src/applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php +++ b/src/applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php @@ -116,7 +116,7 @@ final class PhabricatorLocalDiskFileStorageEngine /** * Convert a handle into an absolute local disk path. * - * @param string File data handle. + * @param string $handle File data handle. * @return string Absolute path to the corresponding file. * @task internal */ diff --git a/src/applications/files/engine/PhabricatorMySQLFileStorageEngine.php b/src/applications/files/engine/PhabricatorMySQLFileStorageEngine.php index eb49ef78c3..5d8ba94bdb 100644 --- a/src/applications/files/engine/PhabricatorMySQLFileStorageEngine.php +++ b/src/applications/files/engine/PhabricatorMySQLFileStorageEngine.php @@ -80,7 +80,7 @@ final class PhabricatorMySQLFileStorageEngine /** * Load the Lisk object that stores the file data for a handle. * - * @param string File data handle. + * @param string $handle File data handle. * @return PhabricatorFileStorageBlob Data DAO. * @task internal */ diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php index e712d533b0..bb81d54b7d 100644 --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -96,7 +96,8 @@ final class PhabricatorFileQuery * `PHID-FILE-aaaa` and all transformations of the file with PHID * `PHID-FILE-bbbb`. * - * @param list List of transform specifications, described above. + * @param list $specs List of transform specifications, described + * above. * @return this */ public function withTransforms(array $specs) { diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index ffa29f1919..16681973e0 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -807,8 +807,8 @@ final class PhabricatorFile extends PhabricatorFileDAO /** * Return an iterable which emits file content bytes. * - * @param int Offset for the start of data. - * @param int Offset for the end of data. + * @param int? $begin Offset for the start of data. + * @param int? $end Offset for the end of data. * @return Iterable Iterable object which emits requested data. */ public function getFileDataIterator($begin = null, $end = null) { @@ -1214,8 +1214,9 @@ final class PhabricatorFile extends PhabricatorFileDAO * Builtins are located in `resources/builtin/` and identified by their * name. * - * @param PhabricatorUser Viewing user. - * @param list List of builtin file specs. + * @param PhabricatorUser $user Viewing user. + * @param list $builtins List of builtin file + * specs. * @return dict Dictionary of named builtins. */ public static function loadBuiltins(PhabricatorUser $user, array $builtins) { @@ -1281,8 +1282,8 @@ final class PhabricatorFile extends PhabricatorFileDAO /** * Convenience wrapper for @{method:loadBuiltins}. * - * @param PhabricatorUser Viewing user. - * @param string Single builtin name to load. + * @param PhabricatorUser $user Viewing user. + * @param string $name Single builtin name to load. * @return PhabricatorFile Corresponding builtin file. */ public static function loadBuiltin(PhabricatorUser $user, $name) { @@ -1473,7 +1474,7 @@ final class PhabricatorFile extends PhabricatorFileDAO * Write the policy edge between this file and some object. * This method is successful even if the file is already attached. * - * @param phid Object PHID to attach to. + * @param phid $phid Object PHID to attach to. * @return this */ public function attachToObject($phid) { @@ -1487,8 +1488,8 @@ final class PhabricatorFile extends PhabricatorFileDAO * NOTE: Please avoid to use this static method directly. * Instead, use PhabricatorFile#attachToObject(phid). * - * @param phid File PHID to attach from. - * @param phid Object PHID to attach to. + * @param phid $file_phid File PHID to attach from. + * @param phid $object_phid Object PHID to attach to. * @return void */ public static function attachFileToObject($file_phid, $object_phid) { @@ -1526,8 +1527,8 @@ final class PhabricatorFile extends PhabricatorFileDAO * This method is called both when creating a file from fresh data, and * when creating a new file which reuses existing storage. * - * @param map Bag of parameters, see @{class:PhabricatorFile} - * for documentation. + * @param map $params Bag of parameters, see + * @{class:PhabricatorFile} for documentation. * @return this */ private function readPropertiesFromParameters(array $params) { diff --git a/src/applications/files/transform/PhabricatorFileImageTransform.php b/src/applications/files/transform/PhabricatorFileImageTransform.php index 98ffcdd706..a06d00916b 100644 --- a/src/applications/files/transform/PhabricatorFileImageTransform.php +++ b/src/applications/files/transform/PhabricatorFileImageTransform.php @@ -11,7 +11,7 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { /** * Get an estimate of the transformed dimensions of a file. * - * @param PhabricatorFile File to transform. + * @param PhabricatorFile $file File to transform. * @return list|null Width and height, if available. */ public function getTransformedDimensions(PhabricatorFile $file) { @@ -132,7 +132,7 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { /** * Create a new @{class:PhabricatorFile} from raw data. * - * @param string Raw file data. + * @param string $data Raw file data. */ protected function newFileFromData($data) { if ($this->file) { @@ -159,8 +159,8 @@ abstract class PhabricatorFileImageTransform extends PhabricatorFileTransform { /** * Create a new image filled with transparent pixels. * - * @param int Desired image width. - * @param int Desired image height. + * @param int $w Desired image width. + * @param int $h Desired image height. * @return resource New image resource. */ protected function newEmptyImage($w, $h) { diff --git a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php index 009758078f..9fe61b5f91 100644 --- a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php +++ b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php @@ -96,7 +96,7 @@ final class HarbormasterBuildStatus extends Phobject { /** * Get a human readable name for a build status constant. * - * @param const Build status constant. + * @param const $status Build status constant. * @return string Human-readable name. */ public static function getBuildStatusName($status) { diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php index fd72e31a0e..266fed3804 100644 --- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php @@ -322,8 +322,9 @@ final class HarbormasterBuildEngine extends Phobject { * particularly relevant when a build uses multiple hosts since it returns * hosts to the pool more quickly. * - * @param list Targets in the build. - * @param list List of running and waiting steps. + * @param list $targets Targets in the build. + * @param list $steps List of running and waiting + * steps. * @return void */ private function releaseUnusedArtifacts(array $targets, array $steps) { @@ -372,7 +373,7 @@ final class HarbormasterBuildEngine extends Phobject { * Process messages which were sent to these targets, kicking applicable * targets out of "Waiting" and into either "Passed" or "Failed". * - * @param list List of targets to process. + * @param list $targets List of targets to process. * @return void */ private function updateWaitingTargets(array $targets) { @@ -436,7 +437,7 @@ final class HarbormasterBuildEngine extends Phobject { * the overall state of the associated buildable. Compute the new aggregate * state and save it on the buildable. * - * @param HarbormasterBuild The buildable to update. + * @param HarbormasterBuild $buildable The buildable to update. * @return void */ public function updateBuildable(HarbormasterBuildable $buildable) { diff --git a/src/applications/harbormaster/engine/HarbormasterTargetEngine.php b/src/applications/harbormaster/engine/HarbormasterTargetEngine.php index d9efb8b9fd..62f679774d 100644 --- a/src/applications/harbormaster/engine/HarbormasterTargetEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterTargetEngine.php @@ -56,7 +56,7 @@ final class HarbormasterTargetEngine extends Phobject { * * This method creates the steps if they do not yet exist. * - * @param list Autotarget keys, like `"core.arc.lint"`. + * @param list $autotargets Autotarget keys, like `"core.arc.lint"`. * @return map Map of keys to step objects. */ private function generateBuildStepMap(array $autotargets) { @@ -127,7 +127,7 @@ final class HarbormasterTargetEngine extends Phobject { * Get all of the @{class:HarbormasterBuildStepImplementation} objects for * a list of autotarget keys. * - * @param list Autotarget keys, like `"core.arc.lint"`. + * @param list $autotargets Autotarget keys, like `"core.arc.lint"`. * @return map Map of keys to implementations. */ private function getAutosteps(array $autotargets) { @@ -154,8 +154,8 @@ final class HarbormasterTargetEngine extends Phobject { * * If some targets or builds do not exist, they are created. * - * @param HarbormasterBuildable A buildable. - * @param map Map of keys to steps. + * @param HarbormasterBuildable $buildable A buildable. + * @param map $step_map Map of keys to steps. * @return map Map of keys to targets. */ private function generateBuildTargetMap( diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php index 3bbb72cd95..851a020687 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -187,9 +187,11 @@ abstract class HarbormasterBuildStepImplementation extends Phobject { * * ls 'dir with spaces' * - * @param string Name of a `vxsprintf` function, like @{function:vcsprintf}. - * @param string User-provided pattern string containing `${variables}`. - * @param dict List of available replacement variables. + * @param string $function Name of a `vxsprintf` function, like + * @{function:vcsprintf}. + * @param string $pattern User-provided pattern string containing + * `${variables}`. + * @param dict $variables List of available replacement variables. * @return string String with variables replaced safely into it. */ protected function mergeVariables($function, $pattern, array $variables) { diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index df8451d842..2d085ce523 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -60,9 +60,9 @@ final class HarbormasterBuildable /** * Start builds for a given buildable. * - * @param phid PHID of the object to build. - * @param phid Container PHID for the buildable. - * @param list List of builds to perform. + * @param phid $phid PHID of the object to build. + * @param phid $container_phid Container PHID for the buildable. + * @param list $requests List of builds to perform. * @return void */ public static function applyBuildPlans( diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index c04a10ea51..8824f645e3 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -171,7 +171,8 @@ abstract class HeraldAdapter extends Phobject { * These transactions are set by @{class:PhabricatorApplicationEditor} * automatically, before it invokes Herald. * - * @param list List of transactions. + * @param list $xactions List of + * transactions. * @return this */ final public function setAppliedTransactions(array $xactions) { diff --git a/src/applications/maniphest/constants/ManiphestTaskPriority.php b/src/applications/maniphest/constants/ManiphestTaskPriority.php index 8b43da132b..1b20b52738 100644 --- a/src/applications/maniphest/constants/ManiphestTaskPriority.php +++ b/src/applications/maniphest/constants/ManiphestTaskPriority.php @@ -137,7 +137,7 @@ final class ManiphestTaskPriority extends ManiphestConstants { /** * Retrieve the full name of the priority level provided. * - * @param int A priority level. + * @param int $priority A priority level. * @return string The priority name if the level is a valid one. */ public static function getTaskPriorityName($priority) { @@ -147,7 +147,7 @@ final class ManiphestTaskPriority extends ManiphestConstants { /** * Retrieve the color of the priority level given * - * @param int A priority level. + * @param int $priority A priority level. * @return string The color of the priority if the level is valid, * or black if it is not. */ diff --git a/src/applications/meta/panel/PhabricatorApplicationConfigurationPanel.php b/src/applications/meta/panel/PhabricatorApplicationConfigurationPanel.php index d96132c9b0..944daa9e08 100644 --- a/src/applications/meta/panel/PhabricatorApplicationConfigurationPanel.php +++ b/src/applications/meta/panel/PhabricatorApplicationConfigurationPanel.php @@ -27,7 +27,7 @@ abstract class PhabricatorApplicationConfigurationPanel /** * Get the URI for this application configuration panel. * - * @param string? Optional path to append. + * @param string? $path Optional path to append. * @return string Relative URI for the panel. */ public function getPanelURI($path = '') { diff --git a/src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php b/src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php index f20e626573..45b7735ce0 100644 --- a/src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php +++ b/src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php @@ -26,7 +26,7 @@ final class PhabricatorMetaMTAEmailBodyParser extends Phobject { * ), * ) * - * @param string Raw mail text body. + * @param string $body Raw mail text body. * @return dict Parsed body. */ public function parseBody($body) { diff --git a/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php b/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php index 65c6089f3b..4b7b0eee37 100644 --- a/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php +++ b/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php @@ -19,9 +19,9 @@ abstract class PhabricatorObjectMailReceiver extends PhabricatorMailReceiver { * Load the object receiving mail, based on an identifying pattern. Normally * this pattern is some sort of object ID. * - * @param string A string matched by @{method:getObjectPattern} - * fragment. - * @param PhabricatorUser The viewing user. + * @param string $pattern A string matched by + * @{method:getObjectPattern} fragment. + * @param PhabricatorUser $viewer The viewing user. * @return object|null The object to receive mail, or null if no such * object exists. */ diff --git a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php index 72e23ec2c1..53941e94cf 100644 --- a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php +++ b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php @@ -199,8 +199,8 @@ abstract class PhabricatorMailReplyHandler extends Phobject { * Each target should be sent a separate email, and contains the information * required to generate it with appropriate permissions and configuration. * - * @param list List of "To" PHIDs. - * @param list List of "CC" PHIDs. + * @param list $raw_to List of "To" PHIDs. + * @param list $raw_cc List of "CC" PHIDs. * @return list List of targets. */ final public function getMailTargets(array $raw_to, array $raw_cc) { @@ -274,8 +274,8 @@ abstract class PhabricatorMailReplyHandler extends Phobject { * This takes any compound recipients (like projects) and looks up all their * members. * - * @param list List of To PHIDs. - * @param list List of CC PHIDs. + * @param list $to List of To PHIDs. + * @param list $cc List of CC PHIDs. * @return pair, list> Expanded PHID lists. */ private function expandRecipientPHIDs(array $to, array $cc) { @@ -332,8 +332,8 @@ abstract class PhabricatorMailReplyHandler extends Phobject { * * Invalid recipients are dropped from the results. * - * @param list List of To PHIDs. - * @param list List of CC PHIDs. + * @param list $to List of To PHIDs. + * @param list $cc List of CC PHIDs. * @return pair Maps from PHIDs to users. */ private function loadRecipientUsers(array $to, array $cc) { @@ -370,8 +370,8 @@ abstract class PhabricatorMailReplyHandler extends Phobject { /** * Remove recipients who do not have permission to view the mail receiver. * - * @param map Map of "To" users. - * @param map Map of "CC" users. + * @param map $to Map of "To" users. + * @param map $cc Map of "CC" users. * @return pair Filtered user maps. */ private function filterRecipientUsers(array $to, array $cc) { diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php index cc3ae82bef..2d1ec54bb9 100644 --- a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php +++ b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php @@ -88,7 +88,7 @@ final class PhabricatorMetaMTAMail * These tags are used to allow users to opt out of receiving certain types * of mail, like updates when a task's projects change. * - * @param list + * @param list $tags * @return this */ public function setMailTags(array $tags) { @@ -109,7 +109,7 @@ final class PhabricatorMetaMTAMail * needs to be set whenever an action is triggered by an email message. See * T251 for more details. * - * @param string The "Message-ID" of the email which precedes this one. + * @param string $id The "Message-ID" of the email which precedes this one. * @return this */ public function setParentMessageID($id) { @@ -419,7 +419,7 @@ final class PhabricatorMetaMTAMail * This is primarily intended to let users who don't want any email still * receive things like password resets. * - * @param bool True to force delivery despite user preferences. + * @param bool $force True to force delivery despite user preferences. * @return this */ public function setForceDelivery($force) { @@ -437,7 +437,7 @@ final class PhabricatorMetaMTAMail * "Precedence: bulk" or similar, but is implementation and configuration * dependent. * - * @param bool True if the mail is automated bulk mail. + * @param bool $is_bulk True if the mail is automated bulk mail. * @return this */ public function setIsBulk($is_bulk) { @@ -454,9 +454,10 @@ final class PhabricatorMetaMTAMail * set appropriate headers (Message-ID, In-Reply-To, References and * Thread-Index) based on the capabilities of the underlying mailer. * - * @param string Unique identifier, appropriate for use in a Message-ID, - * In-Reply-To or References headers. - * @param bool If true, indicates this is the first message in the thread. + * @param string $thread_id Unique identifier, appropriate for use in a + * Message-ID, In-Reply-To or References headers. + * @param bool? $is_first_message If true, indicates this is the first + * message in the thread. * @return this */ public function setThreadID($thread_id, $is_first_message = false) { @@ -858,8 +859,8 @@ final class PhabricatorMetaMTAMail * For example, this will expand project PHIDs into a list of the project's * members. * - * @param list List of recipient PHIDs, possibly including aggregate - * recipients. + * @param list $phids List of recipient PHIDs, possibly including + * aggregate recipients. * @return list Deaggregated list of mailable recipients. */ public function expandRecipients(array $phids) { diff --git a/src/applications/metamta/util/PhabricatorMailUtil.php b/src/applications/metamta/util/PhabricatorMailUtil.php index 270e9786f3..66f2289227 100644 --- a/src/applications/metamta/util/PhabricatorMailUtil.php +++ b/src/applications/metamta/util/PhabricatorMailUtil.php @@ -10,7 +10,7 @@ final class PhabricatorMailUtil * which can make forwarding rules easier to write. This method strips the * prefix if it is present, and normalizes casing and whitespace. * - * @param PhutilEmailAddress Email address. + * @param PhutilEmailAddress $address Email address. * @return PhutilEmailAddress Normalized address. */ public static function normalizeAddress(PhutilEmailAddress $address) { @@ -47,8 +47,8 @@ final class PhabricatorMailUtil * * "Abraham" # With configured prefix. * - * @param PhutilEmailAddress Email address. - * @param PhutilEmailAddress Another email address. + * @param PhutilEmailAddress $u Email address. + * @param PhutilEmailAddress $v Another email address. * @return bool True if addresses are effectively the same address. */ public static function matchAddresses( diff --git a/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php b/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php index 6622f0afeb..2178bfc130 100644 --- a/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php +++ b/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php @@ -40,7 +40,7 @@ final class PhabricatorMetaMTAMailBody extends Phobject { /** * Add a raw block of text to the email. This will be rendered as-is. * - * @param string Block of text. + * @param string $text Block of text. * @return this * @task compose */ @@ -100,8 +100,8 @@ final class PhabricatorMetaMTAMailBody extends Phobject { * HEADER * Text is indented. * - * @param string Header text. - * @param string Section text. + * @param string $header Header text. + * @param string $section Section text. * @return this * @task compose */ @@ -155,7 +155,7 @@ final class PhabricatorMetaMTAMailBody extends Phobject { /** * Add an attachment. * - * @param PhabricatorMailAttachment Attachment. + * @param PhabricatorMailAttachment $attachment Attachment. * @return this * @task compose */ @@ -198,7 +198,7 @@ final class PhabricatorMetaMTAMailBody extends Phobject { /** * Indent a block of text for rendering under a section heading. * - * @param string Text to indent. + * @param string $text Text to indent. * @return string Indented text. * @task render */ diff --git a/src/applications/notification/query/PhabricatorNotificationQuery.php b/src/applications/notification/query/PhabricatorNotificationQuery.php index 021d666b13..3c2a26a217 100644 --- a/src/applications/notification/query/PhabricatorNotificationQuery.php +++ b/src/applications/notification/query/PhabricatorNotificationQuery.php @@ -31,8 +31,8 @@ final class PhabricatorNotificationQuery * only unread notifications, while `false` means to return only //read// * notifications. The default is `null`, which returns both. * - * @param mixed True or false to filter results by read status. Null to remove - * the filter. + * @param mixed $unread True or false to filter results by read status. Null + * to remove the filter. * @return this * @task config */ diff --git a/src/applications/passphrase/view/PassphraseCredentialControl.php b/src/applications/passphrase/view/PassphraseCredentialControl.php index 98294832f7..09245157d2 100644 --- a/src/applications/passphrase/view/PassphraseCredentialControl.php +++ b/src/applications/passphrase/view/PassphraseCredentialControl.php @@ -157,9 +157,9 @@ final class PassphraseCredentialControl extends AphrontFormControl { * - If you do change the credential, the new credential must be one you * can use. * - * @param PhabricatorUser The acting user. - * @param list List of credential altering - * transactions. + * @param PhabricatorUser $actor The acting user. + * @param list $xactions List of + * credential altering transactions. * @return bool True if the transactions are valid. */ public static function validateTransactions( diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index 82f1affe82..0e1e2560ac 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -429,8 +429,8 @@ final class PhabricatorUser /** * Test if a given setting is set to a particular value. * - * @param const Setting key. - * @param wild Value to compare. + * @param const $key Setting key. + * @param wild $value Value to compare. * @return bool True if the setting has the specified value. * @task settings */ @@ -477,7 +477,7 @@ final class PhabricatorUser * * This is primarily useful for unit tests. * - * @param string New timezone identifier. + * @param string $identifier New timezone identifier. * @return this * @task settings */ @@ -770,8 +770,8 @@ final class PhabricatorUser /** * Write to the availability cache. * - * @param wild Availability cache data. - * @param int|null Cache TTL. + * @param wild $availability Availability cache data. + * @param int|null $ttl Cache TTL. * @return this * @task availability */ @@ -916,7 +916,7 @@ final class PhabricatorUser * Get a @{class:PhabricatorHandleList} which benefits from this viewer's * internal handle pool. * - * @param list List of PHIDs to load. + * @param list $phids List of PHIDs to load. * @return PhabricatorHandleList Handle list object. * @task handle */ @@ -935,7 +935,7 @@ final class PhabricatorUser * * This benefits from the viewer's internal handle pool. * - * @param phid PHID to render a handle for. + * @param phid $phid PHID to render a handle for. * @return PHUIHandleView View of the handle. * @task handle */ @@ -949,7 +949,7 @@ final class PhabricatorUser * * This benefits from the viewer's internal handle pool. * - * @param list List of PHIDs to render. + * @param list $phids List of PHIDs to render. * @return PHUIHandleListView View of the handles. * @task handle */ diff --git a/src/applications/people/storage/PhabricatorUserEmail.php b/src/applications/people/storage/PhabricatorUserEmail.php index 47c2911601..bf6b487d88 100644 --- a/src/applications/people/storage/PhabricatorUserEmail.php +++ b/src/applications/people/storage/PhabricatorUserEmail.php @@ -180,7 +180,7 @@ final class PhabricatorUserEmail /** * Send a verification email from $user to this address. * - * @param PhabricatorUser The user sending the verification. + * @param PhabricatorUser $user The user sending the verification. * @return this * @task email */ @@ -229,8 +229,8 @@ final class PhabricatorUserEmail * Send a notification email from $user to this address, informing the * recipient that this is no longer their account's primary address. * - * @param PhabricatorUser The user sending the notification. - * @param PhabricatorUserEmail New primary email address. + * @param PhabricatorUser $user The user sending the notification. + * @param PhabricatorUserEmail $new New primary email address. * @task email */ public function sendOldPrimaryEmail( @@ -269,7 +269,7 @@ final class PhabricatorUserEmail * Send a notification email from $user to this address, informing the * recipient that this is now their account's new primary email address. * - * @param PhabricatorUser The user sending the verification. + * @param PhabricatorUser $user The user sending the verification. * @return this * @task email */ diff --git a/src/applications/phid/PhabricatorObjectHandle.php b/src/applications/phid/PhabricatorObjectHandle.php index 577fd05692..6a6c1f19b8 100644 --- a/src/applications/phid/PhabricatorObjectHandle.php +++ b/src/applications/phid/PhabricatorObjectHandle.php @@ -275,7 +275,7 @@ final class PhabricatorObjectHandle * Set whether or not the underlying object is complete. See * @{method:isComplete} for an explanation of what it means to be complete. * - * @param bool True if the handle represents a complete object. + * @param bool $complete True if the handle represents a complete object. * @return this */ public function setComplete($complete) { diff --git a/src/applications/phid/query/PhabricatorObjectQuery.php b/src/applications/phid/query/PhabricatorObjectQuery.php index 8364f0210d..f1dd1feb4d 100644 --- a/src/applications/phid/query/PhabricatorObjectQuery.php +++ b/src/applications/phid/query/PhabricatorObjectQuery.php @@ -196,8 +196,8 @@ final class PhabricatorObjectQuery * viewer. This method is generally used to validate that PHIDs affected by * a transaction are valid. * - * @param PhabricatorUser Viewer. - * @param list List of ostensibly valid PHIDs. + * @param PhabricatorUser $viewer Viewer. + * @param list $phids List of ostensibly valid PHIDs. * @return list List of invalid or restricted PHIDs. */ public static function loadInvalidPHIDsForViewer( diff --git a/src/applications/phid/type/PhabricatorPHIDType.php b/src/applications/phid/type/PhabricatorPHIDType.php index 6a283a7d08..fda09c00d8 100644 --- a/src/applications/phid/type/PhabricatorPHIDType.php +++ b/src/applications/phid/type/PhabricatorPHIDType.php @@ -52,8 +52,8 @@ abstract class PhabricatorPHIDType extends Phobject { * can provide a dummy implementation for this method and overload * @{method:loadObjects} instead. * - * @param PhabricatorObjectQuery Query being executed. - * @param list PHIDs to load. + * @param PhabricatorObjectQuery $query Query being executed. + * @param list $phids PHIDs to load. * @return PhabricatorPolicyAwareQuery Query object which loads the * specified PHIDs when executed. */ @@ -67,8 +67,8 @@ abstract class PhabricatorPHIDType extends Phobject { * necessary to implement @{method:buildQueryForObjects} to get object * loading to work. * - * @param PhabricatorObjectQuery Query being executed. - * @param list PHIDs to load. + * @param PhabricatorObjectQuery $query Query being executed. + * @param list $phids PHIDs to load. * @return list Corresponding objects. */ public function loadObjects( @@ -113,10 +113,11 @@ abstract class PhabricatorPHIDType extends Phobject { * each handle at a minimum. See @{class:PhabricatorObjectHandle} for other * handle properties. * - * @param PhabricatorHandleQuery Issuing query object. - * @param list Handles to populate with data. - * @param list Objects for these PHIDs loaded by - * @{method:buildQueryForObjects()}. + * @param PhabricatorHandleQuery $query Issuing query object. + * @param list $handles Handles to populate with + * data. + * @param list $objects Objects for these PHIDs + * loaded by @{method:buildQueryForObjects()}. * @return void */ abstract public function loadHandles( @@ -165,7 +166,7 @@ abstract class PhabricatorPHIDType extends Phobject { /** * Get all PHID types of applications installed for a given viewer. * - * @param PhabricatorUser Viewing user. + * @param PhabricatorUser $viewer Viewing user. * @return dict Map of constants to installed * types. */ @@ -209,7 +210,7 @@ abstract class PhabricatorPHIDType extends Phobject { /** * Get all PHID types of an application. * - * @param string Class name of an application + * @param string $application Class name of an application * @return dict Map of constants of application */ public static function getAllTypesForApplication( diff --git a/src/applications/phid/utils.php b/src/applications/phid/utils.php index 152a547284..eb14c57df9 100644 --- a/src/applications/phid/utils.php +++ b/src/applications/phid/utils.php @@ -4,7 +4,7 @@ * Look up the type of a PHID. Returns * PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN if it fails to look up the type * - * @param phid Anything. + * @param phid $phid Anything. * @return string A value from PhabricatorPHIDConstants (ideally) */ function phid_get_type($phid) { @@ -18,7 +18,7 @@ function phid_get_type($phid) { /** * Group a list of phids by type. * - * @param phids array of phids + * @param $phids Array of phids * @return map of phid type => list of phids */ function phid_group_by_type($phids) { diff --git a/src/applications/phrequent/storage/PhrequentTimeBlock.php b/src/applications/phrequent/storage/PhrequentTimeBlock.php index 3800651476..8649b4f67a 100644 --- a/src/applications/phrequent/storage/PhrequentTimeBlock.php +++ b/src/applications/phrequent/storage/PhrequentTimeBlock.php @@ -254,7 +254,8 @@ final class PhrequentTimeBlock extends Phobject { * This is used to avoid double-counting time on objects which had timers * started multiple times. * - * @param list> List of possibly overlapping time ranges. + * @param list> $ranges List of possibly overlapping time + * ranges. * @return list> Nonoverlapping time ranges. */ public static function mergeTimeRanges(array $ranges) { diff --git a/src/applications/policy/filter/PhabricatorPolicyFilter.php b/src/applications/policy/filter/PhabricatorPolicyFilter.php index e7e8c86ee7..2913381be9 100644 --- a/src/applications/policy/filter/PhabricatorPolicyFilter.php +++ b/src/applications/policy/filter/PhabricatorPolicyFilter.php @@ -52,11 +52,13 @@ final class PhabricatorPolicyFilter extends Phobject { * ...will throw a @{class:PhabricatorPolicyException} if the new policy would * remove the user's ability to edit the object. * - * @param PhabricatorUser The viewer to perform a policy check for. - * @param PhabricatorPolicyInterface The object to perform a policy check on. - * @param string Capability to test. - * @param string Perform the test as though the object has this - * policy instead of the policy it actually has. + * @param PhabricatorUser $viewer The viewer to perform a policy check for. + * @param PhabricatorPolicyInterface $object The object to perform a policy + * check on. + * @param string $capability Capability to test. + * @param string $forced_policy Perform the test as though the + * object has this policy instead of the policy it + * actually has. * @return void */ public static function requireCapabilityWithForcedPolicy( diff --git a/src/applications/policy/interface/PhabricatorExtendedPolicyInterface.php b/src/applications/policy/interface/PhabricatorExtendedPolicyInterface.php index deeaecdbef..1a56dacfba 100644 --- a/src/applications/policy/interface/PhabricatorExtendedPolicyInterface.php +++ b/src/applications/policy/interface/PhabricatorExtendedPolicyInterface.php @@ -62,8 +62,8 @@ interface PhabricatorExtendedPolicyInterface { * // ... * ); * - * @param const Capability being tested. - * @param PhabricatorUser Viewer whose capabilities are being tested. + * @param const $capability Capability being tested. + * @param PhabricatorUser $viewer Viewer whose capabilities are being tested. * @return list> List of extended policies. */ public function getExtendedPolicy($capability, PhabricatorUser $viewer); diff --git a/src/applications/policy/rule/PhabricatorPolicyRule.php b/src/applications/policy/rule/PhabricatorPolicyRule.php index 2f9fac9cfb..fe3edce0aa 100644 --- a/src/applications/policy/rule/PhabricatorPolicyRule.php +++ b/src/applications/policy/rule/PhabricatorPolicyRule.php @@ -120,9 +120,9 @@ abstract class PhabricatorPolicyRule extends Phobject { * rendering a verdict about whether the user will be able to see the object * or not after applying the policy change. * - * @param PhabricatorPolicyInterface Object to pass a hint about. - * @param PhabricatorPolicyRule Rule to pass hint to. - * @param wild Hint. + * @param PhabricatorPolicyInterface $object Object to pass a hint about. + * @param PhabricatorPolicyRule $rule Rule to pass hint to. + * @param wild $hint Hint. * @return void */ public static function passTransactionHintToRule( diff --git a/src/applications/policy/storage/PhabricatorPolicy.php b/src/applications/policy/storage/PhabricatorPolicy.php index 5e5ec14107..56f15e37b8 100644 --- a/src/applications/policy/storage/PhabricatorPolicy.php +++ b/src/applications/policy/storage/PhabricatorPolicy.php @@ -348,7 +348,7 @@ final class PhabricatorPolicy * policy. This is used to bulk load data (like project memberships) in order * to apply policy filters efficiently. * - * @param string Policy rule classname. + * @param string $rule_class Policy rule classname. * @return list List of values used in this policy. */ public function getCustomRuleValues($rule_class) { @@ -402,7 +402,7 @@ final class PhabricatorPolicy * set of unique users. In this case, neither is strictly stronger than * the other. * - * @param PhabricatorPolicy Other policy. + * @param PhabricatorPolicy $other Other policy. * @return bool `true` if this policy is more restrictive than the other * policy. */ diff --git a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php index 48eabf1545..8294f6832e 100644 --- a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php +++ b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php @@ -341,8 +341,8 @@ final class PhabricatorRepositoryPullLocalDaemon * With the `$consume` flag, an internal cursor will also be incremented so * that these messages are not returned by subsequent calls. * - * @param bool Pass `true` to consume these messages, so the process will - * not see them again. + * @param bool? $consume Pass `true` to consume these messages, so the + * process will not see them again. * @return list Pending update messages. * * @task pull diff --git a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php index 8f5b346ecd..7d66868f50 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php @@ -538,7 +538,7 @@ final class PhabricatorRepositoryDiscoveryEngine * * @task internal * - * @param list List of refs. + * @param list $refs List of refs. * @return list Sorted list of refs. */ private function sortRefs(array $refs) { diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index f611c51112..af4cd95d0a 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -740,7 +740,7 @@ final class PhabricatorRepositoryPullEngine * error message. To prevent this, censor response bodies out of error * messages. * - * @param string Uncensored Mercurial command output. + * @param string $message Uncensored Mercurial command output. * @return string Censored Mercurial command output. */ private function censorMercurialErrorMessage($message) { diff --git a/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php b/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php index 60a96578a3..6cb9f27384 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php @@ -233,7 +233,7 @@ final class PhabricatorRepositoryRefEngine * point at commits which no longer exist. This can make commands issued later * fail. See T5839 for discussion. * - * @param list List of commit identifiers. + * @param list $identifiers List of commit identifiers. * @return list List with nonexistent identifiers removed. */ private function removeMissingCommits(array $identifiers) { diff --git a/src/applications/repository/graphcache/PhabricatorRepositoryGraphCache.php b/src/applications/repository/graphcache/PhabricatorRepositoryGraphCache.php index c4adc61ab0..2d9c36fae7 100644 --- a/src/applications/repository/graphcache/PhabricatorRepositoryGraphCache.php +++ b/src/applications/repository/graphcache/PhabricatorRepositoryGraphCache.php @@ -64,10 +64,10 @@ final class PhabricatorRepositoryGraphCache extends Phobject { /** * Search the graph cache for the most modification to a path. * - * @param int The commit ID to search ancestors of. - * @param int The path ID to search for changes to. - * @param float Maximum number of seconds to spend trying to satisfy this - * query using the graph cache. By default, `0.5` (500ms). + * @param int $commit_id The commit ID to search ancestors of. + * @param int $path_id The path ID to search for changes to. + * @param float $time Maximum number of seconds to spend trying to satisfy + * this query using the graph cache. By default `0.5` (500ms). * @return mixed Commit ID, or `null` if no ancestors exist, or `false` if * the graph cache was unable to determine the answer. * @task query @@ -189,7 +189,7 @@ final class PhabricatorRepositoryGraphCache extends Phobject { /** * Get the bucket key for a given commit ID. * - * @param int Commit ID. + * @param int $commit_id Commit ID. * @return int Bucket key. * @task cache */ @@ -201,7 +201,7 @@ final class PhabricatorRepositoryGraphCache extends Phobject { /** * Get the cache key for a given bucket key (from @{method:getBucketKey}). * - * @param int Bucket key. + * @param int $bucket_key Bucket key. * @return string Cache key. * @task cache */ @@ -235,9 +235,10 @@ final class PhabricatorRepositoryGraphCache extends Phobject { * Normally, this operates as a readthrough cache call. It can also be used * to force a cache update by passing the existing data to `$rebuild_data`. * - * @param int Bucket key, from @{method:getBucketKey}. - * @param mixed Current data, to force a cache rebuild of this bucket. - * @return array Data from the cache. + * @param int $bucket_key Bucket key, from @{method:getBucketKey}. + * @param mixed? $rebuild_data Current data, to force a cache rebuild of + * this bucket. + * @return array Data from the cache. * @task cache */ private function getBucketData($bucket_key, $rebuild_data = null) { @@ -287,9 +288,9 @@ final class PhabricatorRepositoryGraphCache extends Phobject { /** * Rebuild a cache bucket, amending existing data if available. * - * @param int Bucket key, from @{method:getBucketKey}. - * @param array Existing bucket data. - * @return array Rebuilt bucket data. + * @param int $bucket_key Bucket key, from @{method:getBucketKey}. + * @param array $current_data Existing bucket data. + * @return array Rebuilt bucket data. * @task cache */ private function rebuildBucket($bucket_key, array $current_data) { diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index b2855c0c06..b9aab043ad 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1288,7 +1288,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO /** * Determine if a protocol is SSH or SSH-like. * - * @param string A protocol string, like "http" or "ssh". + * @param string $protocol A protocol string, like "http" or "ssh". * @return bool True if the protocol is SSH-like. * @task uri */ @@ -1701,7 +1701,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO * 2037). We adjust the pull frequency based on when the most recent commit * occurred. * - * @param int The minimum update interval to use, in seconds. + * @param int? $minimum The minimum update interval to use, in seconds. * @return int Repository update interval, in seconds. */ public function loadUpdateInterval($minimum = 15) { @@ -1836,8 +1836,8 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO * with repository services. This method provides lower-level resolution of * services, returning raw URIs. * - * @param PhabricatorUser Viewing user. - * @param map Constraints on selectable services. + * @param PhabricatorUser $viewer Viewing user. + * @param map $options Constraints on selectable services. * @return string|null URI, or `null` for local repositories. */ public function getAlmanacServiceURI( @@ -2168,8 +2168,8 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO * * For lower-level service resolution, see @{method:getAlmanacServiceURI}. * - * @param PhabricatorUser Viewing user. - * @param bool `true` to throw if a client would be returned. + * @param PhabricatorUser $viewer Viewing user. + * @param bool? $never_proxy `true` to throw if a client would be returned. * @return ConduitClient|null Client, or `null` for local repositories. */ public function newConduitClient( diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index 489dd08065..1979ec092a 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -126,7 +126,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { /** * Create a saved query object from the request. * - * @param AphrontRequest The search request. + * @param AphrontRequest $request The search request. * @return PhabricatorSavedQuery */ public function buildSavedQueryFromRequest(AphrontRequest $request) { @@ -147,7 +147,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { /** * Executes the saved query. * - * @param PhabricatorSavedQuery The saved query to operate on. + * @param PhabricatorSavedQuery $original The saved query to operate on. * @return PhabricatorQuery The result of the query. */ public function buildQueryFromSavedQuery(PhabricatorSavedQuery $original) { @@ -201,7 +201,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { * hook to keep old queries working the way users expect, by reading, * adjusting, and overwriting parameters. * - * @param PhabricatorSavedQuery Saved query which will be executed. + * @param PhabricatorSavedQuery $saved Saved query which will be executed. * @return void */ protected function willUseSavedQuery(PhabricatorSavedQuery $saved) { @@ -215,8 +215,8 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { /** * Builds the search form using the request. * - * @param AphrontFormView Form to populate. - * @param PhabricatorSavedQuery The query from which to build the form. + * @param AphrontFormView $form Form to populate. + * @param PhabricatorSavedQuery $saved Query from which to build the form. * @return void */ public function buildSearchForm( @@ -400,7 +400,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { * Return an application URI corresponding to the results page of a query. * Normally, this is something like `/application/query/QUERYKEY/`. * - * @param string The query key to build a URI for. + * @param string $query_key The query key to build a URI for. * @return string URI where the query can be executed. * @task uri */ @@ -730,9 +730,9 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { * links to pages (like "alincoln's open revisions") without needing to make * API calls. * - * @param AphrontRequest Request to read user PHIDs from. - * @param string Key to read in the request. - * @param list Other permitted PHID types. + * @param AphrontRequest $request Request to read user PHIDs from. + * @param string $key Key to read in the request. + * @param list? $allow_types Other permitted PHID types. * @return list List of user PHIDs and selector functions. * @task read */ @@ -782,8 +782,8 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { /** * Read a list of subscribers from a request in a flexible way. * - * @param AphrontRequest Request to read PHIDs from. - * @param string Key to read in the request. + * @param AphrontRequest $request Request to read PHIDs from. + * @param string $key Key to read in the request. * @return list List of object PHIDs. * @task read */ @@ -805,9 +805,10 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { * comma-delimited forms. Objects can be specified either by PHID or by * object name. * - * @param AphrontRequest Request to read PHIDs from. - * @param string Key to read in the request. - * @param list Optional, list of permitted PHID types. + * @param AphrontRequest $request Request to read PHIDs from. + * @param string $key Key to read in the request. + * @param list? $allow_types Optional, list of permitted PHID + * types. * @return list List of object PHIDs. * * @task read @@ -853,8 +854,8 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { * This provides flexibility when constructing URIs, especially from external * sources. * - * @param AphrontRequest Request to read strings from. - * @param string Key to read in the request. + * @param AphrontRequest $request Request to read strings from. + * @param string $key Key to read in the request. * @return list List of values. */ protected function readListFromRequest( diff --git a/src/applications/search/field/PhabricatorSearchField.php b/src/applications/search/field/PhabricatorSearchField.php index 36db0523b7..7b420ac8e6 100644 --- a/src/applications/search/field/PhabricatorSearchField.php +++ b/src/applications/search/field/PhabricatorSearchField.php @@ -33,7 +33,7 @@ abstract class PhabricatorSearchField extends Phobject { * The key should be a short, unique (within a search engine) string which * does not contain any special characters. * - * @param string Unique key which identifies the field. + * @param string $key Unique key which identifies the field. * @return this * @task config */ @@ -59,7 +59,7 @@ abstract class PhabricatorSearchField extends Phobject { * * This should be a short text string, like "Reviewers" or "Colors". * - * @param string Short, human-readable field label. + * @param string $label Short, human-readable field label. * @return this * task config */ @@ -86,7 +86,7 @@ abstract class PhabricatorSearchField extends Phobject { * Engines do not need to do this explicitly; it will be done on their * behalf by the caller. * - * @param PhabricatorUser Viewer. + * @param PhabricatorUser $viewer Viewer. * @return this * @task config */ @@ -115,7 +115,7 @@ abstract class PhabricatorSearchField extends Phobject { * an alias like `authors` to let users write `&authors=alincoln` instead of * `&authorPHIDs=alincoln`. This is a little easier to use. * - * @param list List of aliases for this field. + * @param list $aliases List of aliases for this field. * @return this * @task config */ @@ -142,7 +142,7 @@ abstract class PhabricatorSearchField extends Phobject { * This can allow you to choose a more usable key for API endpoints. * If no key is provided, the main key is used. * - * @param string Alternate key for Conduit. + * @param string $conduit_key Alternate key for Conduit. * @return this * @task config */ @@ -170,7 +170,7 @@ abstract class PhabricatorSearchField extends Phobject { /** * Set a human-readable description for this field. * - * @param string Human-readable description. + * @param string $description Human-readable description. * @return this * @task config */ @@ -194,7 +194,7 @@ abstract class PhabricatorSearchField extends Phobject { /** * Hide this field from the web UI. * - * @param bool True to hide the field from the web UI. + * @param bool $is_hidden True to hide the field from the web UI. * @return this * @task config */ @@ -400,8 +400,8 @@ abstract class PhabricatorSearchField extends Phobject { * This provides flexibility when constructing URIs, especially from external * sources. * - * @param AphrontRequest Request to read strings from. - * @param string Key to read in the request. + * @param AphrontRequest $request Request to read strings from. + * @param string $key Key to read in the request. * @return list List of values. * @task utility */ diff --git a/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php index ba019ea593..3745bbf35e 100644 --- a/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php @@ -47,7 +47,7 @@ abstract class PhabricatorFulltextStorageEngine extends Phobject { /** * Update the index for an abstract document. * - * @param PhabricatorSearchAbstractDocument Document to update. + * @param PhabricatorSearchAbstractDocument $document Document to update. * @return void */ abstract public function reindexAbstractDocument( @@ -56,7 +56,7 @@ abstract class PhabricatorFulltextStorageEngine extends Phobject { /** * Execute a search query. * - * @param PhabricatorSavedQuery A query to execute. + * @param PhabricatorSavedQuery $query A query to execute. * @return list A list of matching PHIDs. */ abstract public function executeSearch(PhabricatorSavedQuery $query); diff --git a/src/applications/settings/panel/PhabricatorSettingsPanel.php b/src/applications/settings/panel/PhabricatorSettingsPanel.php index e2efd92093..ac92a134c6 100644 --- a/src/applications/settings/panel/PhabricatorSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorSettingsPanel.php @@ -222,7 +222,7 @@ abstract class PhabricatorSettingsPanel extends Phobject { * Generally, render your settings panel by returning a form, then return * a redirect when the user saves settings. * - * @param AphrontRequest Incoming request. + * @param AphrontRequest $request Incoming request. * @return wild Response to request, either as an * @{class:AphrontResponse} or something which can * be composed into a @{class:AphrontView}. @@ -234,7 +234,7 @@ abstract class PhabricatorSettingsPanel extends Phobject { /** * Get the URI for this panel. * - * @param string? Optional path to append. + * @param string? $path Optional path to append. * @return string Relative URI for the panel. * @task panel */ diff --git a/src/applications/settings/query/PhabricatorUserPreferencesQuery.php b/src/applications/settings/query/PhabricatorUserPreferencesQuery.php index 5fa9b89bdd..5795b78f27 100644 --- a/src/applications/settings/query/PhabricatorUserPreferencesQuery.php +++ b/src/applications/settings/query/PhabricatorUserPreferencesQuery.php @@ -49,7 +49,8 @@ final class PhabricatorUserPreferencesQuery * If no settings exist for a user, a new empty settings object with * appropriate defaults is returned. * - * @param bool True to generate synthetic preferences for missing users. + * @param bool $synthetic True to generate synthetic preferences for missing + * users. */ public function needSyntheticPreferences($synthetic) { $this->synthetic = $synthetic; diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php index 6cceff57de..f004361a29 100644 --- a/src/applications/settings/storage/PhabricatorUserPreferences.php +++ b/src/applications/settings/storage/PhabricatorUserPreferences.php @@ -119,7 +119,7 @@ final class PhabricatorUserPreferences /** * Load or create a preferences object for the given user. * - * @param PhabricatorUser User to load or create preferences for. + * @param PhabricatorUser $user User to load or create preferences for. */ public static function loadUserPreferences(PhabricatorUser $user) { return id(new PhabricatorUserPreferencesQuery()) @@ -134,7 +134,7 @@ final class PhabricatorUserPreferences * * If no global preferences exist, an empty preferences object is returned. * - * @param PhabricatorUser Viewing user. + * @param PhabricatorUser $viewer Viewing user. */ public static function loadGlobalPreferences(PhabricatorUser $viewer) { $global = id(new PhabricatorUserPreferencesQuery()) diff --git a/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php b/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php index 6703ed664e..6de31ff5c1 100644 --- a/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php +++ b/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php @@ -212,7 +212,7 @@ final class PhabricatorSpacesNamespaceQuery * This is intended to simplify performing a bunch of redundant checks; you * can intentionally pass any value in (including `null`). * - * @param wild + * @param wild $object * @return phid|null */ public static function getObjectSpacePHID($object) { diff --git a/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php b/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php index 3ee4b5f6e8..a36fed1c5d 100644 --- a/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php +++ b/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php @@ -18,7 +18,7 @@ final class PhabricatorSubscriptionsEditor extends PhabricatorEditor { * (or been subscribed) to the object, and will be added even if they * had previously unsubscribed. * - * @param list List of PHIDs to explicitly subscribe. + * @param list $phids List of PHIDs to explicitly subscribe. * @return this */ public function subscribeExplicit(array $phids) { @@ -32,7 +32,7 @@ final class PhabricatorSubscriptionsEditor extends PhabricatorEditor { * implicitly subscribes them (e.g., adding a comment) but it will be * suppressed if they've previously unsubscribed from the object. * - * @param list List of PHIDs to implicitly subscribe. + * @param list $phids List of PHIDs to implicitly subscribe. * @return this */ public function subscribeImplicit(array $phids) { @@ -45,7 +45,7 @@ final class PhabricatorSubscriptionsEditor extends PhabricatorEditor { * Unsubscribe PHIDs and mark them as unsubscribed, so implicit subscriptions * will not resubscribe them. * - * @param list List of PHIDs to unsubscribe. + * @param list $phids List of PHIDs to unsubscribe. * @return this */ public function unsubscribe(array $phids) { diff --git a/src/applications/subscriptions/interface/PhabricatorSubscribableInterface.php b/src/applications/subscriptions/interface/PhabricatorSubscribableInterface.php index 1af9e7107f..6528774ee8 100644 --- a/src/applications/subscriptions/interface/PhabricatorSubscribableInterface.php +++ b/src/applications/subscriptions/interface/PhabricatorSubscribableInterface.php @@ -8,7 +8,8 @@ interface PhabricatorSubscribableInterface { * irrevocably a subscriber). This will, e.g., cause the UI to render * "Automatically Subscribed" instead of "Subscribe". * - * @param PHID PHID (presumably a user) to test for automatic subscription. + * @param PHID $phid PHID (presumably a user) to test for automatic + * subscription. * @return bool True if the object/user is automatically subscribed. */ public function isAutomaticallySubscribed($phid); diff --git a/src/applications/system/engine/PhabricatorSystemActionEngine.php b/src/applications/system/engine/PhabricatorSystemActionEngine.php index 6d6f9eacfd..106ef05e48 100644 --- a/src/applications/system/engine/PhabricatorSystemActionEngine.php +++ b/src/applications/system/engine/PhabricatorSystemActionEngine.php @@ -33,9 +33,9 @@ final class PhabricatorSystemActionEngine extends Phobject { * If any actor is exceeding their rate limit, this method throws a * @{class:PhabricatorSystemActionRateLimitException}. * - * @param list List of actors. - * @param PhabricatorSystemAction Action being taken. - * @param float Score or credit, see above. + * @param list $actors List of actors. + * @param PhabricatorSystemAction $action Action being taken. + * @param float $score Score or credit, see above. * @return void */ public static function willTakeAction( @@ -174,7 +174,7 @@ final class PhabricatorSystemActionEngine extends Phobject { * Reset all action counts for actions taken by some set of actors in the * previous action window. * - * @param list Actors to reset counts for. + * @param list $actors Actors to reset counts for. * @return int Number of actions cleared. */ public static function resetActions(array $actors) { diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 4cc9914fe2..6b3aae2790 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -343,7 +343,7 @@ abstract class PhabricatorEditEngine * Set default placeholder plain text in the comment textarea of the engine. * To be overwritten by conditions defined in the child EditEngine class. * - * @param object Object in which the comment textarea is displayed. + * @param object $object Object in which the comment textarea is displayed. * @return string Placeholder text to display in the comment textarea. * @task text */ @@ -678,7 +678,7 @@ abstract class PhabricatorEditEngine * Initialize a new object for object creation via Conduit. * * @return object Newly initialized object. - * @param list Raw transactions. + * @param list $raw_xactions Raw transactions. * @task load */ protected function newEditableObjectFromConduit(array $raw_xactions) { @@ -698,7 +698,7 @@ abstract class PhabricatorEditEngine /** * Flag this workflow as a create or edit. * - * @param bool True if this is a create workflow. + * @param bool $is_create True if this is a create workflow. * @return this * @task load */ @@ -712,9 +712,9 @@ abstract class PhabricatorEditEngine * Try to load an object by ID, PHID, or monogram. This is done primarily * to make Conduit a little easier to use. * - * @param wild ID, PHID, or monogram. - * @param list List of required capability constants, or omit for - * defaults. + * @param wild $identifier ID, PHID, or monogram. + * @param list? $capabilities List of required capability constants, + * or omit for defaults. * @return object Corresponding editable object. * @task load */ @@ -792,9 +792,9 @@ abstract class PhabricatorEditEngine /** * Load an object by ID. * - * @param int Object ID. - * @param list List of required capability constants, or omit for - * defaults. + * @param int $id Object ID. + * @param list? $capabilities List of required capability constants, + * or omit for defaults. * @return object|null Object, or null if no such object exists. * @task load */ @@ -809,9 +809,9 @@ abstract class PhabricatorEditEngine /** * Load an object by PHID. * - * @param phid Object PHID. - * @param list List of required capability constants, or omit for - * defaults. + * @param phid $phid Object PHID. + * @param list? $capabilities List of required capability constants, + * or omit for defaults. * @return object|null Object, or null if no such object exists. * @task load */ @@ -826,9 +826,9 @@ abstract class PhabricatorEditEngine /** * Load an object given a configured query. * - * @param PhabricatorPolicyAwareQuery Configured query. - * @param list List of required capability constants, or omit for - * defaults. + * @param PhabricatorPolicyAwareQuery $query Configured query. + * @param list? $capabilities List of required capability constants, + * or omit for defaults. * @return object|null Object, or null if no such object exists. * @task load */ @@ -860,7 +860,7 @@ abstract class PhabricatorEditEngine /** * Verify that an object is appropriate for editing. * - * @param wild Loaded value. + * @param wild $object Loaded value. * @return void * @task load */ @@ -1767,7 +1767,7 @@ abstract class PhabricatorEditEngine /** * Respond to a request for documentation on HTTP parameters. * - * @param object Editable object. + * @param object $object Editable object. * @return AphrontResponse Response object. * @task http */ @@ -2244,10 +2244,10 @@ abstract class PhabricatorEditEngine * Generate transactions which can be applied from edit actions in a Conduit * request. * - * @param ConduitAPIRequest The request. - * @param list Raw conduit transactions. - * @param list Supported edit types. - * @param PhabricatorApplicationTransaction Template transaction. + * @param ConduitAPIRequest $request The request. + * @param list $xactions Raw conduit transactions. + * @param list $types Supported edit types. + * @param PhabricatorApplicationTransaction $template Template transaction. * @return list Generated transactions. * @task conduit */ diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php index 4c72d63214..4e17bf006c 100644 --- a/src/applications/transactions/editfield/PhabricatorEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEditField.php @@ -573,8 +573,8 @@ abstract class PhabricatorEditField extends Phobject { * Most fields do not need to store these values or deal with initial value * handling. * - * @param AphrontRequest Request to read from. - * @param string Key to read. + * @param AphrontRequest $request Request to read from. + * @param string $key Key to read. * @return wild Value read from request. */ protected function getInitialValueFromSubmit(AphrontRequest $request, $key) { diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index b643279bc8..b759e58c1d 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -140,7 +140,8 @@ abstract class PhabricatorApplicationTransactionEditor * nothing (e.g., empty comment with a status change that has already been * performed by another user). * - * @param bool True to drop transactions without effect and continue. + * @param bool $continue True to drop transactions without effect and + * continue. * @return this */ public function setContinueOnNoEffect($continue) { @@ -168,8 +169,8 @@ abstract class PhabricatorApplicationTransactionEditor * (like the priority, batch, and merge editors in Maniphest), these * operations can continue to function even if an object is outdated. * - * @param bool True to continue when transactions don't completely satisfy - * all required fields. + * @param bool $continue_on_missing_fields True to continue when transactions + * don't completely satisfy all required fields. * @return this */ public function setContinueOnMissingFields($continue_on_missing_fields) { @@ -2853,11 +2854,11 @@ abstract class PhabricatorApplicationTransactionEditor * missing, by detecting that the object has no field value and there is no * transaction which sets one. * - * @param PhabricatorLiskDAO Object being edited. - * @param string Transaction type to validate. - * @param list Transactions of given type, - * which may be empty if the edit does not apply any transactions of the - * given type. + * @param PhabricatorLiskDAO $object Object being edited. + * @param string $type Transaction type to validate. + * @param list $xactions Transactions of + * given type, which may be empty if the edit does not apply any + * transactions of the given type. * @return list List of * validation errors. */ @@ -3277,9 +3278,9 @@ abstract class PhabricatorApplicationTransactionEditor * This will return `true` if the net effect of the object and transactions * is an empty field. * - * @param wild Current field value. - * @param list Transactions editing the - * field. + * @param wild $field_value Current field value. + * @param list $xactions Transactions + * editing the field. * @return bool True if the field will be an empty text field after edits. */ protected function validateIsEmptyTextField($field_value, array $xactions) { @@ -4600,7 +4601,8 @@ abstract class PhabricatorApplicationTransactionEditor * * This method is used to load state when running worker operations. * - * @param dict Editor state, from @{method:getWorkerState}. + * @param dict $state Editor state, from + @{method:getWorkerState}. * @return this * @task workers */ @@ -4626,7 +4628,7 @@ abstract class PhabricatorApplicationTransactionEditor * Hook; set custom properties on the editor from data emitted by * @{method:getCustomWorkerState}. * - * @param dict Custom state, + * @param dict $state Custom state, * from @{method:getCustomWorkerState}. * @return this * @task workers @@ -4674,8 +4676,8 @@ abstract class PhabricatorApplicationTransactionEditor * * See @{method:getCustomWorkerStateEncoding}. * - * @param map Map of values to encode. - * @param map Map of encodings to apply. + * @param map $state Map of values to encode. + * @param map $encodings Map of encodings to apply. * @return map Map of encoded values. * @task workers */ @@ -4720,8 +4722,8 @@ abstract class PhabricatorApplicationTransactionEditor * * See @{method:getCustomWorkerStateEncoding}. * - * @param map Map of encoded values. - * @param map Map of encodings. + * @param map $state Map of encoded values. + * @param map $encodings Map of encodings. * @return map Map of decoded values. * @task workers */ @@ -4759,7 +4761,7 @@ abstract class PhabricatorApplicationTransactionEditor * If the list of PHIDs include mutually exclusive projects, remove the * conflicting projects. * - * @param list List of project PHIDs. + * @param list $phids List of project PHIDs. * @return list List with conflicts removed. */ private function applyProjectConflictRules(array $phids) { diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index bfaa252d40..5c70f59a89 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -1696,7 +1696,7 @@ abstract class PhabricatorApplicationTransaction * Should this transaction be visually grouped with an existing transaction * group? * - * @param list List of transactions. + * @param list $group List of transactions. * @return bool True to display in a group with the other transactions. */ public function shouldDisplayGroupWith(array $group) { diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index e2f0a02239..1d449ab868 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -428,8 +428,9 @@ abstract class PhabricatorModularTransactionType * additional capability or policy requirement above and beyond CAN_EDIT, it * is usually better implemented as a validation check. * - * @param object Object being edited. - * @param PhabricatorApplicationTransaction Transaction being applied. + * @param object $object Object being edited. + * @param PhabricatorApplicationTransaction $xaction Transaction being + * applied. * @return null|const|list A capability constant (or list of * capability constants) which the actor must have on the object. You can * return `null` as a shorthand for "no capabilities are required". diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php index 308a8bfe1a..11c94d1933 100644 --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php @@ -212,7 +212,7 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject { * For datasources backed by database objects, this is often much less * efficient than filtering at the query level. * - * @param list List of typeahead results. + * @param list $results List of typeahead results. * @return list Filtered results. */ protected function filterResultsAgainstTokens(array $results) { diff --git a/src/infrastructure/cache/PhutilInRequestKeyValueCache.php b/src/infrastructure/cache/PhutilInRequestKeyValueCache.php index 2c303e860f..19edc81e2a 100644 --- a/src/infrastructure/cache/PhutilInRequestKeyValueCache.php +++ b/src/infrastructure/cache/PhutilInRequestKeyValueCache.php @@ -26,7 +26,7 @@ final class PhutilInRequestKeyValueCache extends PhutilKeyValueCache { * When too many keys are inserted, the oldest keys are removed from the * cache. Setting a limit of `0` disables the cache. * - * @param int Maximum number of items to store in the cache. + * @param int $limit Maximum number of items to store in the cache. * @return this */ public function setLimit($limit) { diff --git a/src/infrastructure/cache/PhutilKeyValueCache.php b/src/infrastructure/cache/PhutilKeyValueCache.php index 8260df1ef2..e65eb9675c 100644 --- a/src/infrastructure/cache/PhutilKeyValueCache.php +++ b/src/infrastructure/cache/PhutilKeyValueCache.php @@ -30,9 +30,9 @@ abstract class PhutilKeyValueCache extends Phobject { * Get a single key from cache. See @{method:getKeys} to get multiple keys at * once. * - * @param string Key to retrieve. - * @param wild Optional value to return if the key is not found. By - * default, returns null. + * @param string $key Key to retrieve. + * @param wild? $default Optional value to return if the key is not + * found. By default, returns null. * @return wild Cache value (on cache hit) or default value (on cache * miss). * @task kvimpl @@ -49,9 +49,9 @@ abstract class PhutilKeyValueCache extends Phobject { * * See @{method:setKeys} for a description of TTLs. * - * @param string Key to set. - * @param wild Value to set. - * @param int|null Optional TTL. + * @param string $key Key to set. + * @param wild $value Value to set. + * @param int|null? $ttl Optional TTL. * @return this * @task kvimpl */ @@ -64,7 +64,7 @@ abstract class PhutilKeyValueCache extends Phobject { * Delete a key from the cache. See @{method:deleteKeys} to delete multiple * keys at once. * - * @param string Key to delete. + * @param string $key Key to delete. * @return this * @task kvimpl */ @@ -76,7 +76,7 @@ abstract class PhutilKeyValueCache extends Phobject { /** * Get data from the cache. * - * @param list List of cache keys to retrieve. + * @param list $keys List of cache keys to retrieve. * @return dict Dictionary of keys that were found in the * cache. Keys not present in the cache are * omitted, so you can detect a cache miss. @@ -92,8 +92,8 @@ abstract class PhutilKeyValueCache extends Phobject { * after a specified number of seconds. By default, there is no expiration * policy and data will persist in cache indefinitely. * - * @param dict Map of cache keys to values. - * @param int|null TTL for cache keys, in seconds. + * @param dict $keys Map of cache keys to values. + * @param int|null? $ttl TTL for cache keys, in seconds. * @return this * @task kvimpl */ @@ -103,7 +103,7 @@ abstract class PhutilKeyValueCache extends Phobject { /** * Delete a list of keys from the cache. * - * @param list List of keys to delete. + * @param list $keys List of keys to delete. * @return this * @task kvimpl */ diff --git a/src/infrastructure/cache/PhutilKeyValueCacheProfiler.php b/src/infrastructure/cache/PhutilKeyValueCacheProfiler.php index 9e2271b530..f5d6979c92 100644 --- a/src/infrastructure/cache/PhutilKeyValueCacheProfiler.php +++ b/src/infrastructure/cache/PhutilKeyValueCacheProfiler.php @@ -17,7 +17,7 @@ final class PhutilKeyValueCacheProfiler extends PhutilKeyValueCacheProxy { /** * Set a profiler for cache operations. * - * @param PhutilServiceProfiler Service profiler. + * @param PhutilServiceProfiler $profiler Service profiler. * @return this * @task kvimpl */ diff --git a/src/infrastructure/cache/PhutilKeyValueCacheStack.php b/src/infrastructure/cache/PhutilKeyValueCacheStack.php index 76ea643be4..eab1ff86c5 100644 --- a/src/infrastructure/cache/PhutilKeyValueCacheStack.php +++ b/src/infrastructure/cache/PhutilKeyValueCacheStack.php @@ -38,7 +38,8 @@ final class PhutilKeyValueCacheStack extends PhutilKeyValueCache { /** * Set the caches which comprise this stack. * - * @param list Ordered list of key-value caches. + * @param list $caches Ordered list of key-value + * caches. * @return this * @task config */ @@ -62,7 +63,7 @@ final class PhutilKeyValueCacheStack extends PhutilKeyValueCache { * // TTL does not persist; this will use no TTL. * $stack->getKey('hedgehog'); * - * @param int TTL in seconds. + * @param int $ttl TTL in seconds. * @return this * * @task config diff --git a/src/infrastructure/cache/PhutilMemcacheKeyValueCache.php b/src/infrastructure/cache/PhutilMemcacheKeyValueCache.php index d095eff706..918971ac28 100644 --- a/src/infrastructure/cache/PhutilMemcacheKeyValueCache.php +++ b/src/infrastructure/cache/PhutilMemcacheKeyValueCache.php @@ -100,7 +100,7 @@ final class PhutilMemcacheKeyValueCache extends PhutilKeyValueCache { * ), * )); * - * @param list List of server specifications. + * @param list $servers List of server specifications. * @return this * @task memcache */ diff --git a/src/infrastructure/contentsource/PhabricatorContentSource.php b/src/infrastructure/contentsource/PhabricatorContentSource.php index de36f50b1f..19f954e0d8 100644 --- a/src/infrastructure/contentsource/PhabricatorContentSource.php +++ b/src/infrastructure/contentsource/PhabricatorContentSource.php @@ -22,10 +22,10 @@ abstract class PhabricatorContentSource extends Phobject { /** * Construct a new content source object. * - * @param const The source type constant to build a source for. - * @param array Source parameters. - * @param bool True to suppress errors and force construction of a source - * even if the source type is not valid. + * @param const $source The source type constant to build a source for. + * @param array? $params Source parameters. + * @param bool? $force True to suppress errors and force construction of a + * source even if the source type is not valid. * @return PhabricatorContentSource New source object. */ final public static function newForSource( diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php index 774dbb6630..c513b59c82 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomField.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php @@ -243,7 +243,7 @@ abstract class PhabricatorCustomField extends Phobject { * For general implementations, the general field implementation can return * multiple field instances here. * - * @param object The object to create fields for. + * @param object $object The object to create fields for. * @return list List of fields. * @task core */ @@ -382,7 +382,7 @@ abstract class PhabricatorCustomField extends Phobject { * Set the proxy implementation for this field. See @{method:canSetProxy} for * discussion of field proxies. * - * @param PhabricatorCustomField Field implementation. + * @param PhabricatorCustomField $proxy Field implementation. * @return this * @task proxy */ @@ -421,7 +421,8 @@ abstract class PhabricatorCustomField extends Phobject { /** * Sets the object this field belongs to. * - * @param PhabricatorCustomFieldInterface The object this field belongs to. + * @param PhabricatorCustomFieldInterface $object The object this field + * belongs to. * @return this * @task context */ @@ -440,7 +441,8 @@ abstract class PhabricatorCustomField extends Phobject { /** * Read object data into local field storage, if applicable. * - * @param PhabricatorCustomFieldInterface The object this field belongs to. + * @param PhabricatorCustomFieldInterface $object The object this field + * belongs to. * @return this * @task context */ @@ -607,7 +609,7 @@ abstract class PhabricatorCustomField extends Phobject { * need to undo whatever serialization you applied in * @{method:getValueForStorage}. * - * @param string|null Serialized field representation (from + * @param string|null $value Serialized field representation (from * @{method:getValueForStorage}) or null if no value has * ever been stored. * @return this @@ -729,7 +731,7 @@ abstract class PhabricatorCustomField extends Phobject { /** * Build and populate storage for a string index. * - * @param string String to index. + * @param string $value String to index. * @return PhabricatorCustomFieldStringIndexStorage Populated storage. * @task appsearch */ @@ -748,7 +750,7 @@ abstract class PhabricatorCustomField extends Phobject { /** * Build and populate storage for a numeric index. * - * @param string Numeric value to index. + * @param string $value Numeric value to index. * @return PhabricatorCustomFieldNumericIndexStorage Populated storage. * @task appsearch */ @@ -767,8 +769,9 @@ abstract class PhabricatorCustomField extends Phobject { * Read a query value from a request, for storage in a saved query. Normally, * this method should, e.g., read a string out of the request. * - * @param PhabricatorApplicationSearchEngine Engine building the query. - * @param AphrontRequest Request to read from. + * @param PhabricatorApplicationSearchEngine $engine Engine building the + * query. + * @param AphrontRequest $request Request to read from. * @return wild * @task appsearch */ @@ -789,9 +792,10 @@ abstract class PhabricatorCustomField extends Phobject { * use `with...()` methods to apply filters or other constraints to the * query. * - * @param PhabricatorApplicationSearchEngine Engine executing the query. - * @param PhabricatorCursorPagedPolicyAwareQuery Query to constrain. - * @param wild Constraint provided by the user. + * @param PhabricatorApplicationSearchEngine $engine Engine executing the + * query. + * @param PhabricatorCursorPagedPolicyAwareQuery $query Query to constrain. + * @param wild $value Constraint provided by the user. * @return void * @task appsearch */ @@ -812,9 +816,10 @@ abstract class PhabricatorCustomField extends Phobject { /** * Append search controls to the interface. * - * @param PhabricatorApplicationSearchEngine Engine constructing the form. - * @param AphrontFormView The form to update. - * @param wild Value from the saved query. + * @param PhabricatorApplicationSearchEngine $engine Engine constructing the + * form. + * @param AphrontFormView $form The form to update. + * @param wild $value Value from the saved query. * @return void * @task appsearch */ @@ -1000,12 +1005,12 @@ abstract class PhabricatorCustomField extends Phobject { * when a transaction would set a field to an invalid value, or when a field * is required but no transactions provide value. * - * @param PhabricatorLiskDAO Editor applying the transactions. - * @param string Transaction type. This type is always + * @param PhabricatorLiskDAO $editor Editor applying the transactions. + * @param string $type Transaction type. This type is always * `PhabricatorTransactions::TYPE_CUSTOMFIELD`, it is provided for * convenience when constructing exceptions. - * @param list Transactions being applied, - * which may be empty if this field is not being edited. + * @param list $xactions Transactions + * being applied, which may be empty if this field is not being edited. * @return list Validation * errors. * @@ -1566,7 +1571,7 @@ abstract class PhabricatorCustomField extends Phobject { /** * Get the Herald value type for the given condition. * - * @param const Herald condition constant. + * @param const $condition Herald condition constant. * @return const|null Herald value type, or null to use the default. * @task herald */ diff --git a/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php b/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php index 369bed2297..e5f5c1698f 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php @@ -46,7 +46,8 @@ final class PhabricatorCustomFieldList extends Phobject { /** * Read stored values for all fields which support storage. * - * @param PhabricatorCustomFieldInterface Object to read field values for. + * @param PhabricatorCustomFieldInterface $object Object to read field values + * for. * @return void */ public function readFieldsFromStorage( diff --git a/src/infrastructure/customfield/storage/PhabricatorCustomFieldStorage.php b/src/infrastructure/customfield/storage/PhabricatorCustomFieldStorage.php index cf0140a38a..111a16c968 100644 --- a/src/infrastructure/customfield/storage/PhabricatorCustomFieldStorage.php +++ b/src/infrastructure/customfield/storage/PhabricatorCustomFieldStorage.php @@ -44,7 +44,7 @@ abstract class PhabricatorCustomFieldStorage * The keys in the result should correspond to the keys in the input. The * fields in the list may belong to different objects. * - * @param map Map of fields. + * @param map $fields Map of fields. * @return map Map of available field data. */ final public function loadStorageSourceData(array $fields) { diff --git a/src/infrastructure/daemon/PhabricatorDaemon.php b/src/infrastructure/daemon/PhabricatorDaemon.php index f42a59f134..0e203566f8 100644 --- a/src/infrastructure/daemon/PhabricatorDaemon.php +++ b/src/infrastructure/daemon/PhabricatorDaemon.php @@ -24,7 +24,7 @@ abstract class PhabricatorDaemon extends PhutilDaemon { * Format a command so it executes as the daemon user, if a daemon user is * defined. This wraps the provided command in `sudo -u ...`, roughly. * - * @param PhutilCommandString Command to execute. + * @param PhutilCommandString $command Command to execute. * @return PhutilCommandString `sudo` version of the command. */ public static function sudoCommandAsDaemonUser($command) { diff --git a/src/infrastructure/daemon/PhutilDaemonHandle.php b/src/infrastructure/daemon/PhutilDaemonHandle.php index 3792c3b773..a44ba7b7cb 100644 --- a/src/infrastructure/daemon/PhutilDaemonHandle.php +++ b/src/infrastructure/daemon/PhutilDaemonHandle.php @@ -328,8 +328,8 @@ final class PhutilDaemonHandle extends Phobject { /** * Dispatch an event to event listeners. * - * @param string Event type. - * @param dict Event parameters. + * @param string $type Event type. + * @param dict? $params Event parameters. * @return void */ private function dispatchEvent($type, array $params = array()) { diff --git a/src/infrastructure/daemon/PhutilDaemonOverseerModule.php b/src/infrastructure/daemon/PhutilDaemonOverseerModule.php index 3e2cdaad3e..9c746d17a1 100644 --- a/src/infrastructure/daemon/PhutilDaemonOverseerModule.php +++ b/src/infrastructure/daemon/PhutilDaemonOverseerModule.php @@ -47,8 +47,8 @@ abstract class PhutilDaemonOverseerModule extends Phobject { * return; * } * - * @param string Throttle key. - * @param float Duration in seconds. + * @param string $name Throttle key. + * @param float $duration Duration in seconds. * @return bool True to throttle the check. */ protected function shouldThrottle($name, $duration) { diff --git a/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php index 9561f3d18a..736f17f81b 100644 --- a/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php +++ b/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php @@ -122,7 +122,7 @@ final class PhabricatorTriggerDaemon * Process all of the triggers which have been updated since the last time * the daemon ran, scheduling them into the event table. * - * @param int Cursor for the next version update to process. + * @param int $cursor Cursor for the next version update to process. * @return void */ private function scheduleTriggers($cursor) { @@ -326,7 +326,7 @@ final class PhabricatorTriggerDaemon /** * Run the garbage collector for up to a specified number of seconds. * - * @param int Number of seconds the GC may run for. + * @param int $duration Number of seconds the GC may run for. * @return int Number of seconds remaining in the time budget. * @task garbage */ diff --git a/src/infrastructure/daemon/workers/PhabricatorWorker.php b/src/infrastructure/daemon/workers/PhabricatorWorker.php index 2b1e650485..69815557ec 100644 --- a/src/infrastructure/daemon/workers/PhabricatorWorker.php +++ b/src/infrastructure/daemon/workers/PhabricatorWorker.php @@ -68,12 +68,12 @@ abstract class PhabricatorWorker extends Phobject { * retrying. For most tasks you can leave this at `null`, which will give you * a short default retry period (currently 60 seconds). * - * @param PhabricatorWorkerTask The task itself. This object is probably - * useful mostly to examine the failure count - * if you want to implement staggered retries, - * or to examine the execution exception if - * you want to react to different failures in - * different ways. + * @param PhabricatorWorkerTask $task The task itself. This object is + * probably useful mostly to examine the + * failure count if you want to implement + * staggered retries, or to examine the + * execution exception if you want to react to + * different failures in different ways. * @return int|null Number of seconds to wait between retries, * or null for a default retry period * (currently 60 seconds). @@ -230,9 +230,9 @@ abstract class PhabricatorWorker extends Phobject { * * The followup task will be queued only if this task completes cleanly. * - * @param string Task class to queue. - * @param array Data for the followup task. - * @param array Options for the followup task. + * @param string $class Task class to queue. + * @param array $data Data for the followup task. + * @param array? $options Options for the followup task. * @return this */ final protected function queueTask( @@ -261,7 +261,7 @@ abstract class PhabricatorWorker extends Phobject { * this method to force the queue to flush before failing (for example, if * you are using queues to improve locking behavior). * - * @param map Optional default options. + * @param map? $defaults Optional default options. */ final public function flushTaskQueue($defaults = array()) { foreach ($this->getQueuedTasks() as $task) { @@ -286,7 +286,7 @@ abstract class PhabricatorWorker extends Phobject { * This method does not provide any assurances about when these tasks will * execute, or even guarantee that it will have any effect at all. * - * @param list List of task IDs to try to awaken. + * @param list $ids List of task IDs to try to awaken. * @return void */ final public static function awakenTaskIDs(array $ids) { diff --git a/src/infrastructure/daemon/workers/action/PhabricatorTriggerAction.php b/src/infrastructure/daemon/workers/action/PhabricatorTriggerAction.php index 2390a284ba..329c75dfc6 100644 --- a/src/infrastructure/daemon/workers/action/PhabricatorTriggerAction.php +++ b/src/infrastructure/daemon/workers/action/PhabricatorTriggerAction.php @@ -28,7 +28,7 @@ abstract class PhabricatorTriggerAction extends Phobject { /** * Validate action configuration. * - * @param map Map of action properties. + * @param map $properties Map of action properties. * @return void */ abstract public function validateProperties(array $properties); @@ -62,10 +62,10 @@ abstract class PhabricatorTriggerAction extends Phobject { * may be simplest to pass the trigger time to the task and then make the * decision to discard the action there. * - * @param int|null Last time the event occurred, or null if it has never - * triggered before. - * @param int The scheduled time for the current action. This may be - * significantly different from the current time. + * @param int|null $last_epoch Last time the event occurred, or null if it + * has never triggered before. + * @param int $this_epoch The scheduled time for the current action. This + * may be significantly different from the current time. * @return void */ abstract public function execute($last_epoch, $this_epoch); diff --git a/src/infrastructure/daemon/workers/clock/PhabricatorTriggerClock.php b/src/infrastructure/daemon/workers/clock/PhabricatorTriggerClock.php index 400e6fad8c..b33ea0ade3 100644 --- a/src/infrastructure/daemon/workers/clock/PhabricatorTriggerClock.php +++ b/src/infrastructure/daemon/workers/clock/PhabricatorTriggerClock.php @@ -32,7 +32,7 @@ abstract class PhabricatorTriggerClock extends Phobject { /** * Validate clock configuration. * - * @param map Map of clock properties. + * @param map $properties Map of clock properties. * @return void */ abstract public function validateProperties(array $properties); @@ -64,9 +64,10 @@ abstract class PhabricatorTriggerClock extends Phobject { * week to 3 minutes from now, the clock may reschedule the notification to * occur 12 minutes ago. This will cause it to execute immediately. * - * @param int|null Last time the event occurred, or null if it has never - * triggered before. - * @param bool True if this is a reschedule after a successful trigger. + * @param int|null $last_epoch Last time the event occurred, or null if it + * has never triggered before. + * @param bool $is_reschedule True if this is a reschedule after a successful + * trigger. * @return int|null Next event, or null to decline to reschedule. */ abstract public function getNextEventEpoch($last_epoch, $is_reschedule); diff --git a/src/infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php b/src/infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php index 0163143ae7..697d89a168 100644 --- a/src/infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php +++ b/src/infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php @@ -30,7 +30,7 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery { * This can be used to show which tasks are coming up next without altering * the queue's behavior. * - * @param bool True to skip the lease acquisition step. + * @param bool $skip True to skip the lease acquisition step. */ public function setSkipLease($skip) { $this->skipLease = $skip; @@ -58,8 +58,8 @@ final class PhabricatorWorkerLeaseQuery extends PhabricatorQuery { * leasing using @{method:setSkipLease}. These options are intended for use * when displaying task status information. * - * @param mixed `true` to select only leased tasks, `false` to select only - * unleased tasks (default), or `null` to select both. + * @param mixed $leased `true` to select only leased tasks, `false` to select + * only unleased tasks (default), or `null` to select both. * @return this */ public function withLeasedTasks($leased) { diff --git a/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php b/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php index 8ca12d60e4..fa2f04f5a1 100644 --- a/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php +++ b/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php @@ -59,7 +59,7 @@ final class PhabricatorWorkerTriggerQuery * triggers which have been scheduled to execute. You should not use this * ordering when querying for specific triggers, e.g. by ID or PHID. * - * @param const Result order. + * @param const $order Result order. * @return this */ public function setOrder($order) { diff --git a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerTrigger.php b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerTrigger.php index 7b9a24d7e0..70a6c6d0f5 100644 --- a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerTrigger.php +++ b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerTrigger.php @@ -67,9 +67,10 @@ final class PhabricatorWorkerTrigger * is changed (usually because of an application edit). The `$is_reschedule` * parameter distinguishes between these cases. * - * @param int|null Epoch of the most recent successful event execution. - * @param bool `true` if we're trying to reschedule the event after - * execution; `false` if this is in response to a trigger update. + * @param int|null $last_epoch Epoch of the most recent successful event + * execution. + * @param bool $is_reschedule `true` if we're trying to reschedule the event + * after execution; `false` if this is in response to a trigger update. * @return int|null Return an epoch to schedule the next event execution, * or `null` to stop the event from executing again. */ @@ -81,10 +82,10 @@ final class PhabricatorWorkerTrigger /** * Execute the event. * - * @param int|null Epoch of previous execution, or null if this is the first - * execution. - * @param int Scheduled epoch of this execution. This may not be the same - * as the current time. + * @param int|null $last_event Epoch of previous execution, or null if this + * is the first execution. + * @param int $this_event Scheduled epoch of this execution. This may not be + * the same as the current time. * @return void */ public function executeTrigger($last_event, $this_event) { diff --git a/src/infrastructure/diff/PhabricatorDifferenceEngine.php b/src/infrastructure/diff/PhabricatorDifferenceEngine.php index 35aa63a1b8..3326a939d1 100644 --- a/src/infrastructure/diff/PhabricatorDifferenceEngine.php +++ b/src/infrastructure/diff/PhabricatorDifferenceEngine.php @@ -21,7 +21,7 @@ final class PhabricatorDifferenceEngine extends Phobject { /** * Set the name to identify the old file with. Primarily cosmetic. * - * @param string Old file name. + * @param string $old_name Old file name. * @return this * @task config */ @@ -34,7 +34,7 @@ final class PhabricatorDifferenceEngine extends Phobject { /** * Set the name to identify the new file with. Primarily cosmetic. * - * @param string New file name. + * @param string $new_name New file name. * @return this * @task config */ @@ -62,8 +62,8 @@ final class PhabricatorDifferenceEngine extends Phobject { * @{method:generateChangesetFromFileContent}, but may be useful if you need * to use a custom parser configuration, as with Diffusion. * - * @param string Entire previous file content. - * @param string Entire current file content. + * @param string $old Entire previous file content. + * @param string $new Entire current file content. * @return string Raw diff between the two files. * @task diff */ @@ -132,8 +132,8 @@ final class PhabricatorDifferenceEngine extends Phobject { * principally useful because you can feed the output to * @{class:DifferentialChangesetParser} in order to render it. * - * @param string Entire previous file content. - * @param string Entire current file content. + * @param string $old Entire previous file content. + * @param string $new Entire current file content. * @return @{class:DifferentialChangeset} Synthetic changeset. * @task diff */ diff --git a/src/infrastructure/edges/editor/PhabricatorEdgeEditor.php b/src/infrastructure/edges/editor/PhabricatorEdgeEditor.php index d019c7d0fe..8ab8ddce58 100644 --- a/src/infrastructure/edges/editor/PhabricatorEdgeEditor.php +++ b/src/infrastructure/edges/editor/PhabricatorEdgeEditor.php @@ -45,10 +45,10 @@ final class PhabricatorEdgeEditor extends Phobject { * - `inverse_data` Optional, data to write on the inverse edge. If not * provided, `data` will be written. * - * @param phid Source object PHID. - * @param const Edge type constant. - * @param phid Destination object PHID. - * @param map Options map (see documentation). + * @param phid $src Source object PHID. + * @param const $type Edge type constant. + * @param phid $dst Destination object PHID. + * @param map? $options Options map (see documentation). * @return this * * @task edit @@ -67,9 +67,9 @@ final class PhabricatorEdgeEditor extends Phobject { * will be ignored. Edges are added after edges are removed, so the effect of * a remove plus an add is to overwrite. * - * @param phid Source object PHID. - * @param const Edge type constant. - * @param phid Destination object PHID. + * @param phid $src Source object PHID. + * @param const $type Edge type constant. + * @param phid $dst Destination object PHID. * @return this * * @task edit diff --git a/src/infrastructure/edges/query/PhabricatorEdgeQuery.php b/src/infrastructure/edges/query/PhabricatorEdgeQuery.php index f63e827431..eeb6123abf 100644 --- a/src/infrastructure/edges/query/PhabricatorEdgeQuery.php +++ b/src/infrastructure/edges/query/PhabricatorEdgeQuery.php @@ -40,7 +40,7 @@ final class PhabricatorEdgeQuery extends PhabricatorQuery { * Find edges originating at one or more source PHIDs. You MUST provide this * to execute an edge query. * - * @param list List of source PHIDs. + * @param list $source_phids List of source PHIDs. * @return this * * @task config @@ -61,7 +61,7 @@ final class PhabricatorEdgeQuery extends PhabricatorQuery { /** * Find edges terminating at one or more destination PHIDs. * - * @param list List of destination PHIDs. + * @param list $dest_phids List of destination PHIDs. * @return this * */ @@ -74,7 +74,7 @@ final class PhabricatorEdgeQuery extends PhabricatorQuery { /** * Find edges of specific types. * - * @param list List of PhabricatorEdgeConfig type constants. + * @param list $types List of PhabricatorEdgeConfig type constants. * @return this * * @task config @@ -88,7 +88,7 @@ final class PhabricatorEdgeQuery extends PhabricatorQuery { /** * Configure the order edge results are returned in. * - * @param const Order constant. + * @param const $order Order constant. * @return this * * @task config @@ -102,7 +102,7 @@ final class PhabricatorEdgeQuery extends PhabricatorQuery { /** * When loading edges, also load edge data. * - * @param bool True to load edge data. + * @param bool $need True to load edge data. * @return this * * @task config @@ -121,8 +121,8 @@ final class PhabricatorEdgeQuery extends PhabricatorQuery { * edge type. Equivalent to building a full query, but simplifies a common * use case. * - * @param phid Source PHID. - * @param const Edge type. + * @param phid $src_phid Source PHID. + * @param const $edge_type Edge type. * @return list List of destination PHIDs. */ public static function loadDestinationPHIDs($src_phid, $edge_type) { @@ -139,9 +139,9 @@ final class PhabricatorEdgeQuery extends PhabricatorQuery { * if the edge does not exist or does not have metadata. Builds * and immediately executes a full query. * - * @param phid Source PHID. - * @param const Edge type. - * @param phid Destination PHID. + * @param phid $src_phid Source PHID. + * @param const $edge_type Edge type. + * @param phid $dest_phid Destination PHID. * @return wild Edge annotation (or null). */ public static function loadSingleEdgeData($src_phid, $edge_type, $dest_phid) { @@ -256,8 +256,8 @@ final class PhabricatorEdgeQuery extends PhabricatorQuery { * $object->attachHandles(array_select_keys($handles, $dst_phids)); * } * - * @param list? List of PHIDs to select, or empty to select all. - * @param list? List of edge types to select, or empty to select all. + * @param list? $src_phids List of PHIDs to select, or empty to select all. + * @param list? $types List of edge types to select, or empty to select all. * @return list List of matching destination PHIDs. */ public function getDestinationPHIDs( diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php index a471874e43..3311869f23 100644 --- a/src/infrastructure/env/PhabricatorEnv.php +++ b/src/infrastructure/env/PhabricatorEnv.php @@ -638,7 +638,7 @@ final class PhabricatorEnv extends Phobject { * NOTE: This method is generally intended to reject URIs which it may be * unsafe to put in an "href" link attribute. * - * @param string URI to test. + * @param string $uri URI to test. * @return bool True if the URI identifies a web resource. * @task uri */ @@ -654,7 +654,7 @@ final class PhabricatorEnv extends Phobject { * NOTE: This method is generally intended to reject URIs which it may be * unsafe to issue a "Location:" redirect to. * - * @param string URI to test. + * @param string $uri URI to test. * @return bool True if the URI identifies a local page. * @task uri */ @@ -696,7 +696,7 @@ final class PhabricatorEnv extends Phobject { /** * Detect if a URI identifies some valid linkable remote resource. * - * @param string URI to test. + * @param string $uri URI to test. * @return bool True if a URI identifies a remote resource with an allowed * protocol. * @task uri @@ -718,7 +718,7 @@ final class PhabricatorEnv extends Phobject { * A valid linkable remote resource can be safely linked or redirected to. * This is primarily a protocol whitelist check. * - * @param string URI to test. + * @param string $raw_uri URI to test. * @return void * @task uri */ @@ -758,8 +758,8 @@ final class PhabricatorEnv extends Phobject { /** * Detect if a URI identifies a valid fetchable remote resource. * - * @param string URI to test. - * @param list Allowed protocols. + * @param string $uri URI to test. + * @param list $protocols Allowed protocols. * @return bool True if the URI is a valid fetchable remote resource. * @task uri */ @@ -781,8 +781,8 @@ final class PhabricatorEnv extends Phobject { * originating on this server. This is a primarily an address check against * the outbound address blacklist. * - * @param string URI to test. - * @param list Allowed protocols. + * @param string $raw_uri URI to test. + * @param list $protocols Allowed protocols. * @return pair Pre-resolved URI and domain. * @task uri */ @@ -853,7 +853,7 @@ final class PhabricatorEnv extends Phobject { /** * Determine if an IP address is in the outbound address blacklist. * - * @param string IP address. + * @param string $address IP address. * @return bool True if the address is blacklisted. */ public static function isBlacklistedOutboundAddress($address) { diff --git a/src/infrastructure/env/PhabricatorScopedEnv.php b/src/infrastructure/env/PhabricatorScopedEnv.php index 3bec720ae4..80a9e5893a 100644 --- a/src/infrastructure/env/PhabricatorScopedEnv.php +++ b/src/infrastructure/env/PhabricatorScopedEnv.php @@ -17,8 +17,8 @@ final class PhabricatorScopedEnv extends Phobject { /** * Override a configuration key in this scope, setting it to a new value. * - * @param string Key to override. - * @param wild New value. + * @param string $key Key to override. + * @param wild $value New value. * @return this * * @task override diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php index 84503048d0..89f03977f8 100644 --- a/src/infrastructure/markup/PhabricatorMarkupEngine.php +++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -56,10 +56,11 @@ final class PhabricatorMarkupEngine extends Phobject { * Convenience method for pushing a single object through the markup * pipeline. * - * @param PhabricatorMarkupInterface The object to render. - * @param string The field to render. - * @param PhabricatorUser User viewing the markup. - * @param object A context object for policy checks + * @param PhabricatorMarkupInterface $object The object to render. + * @param string $field The field to render. + * @param PhabricatorUser $viewer User viewing the markup. + * @param object? $context_object A context object for + * policy checks. * @return string Marked up output. * @task markup */ @@ -81,8 +82,8 @@ final class PhabricatorMarkupEngine extends Phobject { * Queue an object for markup generation when @{method:process} is * called. You can retrieve the output later with @{method:getOutput}. * - * @param PhabricatorMarkupInterface The object to render. - * @param string The field to render. + * @param PhabricatorMarkupInterface $object The object to render. + * @param string $field The field to render. * @return this * @task markup */ @@ -175,8 +176,8 @@ final class PhabricatorMarkupEngine extends Phobject { * @{method:addObject}. Before you can call this method, you must call * @{method:process}. * - * @param PhabricatorMarkupInterface The object to retrieve. - * @param string The field to retrieve. + * @param PhabricatorMarkupInterface $object The object to retrieve. + * @param string $field The field to retrieve. * @return string Processed output. * @task markup */ @@ -191,10 +192,11 @@ final class PhabricatorMarkupEngine extends Phobject { /** * Retrieve engine metadata for a given field. * - * @param PhabricatorMarkupInterface The object to retrieve. - * @param string The field to retrieve. - * @param string The engine metadata field to retrieve. - * @param wild Optional default value. + * @param PhabricatorMarkupInterface $object The object to retrieve. + * @param string $field The field to retrieve. + * @param string $metadata_key The engine metadata field + * to retrieve. + * @param wild? $default Optional default value. * @task markup */ public function getEngineMetadata( @@ -316,7 +318,7 @@ final class PhabricatorMarkupEngine extends Phobject { /** * Set the viewing user. Used to implement object permissions. * - * @param PhabricatorUser The viewing user. + * @param PhabricatorUser $viewer The viewing user. * @return this * @task markup */ @@ -328,7 +330,7 @@ final class PhabricatorMarkupEngine extends Phobject { /** * Set the context object. Used to implement object permissions. * - * @param The object in which context this remarkup is used. + * @param $object The object in which context this remarkup is used. * @return this * @task markup */ @@ -670,7 +672,7 @@ final class PhabricatorMarkupEngine extends Phobject { * * TODO: We could do a better job of this. * - * @param string Remarkup corpus to summarize. + * @param string $corpus Remarkup corpus to summarize. * @return string Summarized corpus. */ public static function summarize($corpus) { diff --git a/src/infrastructure/markup/PhabricatorMarkupInterface.php b/src/infrastructure/markup/PhabricatorMarkupInterface.php index f28c7ab50b..fdc39407b3 100644 --- a/src/infrastructure/markup/PhabricatorMarkupInterface.php +++ b/src/infrastructure/markup/PhabricatorMarkupInterface.php @@ -28,7 +28,7 @@ interface PhabricatorMarkupInterface { * * "{$phid}:{$field}" * - * @param string Field name. + * @param string $field Field name. * @return string Cache key up to 125 characters. * * @task markup @@ -39,7 +39,7 @@ interface PhabricatorMarkupInterface { /** * Build the engine the field should use. * - * @param string Field name. + * @param string $field Field name. * @return PhutilRemarkupEngine Markup engine to use. * @task markup */ @@ -49,7 +49,7 @@ interface PhabricatorMarkupInterface { /** * Return the contents of the specified field. * - * @param string Field name. + * @param string $field Field name. * @return string The raw markup contained in the field. * @task markup */ @@ -60,9 +60,9 @@ interface PhabricatorMarkupInterface { * Callback for final postprocessing of output. Normally, you can return * the output unmodified. * - * @param string Field name. - * @param string The finalized output of the engine. - * @param string The engine which generated the output. + * @param string $field Field name. + * @param string $output The finalized output of the engine. + * @param string $engine The engine which generated the output. * @return string Final output. * @task markup */ diff --git a/src/infrastructure/markup/PhutilMarkupEngine.php b/src/infrastructure/markup/PhutilMarkupEngine.php index e00ef15844..7f650a4417 100644 --- a/src/infrastructure/markup/PhutilMarkupEngine.php +++ b/src/infrastructure/markup/PhutilMarkupEngine.php @@ -8,8 +8,8 @@ abstract class PhutilMarkupEngine extends Phobject { * documentation for specific rules and blocks for what options are available * for configuration. * - * @param string Key to set in the configuration dictionary. - * @param string Value to set. + * @param string $key Key to set in the configuration dictionary. + * @param string $value Value to set. * @return this */ abstract public function setConfig($key, $value); @@ -21,8 +21,9 @@ abstract class PhutilMarkupEngine extends Phobject { * text; consult the documentation for specific rules and blocks to see what * metadata may be available in your configuration. * - * @param string Key to retrieve from metadata. - * @param mixed Default value to return if the key is not available. + * @param string $key Key to retrieve from metadata. + * @param mixed? $default Default value to return if the key is not + * available. * @return mixed Metadata property, or default value. */ abstract public function getTextMetadata($key, $default = null); diff --git a/src/infrastructure/markup/blockrule/PhutilRemarkupCodeBlockRule.php b/src/infrastructure/markup/blockrule/PhutilRemarkupCodeBlockRule.php index 8763111dd1..8cfc5060e7 100644 --- a/src/infrastructure/markup/blockrule/PhutilRemarkupCodeBlockRule.php +++ b/src/infrastructure/markup/blockrule/PhutilRemarkupCodeBlockRule.php @@ -350,7 +350,7 @@ final class PhutilRemarkupCodeBlockRule extends PhutilRemarkupBlockRule { /** * Get the extension from a filename. - * @param string "/path/to/something.name" + * @param string $name "/path/to/something.name" * @return null|string ".name" */ private function guessFilenameExtension($name) { diff --git a/src/infrastructure/markup/markuprule/PhutilRemarkupRule.php b/src/infrastructure/markup/markuprule/PhutilRemarkupRule.php index a5ddaa4ab6..6513d0f66c 100644 --- a/src/infrastructure/markup/markuprule/PhutilRemarkupRule.php +++ b/src/infrastructure/markup/markuprule/PhutilRemarkupRule.php @@ -20,9 +20,10 @@ abstract class PhutilRemarkupRule extends Phobject { /** * Check input whether to apply RemarkupRule. If true, apply formatting. - * @param string|PhutilSafeHTML String to check and potentially format. + * @param string|PhutilSafeHTML $text String to check and potentially + * format. * @return string|PhutilSafeHTML Unchanged input if no match, or input after - * matching the formatting rule and applying the formatting. + * matching the formatting rule and applying the formatting. */ abstract public function apply($text); @@ -59,9 +60,9 @@ abstract class PhutilRemarkupRule extends Phobject { * This method acts as @{function:phutil_tag}, but checks attributes before * using them. * - * @param string Tag name. - * @param dict Tag attributes. - * @param wild Tag content. + * @param string $name Tag name. + * @param dict $attrs Tag attributes. + * @param wild? $content Tag content. * @return PhutilSafeHTML Tag object. */ protected function newTag($name, array $attrs, $content = null) { @@ -85,7 +86,7 @@ abstract class PhutilRemarkupRule extends Phobject { * Normally, you can call @{method:newTag} rather than calling this method * directly. @{method:newTag} will check attributes for you. * - * @param wild Ostensibly flat text. + * @param wild $text Ostensibly flat text. * @return string Flat text. */ protected function assertFlatText($text) { @@ -104,7 +105,7 @@ abstract class PhutilRemarkupRule extends Phobject { /** * Check whether text is flat (contains no replacement tokens) or not. * - * @param wild Ostensibly flat text. + * @param wild $text Ostensibly flat text. * @return bool True if the text is flat. */ protected function isFlatText($text) { diff --git a/src/infrastructure/markup/render.php b/src/infrastructure/markup/render.php index 84c3616fe8..eaa4e067b2 100644 --- a/src/infrastructure/markup/render.php +++ b/src/infrastructure/markup/render.php @@ -16,9 +16,9 @@ * trusted blindly, and not escaped. You should not pass user data in these * parameters. * - * @param string The name of the tag, like `a` or `div`. - * @param map A map of tag attributes. - * @param wild Content to put in the tag. + * @param string $tag The name of the tag, like `a` or `div`. + * @param map? $attributes A map of tag attributes. + * @param wild? $content Content to put in the tag. * @return PhutilSafeHTML Tag object. */ function phutil_tag($tag, array $attributes = array(), $content = null) { diff --git a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php index b0399527b0..c0311ea236 100644 --- a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php @@ -258,7 +258,7 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule { * This is intended to make it easy to write unit tests for object remarkup * rules. Production code is not normally expected to call this method. * - * @param string Text to match rules against. + * @param string $text Text to match rules against. * @return wild Matches, suitable for writing unit tests against. */ public function extractReferences($text) { diff --git a/src/infrastructure/parser/PhutilURIHelper.php b/src/infrastructure/parser/PhutilURIHelper.php index 4341585e39..a43fafe576 100644 --- a/src/infrastructure/parser/PhutilURIHelper.php +++ b/src/infrastructure/parser/PhutilURIHelper.php @@ -19,7 +19,7 @@ final class PhutilURIHelper extends Phobject { private $phutilUri; /** - * @param string|PhutilURI + * @param string|PhutilURI $uri */ public function __construct($uri) { diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index cdd941a5f4..7d57df7c35 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -718,9 +718,9 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery * * This method will then return a composable clause for inclusion in WHERE. * - * @param AphrontDatabaseConnection Connection query will execute on. - * @param list Column description dictionaries. - * @param map Additional construction options. + * @param AphrontDatabaseConnection $conn Connection query will execute on. + * @param list $columns Column description dictionaries. + * @param map $options Additional construction options. * @return string Query clause. * @task paging */ @@ -884,7 +884,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery * across individual orderable columns. This offers greater control but is * also more involved. * - * @param string Key of a builtin order supported by this query. + * @param string $order Key of a builtin order supported by this query. * @return this * @task order */ @@ -917,7 +917,8 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery * This is a high-level method which works alongside @{method:setOrder}. For * lower-level control over order vectors, use @{method:setOrderVector}. * - * @param PhabricatorQueryOrderVector|list List of order keys. + * @param PhabricatorQueryOrderVector|list $vector List of order + * keys. * @return this * @task order */ @@ -1041,7 +1042,8 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery * To set an order vector, specify a list of order keys as provided by * @{method:getOrderableColumns}. * - * @param PhabricatorQueryOrderVector|list List of order keys. + * @param PhabricatorQueryOrderVector|list $vector List of order + * keys. * @return this * @task order */ @@ -1353,8 +1355,9 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery * - Find users with shirt sizes "X" or "XL". * - Find shoes with size "13". * - * @param PhabricatorCustomFieldIndexStorage Table where the index is stored. - * @param string|list One or more values to filter by. + * @param PhabricatorCustomFieldIndexStorage $index Table where the index is + * stored. + * @param string|list $value One or more values to filter by. * @return this * @task appsearch */ @@ -1403,9 +1406,10 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery * `5` will match fields with values `3`, `4`, or `5`. Providing `null` for * either end of the range will leave that end of the constraint open. * - * @param PhabricatorCustomFieldIndexStorage Table where the index is stored. - * @param int|null Minimum permissible value, inclusive. - * @param int|null Maximum permissible value, inclusive. + * @param PhabricatorCustomFieldIndexStorage $index Table where the index is + * stored. + * @param int|null $min Minimum permissible value, inclusive. + * @param int|null $max Maximum permissible value, inclusive. * @return this * @task appsearch */ @@ -1449,7 +1453,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery * See @{method:getPrimaryTableAlias} if the column needs to be qualified with * a table alias. * - * @param AphrontDatabaseConnection Connection executing queries. + * @param AphrontDatabaseConnection $conn Connection executing queries. * @return PhutilQueryString Column name. * @task appsearch */ @@ -1509,7 +1513,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery /** * Construct a GROUP BY clause appropriate for ApplicationSearch constraints. * - * @param AphrontDatabaseConnection Connection executing the query. + * @param AphrontDatabaseConnection $conn Connection executing the query. * @return string Group clause. * @task appsearch */ @@ -1531,7 +1535,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery * Construct a JOIN clause appropriate for applying ApplicationSearch * constraints. * - * @param AphrontDatabaseConnection Connection executing the query. + * @param AphrontDatabaseConnection $conn Connection executing the query. * @return string Join clause. * @task appsearch */ @@ -1653,7 +1657,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery * Construct a WHERE clause appropriate for applying ApplicationSearch * constraints. * - * @param AphrontDatabaseConnection Connection executing the query. + * @param AphrontDatabaseConnection $conn Connection executing the query. * @return list Where clause parts. * @task appsearch */ @@ -2583,9 +2587,9 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery * Convenience method for specifying edge logic constraints with a list of * PHIDs. * - * @param const Edge constant. - * @param const Constraint operator. - * @param list List of PHIDs. + * @param const $edge_type Edge constant. + * @param const $operator Constraint operator. + * @param list $phids List of PHIDs. * @return this * @task edgelogic */ @@ -3091,7 +3095,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery * Queries are always constrained to include only results from spaces the * viewer has access to. * - * @param list + * @param list $space_phids * @task spaces */ public function withSpacePHIDs(array $space_phids) { @@ -3135,7 +3139,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery * viewer has access to see with any explicit constraint on spaces added by * @{method:withSpacePHIDs}. * - * @param AphrontDatabaseConnection Database connection. + * @param AphrontDatabaseConnection $conn Database connection. * @return string Part of a WHERE clause. * @task spaces */ diff --git a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php index c43edaefcb..1faf45d63d 100644 --- a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php @@ -57,7 +57,7 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { * according to the viewer's capabilities. You must set a viewer to execute * a policy query. * - * @param PhabricatorUser The viewing user. + * @param PhabricatorUser $viewer The viewing user. * @return this * @task config */ @@ -473,8 +473,8 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { * automatically populated as a side effect of objects surviving policy * filtering. * - * @param map Objects to add to the query - * workspace. + * @param map $objects Objects to add to + * the query workspace. * @return this * @task workspace */ @@ -506,7 +506,7 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { * searches both the current query's workspace and the workspaces of parent * queries. * - * @param list List of PHIDs to retrieve. + * @param list $phids List of PHIDs to retrieve. * @return this * @task workspace */ @@ -536,7 +536,7 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { * PHIDs which are "in flight" are actively being queried for. Using this * list can prevent infinite query loops by aborting queries which cycle. * - * @param list List of PHIDs which are now in flight. + * @param list $phids List of PHIDs which are now in flight. * @return this */ public function putPHIDsInFlight(array $phids) { @@ -606,7 +606,7 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { * return new results. Generally, you should adjust a cursor position based * on the provided result page. * - * @param list The current page of results. + * @param list $page The current page of results. * @return void * @task policyimpl */ @@ -627,7 +627,7 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { * This method will only be called if data is available. Implementations * do not need to handle the case of no results specially. * - * @param list Results from `loadPage()`. + * @param list $page Results from `loadPage()`. * @return list Objects for policy filtering. * @task policyimpl */ @@ -650,7 +650,7 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { * This method will only be called if data is available. Implementations do * not need to handle the case of no results specially. * - * @param list Results from @{method:willFilterPage()}. + * @param list $page Results from @{method:willFilterPage()}. * @return list Objects after additional * non-policy processing. */ @@ -665,8 +665,8 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { * filtered for policy reasons. The query should remove them from any cached * or partial result sets. * - * @param list List of objects that should not be returned by alternate - * result mechanisms. + * @param list $results List of objects that should not be returned by + * alternate result mechanisms. * @return void * @task policyimpl */ @@ -680,7 +680,7 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { * used by @{class:PhabricatorCursorPagedPolicyAwareQuery} to reverse results * that are queried during reverse paging. * - * @param list Query results. + * @param list $results Query results. * @return list Final results. * @task policyimpl */ diff --git a/src/infrastructure/storage/lisk/LiskDAO.php b/src/infrastructure/storage/lisk/LiskDAO.php index 2e81b4641a..1915bd71ee 100644 --- a/src/infrastructure/storage/lisk/LiskDAO.php +++ b/src/infrastructure/storage/lisk/LiskDAO.php @@ -221,7 +221,8 @@ abstract class LiskDAO extends Phobject * return a new connection. Lisk handles connection caching and management; * do not perform caching deeper in the stack. * - * @param string Mode, either 'r' (reading) or 'w' (reading and writing). + * @param string $mode Mode, either 'r' (reading) or 'w' (reading and + * writing). * @return AphrontDatabaseConnection New database connection. * @task conn */ @@ -245,7 +246,7 @@ abstract class LiskDAO extends Phobject /** * Get an existing, cached connection for this object. * - * @param mode Connection mode. + * @param mode $mode Connection mode. * @return AphrontDatabaseConnection|null Connection, if it exists in cache. * @task conn */ @@ -261,8 +262,9 @@ abstract class LiskDAO extends Phobject /** * Store a connection in the connection cache. * - * @param mode Connection mode. - * @param AphrontDatabaseConnection Connection to cache. + * @param mode $mode Connection mode. + * @param AphrontDatabaseConnection $connection Connection to cache. + * @param bool? $force_unique * @return this * @task conn */ @@ -291,7 +293,8 @@ abstract class LiskDAO extends Phobject * This overrides all connection management and forces the object to use * a specific connection when interacting with the database. * - * @param AphrontDatabaseConnection Connection to force this object to use. + * @param AphrontDatabaseConnection $connection Connection to force this + * object to use. * @task conn */ public function setForcedConnection(AphrontDatabaseConnection $connection) { @@ -397,12 +400,12 @@ abstract class LiskDAO extends Phobject /** - * Determine the setting of a configuration option for this class of objects. + * Determine the setting of a configuration option for this class of objects. * - * @param const Option name, one of the CONFIG_* constants. - * @return mixed Option value, if configured (null if unavailable). + * @param const $option_name Option name, one of the CONFIG_* constants. + * @return mixed Option value, if configured (null if unavailable). * - * @task config + * @task config */ public function getConfigOption($option_name) { $options = $this->getLiskMetadata('config'); @@ -426,7 +429,7 @@ abstract class LiskDAO extends Phobject * * $dog = id(new Dog())->load($dog_id); * - * @param int Numeric ID identifying the object to load. + * @param int $id Numeric ID identifying the object to load. * @return obj|null Identified object, or null if it does not exist. * * @task load @@ -468,7 +471,7 @@ abstract class LiskDAO extends Phobject * * The pattern and arguments are as per queryfx(). * - * @param string queryfx()-style SQL WHERE clause. + * @param string $pattern queryfx()-style SQL WHERE clause. * @param ... Zero or more conversions. * @return dict Dictionary of matching objects, keyed on ID. * @@ -489,7 +492,7 @@ abstract class LiskDAO extends Phobject * query. See loadAllWhere(). This method is similar, but returns a single * result instead of a list. * - * @param string queryfx()-style SQL WHERE clause. + * @param string $pattern queryfx()-style SQL WHERE clause. * @param ... Zero or more conversions. * @return obj|null Matching object, or null if no object matches. * @@ -574,8 +577,8 @@ abstract class LiskDAO extends Phobject * convenient to pull data from elsewhere directly (e.g., a complicated * join via @{method:queryData}) and then load from an array representation. * - * @param dict Dictionary of properties, which should be equivalent to - * selecting a row from the table or calling + * @param dict $row Dictionary of properties, which should be equivalent + * to selecting a row from the table or calling * @{method:getProperties}. * @return this * @@ -649,7 +652,7 @@ abstract class LiskDAO extends Phobject * * This is a lot messier than @{method:loadAllWhere}, but more flexible. * - * @param list List of property dictionaries. + * @param list $rows List of property dictionaries. * @return dict List of constructed objects, keyed on ID. * * @task load @@ -690,7 +693,7 @@ abstract class LiskDAO extends Phobject * Set unique ID identifying this object. You normally don't need to call this * method unless with `IDS_MANUAL`. * - * @param mixed Unique ID. + * @param mixed $id Unique ID. * @return this * @task save */ @@ -723,7 +726,7 @@ abstract class LiskDAO extends Phobject /** * Test if a property exists. * - * @param string Property name. + * @param string $property Property name. * @return bool True if the property exists. * @task info */ @@ -798,9 +801,9 @@ abstract class LiskDAO extends Phobject /** * Get or build the database connection for this object. * - * @param string 'r' for read, 'w' for read/write. - * @param bool True to force a new connection. The connection will not - * be retrieved from or saved into the connection cache. + * @param string $mode 'r' for read, 'w' for read/write. + * @param bool? $force_new True to force a new connection. The connection + * will not be retrieved from or saved into the connection cache. * @return AphrontDatabaseConnection Lisk connection object. * * @task info @@ -1038,7 +1041,8 @@ abstract class LiskDAO extends Phobject /** * Internal implementation of INSERT and REPLACE. * - * @param const Either "INSERT" or "REPLACE", to force the desired mode. + * @param const $mode Either "INSERT" or "REPLACE", to force the desired + * mode. * @return this * * @task save @@ -1272,7 +1276,7 @@ abstract class LiskDAO extends Phobject * Reads the value from a field. Override this method for custom behavior * of @{method:getField} instead of overriding getField directly. * - * @param string Canonical field name + * @param string $field Canonical field name * @return mixed Value of the field * * @task hook @@ -1288,8 +1292,8 @@ abstract class LiskDAO extends Phobject * Writes a value to a field. Override this method for custom behavior of * setField($value) instead of overriding setField directly. * - * @param string Canonical field name - * @param mixed Value to write + * @param string $field Canonical field name + * @param mixed $value Value to write * * @task hook */ @@ -1476,7 +1480,8 @@ abstract class LiskDAO extends Phobject * Long-running processes can use this method to clean up connections which * have not been used recently. * - * @param int Close connections with no activity for this many seconds. + * @param int $idle_window Close connections with no activity for this many + * seconds. * @return void */ public static function closeInactiveConnections($idle_window) { @@ -1576,8 +1581,8 @@ abstract class LiskDAO extends Phobject /** * Black magic. Builds implied get*() and set*() for all properties. * - * @param string Method name. - * @param list Argument vector. + * @param string $method Method name. + * @param list $args Argument vector. * @return mixed get*() methods return the property value. set*() methods * return $this. * @task util @@ -1652,8 +1657,10 @@ abstract class LiskDAO extends Phobject /** * Increments a named counter and returns the next value. * - * @param AphrontDatabaseConnection Database where the counter resides. - * @param string Counter name to create or increment. + * @param AphrontDatabaseConnection $conn_w Database where the counter + * resides. + * @param string $counter_name Counter name to create + * or increment. * @return int Next counter value. * * @task util @@ -1686,8 +1693,9 @@ abstract class LiskDAO extends Phobject /** * Returns the current value of a named counter. * - * @param AphrontDatabaseConnection Database where the counter resides. - * @param string Counter name to read. + * @param AphrontDatabaseConnection $conn_r Database where the counter + * resides. + * @param string $counter_name Counter name to read. * @return int|null Current value, or `null` if the counter does not exist. * * @task util @@ -1714,8 +1722,10 @@ abstract class LiskDAO extends Phobject * * If the counter does not exist, it is created. * - * @param AphrontDatabaseConnection Database where the counter resides. - * @param string Counter name to create or overwrite. + * @param AphrontDatabaseConnection $conn_w Database where the counter + * resides. + * @param string $counter_name Counter name to create or overwrite. + * @param int $counter_value * @return void * * @task util diff --git a/src/infrastructure/util/PhabricatorGlobalLock.php b/src/infrastructure/util/PhabricatorGlobalLock.php index 2dd38a50c7..c82c2f0604 100644 --- a/src/infrastructure/util/PhabricatorGlobalLock.php +++ b/src/infrastructure/util/PhabricatorGlobalLock.php @@ -88,7 +88,7 @@ final class PhabricatorGlobalLock extends PhutilLock { * (somewhat arbitrarily). In most cases this is fine, but this method can * be used to lock on a specific connection. * - * @param AphrontDatabaseConnection + * @param AphrontDatabaseConnection $conn * @return this */ public function setExternalConnection(AphrontDatabaseConnection $conn) { diff --git a/src/infrastructure/util/PhabricatorHash.php b/src/infrastructure/util/PhabricatorHash.php index 8916b37a0e..2b3fe8ab4c 100644 --- a/src/infrastructure/util/PhabricatorHash.php +++ b/src/infrastructure/util/PhabricatorHash.php @@ -11,7 +11,8 @@ final class PhabricatorHash extends Phobject { * Because a SHA1 collision is now known, this method should be considered * weak. Callers should prefer @{method:digestWithNamedKey}. * - * @param string Input string. + * @param string $string Input string. + * @param string? $key * @return string 32-byte hexadecimal SHA1+HMAC hash. */ public static function weakDigest($string, $key = null) { @@ -38,7 +39,7 @@ final class PhabricatorHash extends Phobject { * This method emphasizes compactness, and should not be used for security * related hashing (for general purpose hashing, see @{method:digest}). * - * @param string Input string. + * @param string $string Input string. * @return string 12-byte, case-sensitive, mostly-alphanumeric hash of * the string. */ @@ -72,7 +73,7 @@ final class PhabricatorHash extends Phobject { * much stuff we're breaking by switching to it. For additional discussion, * see T13045. * - * @param string Input string. + * @param string $string Input string. * @return string 12-byte, case-sensitive, purely-alphanumeric hash of * the string. */ @@ -149,8 +150,8 @@ final class PhabricatorHash extends Phobject { * maintaining a high degree of collision resistance and a moderate degree * of human readability. * - * @param string The string to shorten. - * @param int Maximum length of the result. + * @param string $string The string to shorten. + * @param int $length Maximum length of the result. * @return string String shortened in a collision-resistant way. */ public static function digestToLength($string, $length) { diff --git a/src/infrastructure/util/password/PhabricatorPasswordHasher.php b/src/infrastructure/util/password/PhabricatorPasswordHasher.php index fe35d2c296..ac7c9963e9 100644 --- a/src/infrastructure/util/password/PhabricatorPasswordHasher.php +++ b/src/infrastructure/util/password/PhabricatorPasswordHasher.php @@ -101,7 +101,7 @@ abstract class PhabricatorPasswordHasher extends Phobject { /** * Produce a password hash. * - * @param PhutilOpaqueEnvelope Text to be hashed. + * @param PhutilOpaqueEnvelope $envelope Text to be hashed. * @return PhutilOpaqueEnvelope Hashed text. * @task hasher */ @@ -114,8 +114,8 @@ abstract class PhabricatorPasswordHasher extends Phobject { * The default implementation checks for equality; if a hasher embeds salt in * hashes it should override this method and perform a salt-aware comparison. * - * @param PhutilOpaqueEnvelope Password to compare. - * @param PhutilOpaqueEnvelope Bare password hash. + * @param PhutilOpaqueEnvelope $password Password to compare. + * @param PhutilOpaqueEnvelope $hash Bare password hash. * @return bool True if the passwords match. * @task hasher */ @@ -137,7 +137,7 @@ abstract class PhabricatorPasswordHasher extends Phobject { * have (for example) an internal cost function may be able to upgrade an * existing hash to a stronger one with a higher cost. * - * @param PhutilOpaqueEnvelope Bare hash. + * @param PhutilOpaqueEnvelope $hash Bare hash. * @return bool True if the hash can be upgraded without * changing the algorithm (for example, to a * higher cost). @@ -154,7 +154,7 @@ abstract class PhabricatorPasswordHasher extends Phobject { /** * Get the hash of a password for storage. * - * @param PhutilOpaqueEnvelope Password text. + * @param PhutilOpaqueEnvelope $envelope Password text. * @return PhutilOpaqueEnvelope Hashed text. * @task hashing */ @@ -349,7 +349,7 @@ abstract class PhabricatorPasswordHasher extends Phobject { /** * Generate a new hash for a password, using the best available hasher. * - * @param PhutilOpaqueEnvelope Password to hash. + * @param PhutilOpaqueEnvelope $password Password to hash. * @return PhutilOpaqueEnvelope Hashed password, using best available * hasher. * @task hashing @@ -364,8 +364,8 @@ abstract class PhabricatorPasswordHasher extends Phobject { /** * Compare a password to a stored hash. * - * @param PhutilOpaqueEnvelope Password to compare. - * @param PhutilOpaqueEnvelope Stored password hash. + * @param PhutilOpaqueEnvelope $password Password to compare. + * @param PhutilOpaqueEnvelope $hash Stored password hash. * @return bool True if the passwords match. * @task hashing */ @@ -383,7 +383,7 @@ abstract class PhabricatorPasswordHasher extends Phobject { /** * Get the human-readable algorithm name for a given hash. * - * @param PhutilOpaqueEnvelope Storage hash. + * @param PhutilOpaqueEnvelope $hash Storage hash. * @return string Human-readable algorithm name. */ public static function getCurrentAlgorithmName(PhutilOpaqueEnvelope $hash) { diff --git a/src/view/AphrontView.php b/src/view/AphrontView.php index 669742f29b..f1773c7579 100644 --- a/src/view/AphrontView.php +++ b/src/view/AphrontView.php @@ -16,7 +16,7 @@ abstract class AphrontView extends Phobject /** * Set the user viewing this element. * - * @param PhabricatorUser Viewing user. + * @param PhabricatorUser $viewer Viewing user. * @return this */ public function setViewer(PhabricatorUser $viewer) { @@ -100,7 +100,7 @@ abstract class AphrontView extends Phobject * This method will only work if the view supports children, which is * determined by @{method:canAppendChild}. * - * @param wild Something renderable. + * @param wild $child Something renderable. * @return this */ final public function appendChild($child) { @@ -152,7 +152,7 @@ abstract class AphrontView extends Phobject * NOTE: Because View children are not rendered, a View which renders down * to nothing will not be reduced by this method. * - * @param list Renderable children. + * @param list $children Renderable children. * @return list Reduced list of children. * @task children */ diff --git a/src/view/control/AphrontTableView.php b/src/view/control/AphrontTableView.php index ab1f6be0ed..697e895c3d 100644 --- a/src/view/control/AphrontTableView.php +++ b/src/view/control/AphrontTableView.php @@ -105,7 +105,7 @@ final class AphrontTableView extends AphrontView { * * list($sort, $reverse) = AphrontTableView::parseSortParam($sort_param); * - * @param string Sort request parameter. + * @param string $sort Sort request parameter. * @return pair Sort value, sort direction. */ public static function parseSort($sort) { diff --git a/src/view/form/AphrontFormView.php b/src/view/form/AphrontFormView.php index 3022a91f9c..de1143ab85 100644 --- a/src/view/form/AphrontFormView.php +++ b/src/view/form/AphrontFormView.php @@ -120,7 +120,7 @@ final class AphrontFormView extends AphrontView { * controls. It will propagate some information from the form to the * control to simplify rendering. * - * @param AphrontFormControl Control to append. + * @param AphrontFormControl $control Control to append. * @return this */ public function appendControl(AphrontFormControl $control) { diff --git a/src/view/form/control/AphrontFormControl.php b/src/view/form/control/AphrontFormControl.php index 83e0207241..af317d257c 100644 --- a/src/view/form/control/AphrontFormControl.php +++ b/src/view/form/control/AphrontFormControl.php @@ -59,7 +59,7 @@ abstract class AphrontFormControl extends AphrontView { /** * Set the Caption * The Caption shows a tip usually nearby the related input field. - * @param string|PhutilSafeHTML|null + * @param string|PhutilSafeHTML|null $caption * @return self */ public function setCaption($caption) { diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index eb7b775f0f..84581e991c 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -379,7 +379,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView /** * Insert a HTML element into of the page to render. * - * @param PhutilSafeHTML HTML header to add + * @param PhutilSafeHTML $html HTML header to add */ public function addHeadItem($html) { if ($html instanceof PhutilSafeHTML) { diff --git a/src/view/phui/PHUIBoxView.php b/src/view/phui/PHUIBoxView.php index 33924379cc..2fb7f68802 100644 --- a/src/view/phui/PHUIBoxView.php +++ b/src/view/phui/PHUIBoxView.php @@ -35,7 +35,8 @@ final class PHUIBoxView extends AphrontTagView { * Render PHUIBoxView as a
instead of a
HTML tag. * To be used for collapse/expand in combination with PHUIHeaderView. * - * @param bool True to wrap in instead of
HTML tag. + * @param bool $collapsible True to wrap in instead of
HTML + * tag. */ public function setCollapsible($collapsible) { $this->collapsible = $collapsible; diff --git a/src/view/phui/PHUICrumbView.php b/src/view/phui/PHUICrumbView.php index 1e5dad2ec6..a8c0a7e57a 100644 --- a/src/view/phui/PHUICrumbView.php +++ b/src/view/phui/PHUICrumbView.php @@ -23,7 +23,7 @@ final class PHUICrumbView extends AphrontView { * Make this crumb always visible, even on devices where it would normally * be hidden. * - * @param bool True to make the crumb always visible. + * @param bool $always_visible True to make the crumb always visible. * @return this */ public function setAlwaysVisible($always_visible) { diff --git a/src/view/phui/PHUICrumbsView.php b/src/view/phui/PHUICrumbsView.php index fb1a0ae9b4..bd202ed5a7 100644 --- a/src/view/phui/PHUICrumbsView.php +++ b/src/view/phui/PHUICrumbsView.php @@ -15,8 +15,8 @@ final class PHUICrumbsView extends AphrontView { * Convenience method for adding a simple crumb with just text, or text and * a link. * - * @param string Text of the crumb. - * @param string? Optional href for the crumb. + * @param string $text Text of the crumb. + * @param string? $href Optional href for the crumb. * @return this */ public function addTextCrumb($text, $href = null) { diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index f1952b4843..8b8774c4cf 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -95,7 +95,8 @@ final class PHUIHeaderView extends AphrontTagView { * Render PHUIHeaderView as a instead of a
HTML tag. * To be used for collapse/expand in combination with PHUIBoxView. * - * @param bool True to wrap in instead of
HTML tag. + * @param bool $collapsible True to wrap in instead of
HTML + * tag. */ public function setCollapsible($collapsible) { $this->collapsible = $collapsible; diff --git a/src/view/phui/PHUIPagerView.php b/src/view/phui/PHUIPagerView.php index 2bb3a8276e..a3821bb4a8 100644 --- a/src/view/phui/PHUIPagerView.php +++ b/src/view/phui/PHUIPagerView.php @@ -95,7 +95,7 @@ final class PHUIPagerView extends AphrontView { * $pager->getPageSize() + 1); * $results = $pager->sliceResults($results); * - * @param list Result array. + * @param list $results Result array. * @return list One page of results. */ public function sliceResults(array $results) { diff --git a/src/view/viewutils.php b/src/view/viewutils.php index 956f6ca2c2..47a6f0178d 100644 --- a/src/view/viewutils.php +++ b/src/view/viewutils.php @@ -104,9 +104,9 @@ function phabricator_datetimezone($epoch, $user) { * @{function:phabricator_date}, @{function:phabricator_time}, or * @{function:phabricator_datetime}. * - * @param int Unix epoch timestamp. - * @param PhabricatorUser User viewing the timestamp. - * @param string Date format, as per DateTime class. + * @param int $epoch Unix epoch timestamp. + * @param PhabricatorUser $user User viewing the timestamp. + * @param string $format Date format, as per DateTime class. * @return string Formatted, local date/time. */ function phabricator_format_local_time($epoch, $user, $format) { diff --git a/support/startup/PhabricatorClientLimit.php b/support/startup/PhabricatorClientLimit.php index c43e4b42c0..ea2f39d217 100644 --- a/support/startup/PhabricatorClientLimit.php +++ b/support/startup/PhabricatorClientLimit.php @@ -170,7 +170,7 @@ abstract class PhabricatorClientLimit { /** * Get the APC key for a given bucket. * - * @param int Bucket to get the key for. + * @param int $bucket_id Bucket to get the key for. * @return string APC key for the bucket. */ private function getBucketCacheKey($bucket_id) { @@ -182,9 +182,8 @@ abstract class PhabricatorClientLimit { /** * Add points to the rate limit score for some client. * - * @param string Some key which identifies the client making the request. - * @param float The cost for this request; more points pushes them toward - * the limit faster. + * @param float $score The cost for this request; more points pushes them + * toward the limit faster. * @return this */ private function addScore($score) { diff --git a/support/startup/PhabricatorStartup.php b/support/startup/PhabricatorStartup.php index 2ae5993dfb..38c8af13b1 100644 --- a/support/startup/PhabricatorStartup.php +++ b/support/startup/PhabricatorStartup.php @@ -112,7 +112,7 @@ final class PhabricatorStartup { /** - * @param float Request start time, from `microtime(true)`. + * @param float $start_time Request start time, from `microtime(true)`. * @task hook */ public static function didStartup($start_time) { @@ -255,7 +255,7 @@ final class PhabricatorStartup { * The limit is implemented with a tick function, so enabling it implies * some accounting overhead. * - * @param int Time limit in seconds. + * @param int $limit Time limit in seconds. * @return void */ public static function setDebugTimeLimit($limit) { @@ -312,11 +312,12 @@ final class PhabricatorStartup { * Fatal the request completely in response to an exception, sending a plain * text message to the client. Calls @{method:didFatal} internally. * - * @param string Brief description of the exception context, like + * @param string $note Brief description of the exception context, like * `"Rendering Exception"`. - * @param Throwable The exception itself. - * @param bool True if it's okay to show the exception's stack trace - * to the user. The trace will always be logged. + * @param Throwable $ex The exception itself. + * @param bool $show_trace True if it's okay to show the exception's + * stack trace to the user. The trace will always be + * logged. * * @task apocalypse */ @@ -342,11 +343,11 @@ final class PhabricatorStartup { /** * Fatal the request completely, sending a plain text message to the client. * - * @param string Plain text message to send to the client. - * @param string Plain text message to send to the error log. If not - * provided, the client message is used. You can pass a more - * detailed message here (e.g., with stack traces) to avoid - * showing it to users. + * @param string $message Plain text message to send to the client. + * @param string? $log_message Plain text message to send to the error log. + * If not provided, the client message is used. You can pass + * a more detailed message here (e.g., with stack traces) to + * avoid showing it to users. * @return exit This method **does not return**. * * @task apocalypse @@ -513,7 +514,7 @@ final class PhabricatorStartup { * Adjustments here primarily impact the environment as seen by subprocesses. * The environment is forwarded explicitly by @{class:ExecFuture}. * - * @param map Input `$_ENV`. + * @param map $env Input `$_ENV`. * @return map Suitable `$_ENV`. * @task validation */ @@ -653,7 +654,7 @@ final class PhabricatorStartup { /** * Add a new client limits. * - * @param PhabricatorClientLimit New limit. + * @param PhabricatorClientLimit $limit New limit. * @return PhabricatorClientLimit The limit. */ public static function addRateLimit(PhabricatorClientLimit $limit) { @@ -698,7 +699,8 @@ final class PhabricatorStartup { /** * Tear down rate limiting and allow limits to score the request. * - * @param map Additional, freeform request state. + * @param map $request_state Additional, freeform request + * state. * @return void * @task ratelimit */ @@ -745,7 +747,7 @@ final class PhabricatorStartup { * time and record it with @{method:recordStartupPhase} after the class is * available. * - * @param string Phase name. + * @param string $phase Phase name. * @task phases */ public static function beginStartupPhase($phase) { @@ -761,8 +763,8 @@ final class PhabricatorStartup { * record a time before the class loads, then hand it over once the class * becomes available. * - * @param string Phase name. - * @param float Phase start time, from `microtime(true)`. + * @param string $phase Phase name. + * @param float $time Phase start time, from `microtime(true)`. * @task phases */ public static function recordStartupPhase($phase, $time) { diff --git a/support/startup/preamble-utils.php b/support/startup/preamble-utils.php index 8dd3b502d6..c04f06b900 100644 --- a/support/startup/preamble-utils.php +++ b/support/startup/preamble-utils.php @@ -4,7 +4,7 @@ * Parse the "X_FORWARDED_FOR" HTTP header to determine the original client * address. * - * @param int Number of devices to trust. + * @param int? $layers Number of devices to trust. * @return void */ function preamble_trust_x_forwarded_for_header($layers = 1) { From b9fb8d789151317fa3cb9a62433773d3b681c883 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Wed, 21 Aug 2024 14:05:19 +0200 Subject: [PATCH 116/118] Replace invalid PHPDoc @return type boolean with bool Summary: `@return boolean` is not a valid type thus use `@return bool`. Test Plan: None. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D25795 --- src/infrastructure/parser/PhutilURIHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/parser/PhutilURIHelper.php b/src/infrastructure/parser/PhutilURIHelper.php index a43fafe576..2ead1b1403 100644 --- a/src/infrastructure/parser/PhutilURIHelper.php +++ b/src/infrastructure/parser/PhutilURIHelper.php @@ -69,7 +69,7 @@ final class PhutilURIHelper extends Phobject { /** * Check whenever the URI starts with the provided character. * @param string $char String that MUST have length of 1. - * @return boolean + * @return bool */ private function isStartingWithChar($char) { return strncmp($this->uriStr, $char, 1) === 0; From 3c5e43cccbf07e26bd3ffcd94aec212643ac7156 Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Thu, 15 Aug 2024 14:23:34 +0200 Subject: [PATCH 117/118] Fix PHP 8.1 "strlen(null)" exception in DiffusionGitRawDiffQuery Summary: `strlen()` was used in Phabricator to check if a generic value is a non-empty string. This behavior is deprecated since PHP 8.1. Phorge adopts `phutil_nonempty_string()` as a replacement. Note: this may highlight other absurd input values that might be worth correcting instead of just ignoring. If phutil_nonempty_string() throws an exception in your instance, report it to Phorge to evaluate and fix that specific corner case. ``` Deprecated: strlen(): Passing null to parameter #1 ($string) of type string is deprecated in /home/phd/phabricator/src/applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php on line 38 ``` Closes T15399 Test Plan: Read the code. Reviewers: O1 Blessed Committers, valerio.bozzolan Reviewed By: O1 Blessed Committers, valerio.bozzolan Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Maniphest Tasks: T15399 Differential Revision: https://we.phorge.it/D25779 --- .../diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php b/src/applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php index 9c894437ef..b05e96e592 100644 --- a/src/applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php +++ b/src/applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php @@ -35,7 +35,7 @@ final class DiffusionGitRawDiffQuery extends DiffusionRawDiffQuery { } $path = $drequest->getPath(); - if (!strlen($path)) { + if (!phutil_nonempty_string($path)) { $path = '.'; } From b02615bd5027ee51ac68d48a0a64306b75285789 Mon Sep 17 00:00:00 2001 From: Valerio Bozzolan Date: Sat, 24 Aug 2024 15:34:06 +0200 Subject: [PATCH 118/118] Subversion: fix empty "Authored on" date on commit pages Summary: Subversion does not allow to have local commits. So, manage this case. Closes T15612 Test Plan: Surf a git commit: nothing changed. Surf a svn commit: the "Authored on" date is not empty anymore. Reviewers: O1 Blessed Committers, aklapper Reviewed By: O1 Blessed Committers, aklapper Subscribers: aklapper, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15612 Differential Revision: https://we.phorge.it/D25796 --- .../diffusion/controller/DiffusionCommitController.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index ce3ef41c9e..1e2ec10847 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -624,6 +624,13 @@ final class DiffusionCommitController extends DiffusionController { $author_view = $commit->newCommitAuthorView($viewer); if ($author_view) { $author_date = $data->getAuthorEpoch(); + + // Add support to Subversion commits that only have one date, + // since in SVN you cannot prepare local commits to push them later. + if ($author_date === null) { + $author_date = $commit->getEpoch(); + } + $author_date = phabricator_datetime($author_date, $viewer); $provenance_list->addItem(