Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
abstract class PhabricatorEditField extends Phobject {
|
|
|
|
|
|
|
|
private $key;
|
|
|
|
private $viewer;
|
|
|
|
private $label;
|
|
|
|
private $aliases = array();
|
|
|
|
private $value;
|
2015-11-29 22:24:47 +01:00
|
|
|
private $initialValue;
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
private $hasValue = false;
|
|
|
|
private $object;
|
|
|
|
private $transactionType;
|
|
|
|
private $metadata = array();
|
2015-11-03 14:38:06 +01:00
|
|
|
private $description;
|
2015-11-03 17:45:00 +01:00
|
|
|
private $editTypeKey;
|
2015-11-29 19:05:41 +01:00
|
|
|
private $isRequired;
|
2015-12-15 15:57:32 +01:00
|
|
|
|
2015-12-04 16:56:03 +01:00
|
|
|
private $commentActionLabel;
|
2015-12-15 15:57:32 +01:00
|
|
|
private $commentActionValue;
|
|
|
|
private $hasCommentActionValue;
|
2015-11-18 19:50:09 +01:00
|
|
|
|
Allow ApplicationEditor forms to be reconfigured
Summary:
Ref T9132. This diff doesn't do anything interesting, it just lays the groundwork for more interesting future diffs.
Broadly, the idea here is to let you create multiple views of each edit form. For example, we might create several different "Create Task" forms, like:
- "New Bug Report"
- "New Feature Request"
These would be views of the "Create Task" form, but with various adjustments:
- A form might have additional instructions ("how to file a good bug report").
- A form might have prefilled values for some fields (like particular projects, subscribers, or policies).
- A form might have some fields locked (so they can not be edited) or hidden.
- A form might have a different field order.
- A form might have a limited visibility policy, so only some users can access it.
This diff adds a new storage object (`EditEngineConfiguration`) to keep track of all those customizations and represent "a form which has been configured to look and work a certain way".
This doesn't let these configurations do anything useful/interesting, and you can't access them directly yet, it's just all the boring plumbing to enable more interesting behavior in the future.
Test Plan:
ApplicationEditor forms now let you manage available forms and edit the current form:
{F959025}
There's a new (bare bones) list of all available engines:
{F959030}
And if you jump into an engine, you can see all the forms for it:
{F959038}
The actual form configurations have standard detail/edit pages. The edit pages are themselves driven by ApplicationEditor, of course, so you can edit the form for editing forms.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T9132
Differential Revision: https://secure.phabricator.com/D14453
2015-11-04 21:52:52 +01:00
|
|
|
private $isLocked;
|
2015-11-18 19:50:09 +01:00
|
|
|
private $isHidden;
|
|
|
|
|
2015-11-17 18:33:06 +01:00
|
|
|
private $isPreview;
|
2015-11-18 19:38:12 +01:00
|
|
|
private $isEditDefaults;
|
2015-11-29 19:05:41 +01:00
|
|
|
private $isSubmittedForm;
|
|
|
|
private $controlError;
|
2015-11-18 19:50:09 +01:00
|
|
|
|
|
|
|
private $isReorderable = true;
|
2015-11-18 19:38:12 +01:00
|
|
|
private $isDefaultable = true;
|
2015-11-18 19:50:09 +01:00
|
|
|
private $isLockable = true;
|
2015-12-07 20:37:51 +01:00
|
|
|
private $isCopyable = false;
|
2015-12-15 15:23:13 +01:00
|
|
|
private $isConduitOnly = false;
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
|
|
|
|
public function setKey($key) {
|
|
|
|
$this->key = $key;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getKey() {
|
|
|
|
return $this->key;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setLabel($label) {
|
|
|
|
$this->label = $label;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getLabel() {
|
|
|
|
return $this->label;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setViewer(PhabricatorUser $viewer) {
|
|
|
|
$this->viewer = $viewer;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getViewer() {
|
|
|
|
return $this->viewer;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setAliases(array $aliases) {
|
|
|
|
$this->aliases = $aliases;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getAliases() {
|
|
|
|
return $this->aliases;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setObject($object) {
|
|
|
|
$this->object = $object;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getObject() {
|
|
|
|
return $this->object;
|
|
|
|
}
|
|
|
|
|
2015-11-03 14:38:06 +01:00
|
|
|
public function setDescription($description) {
|
|
|
|
$this->description = $description;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getDescription() {
|
|
|
|
return $this->description;
|
|
|
|
}
|
|
|
|
|
Allow ApplicationEditor forms to be reconfigured
Summary:
Ref T9132. This diff doesn't do anything interesting, it just lays the groundwork for more interesting future diffs.
Broadly, the idea here is to let you create multiple views of each edit form. For example, we might create several different "Create Task" forms, like:
- "New Bug Report"
- "New Feature Request"
These would be views of the "Create Task" form, but with various adjustments:
- A form might have additional instructions ("how to file a good bug report").
- A form might have prefilled values for some fields (like particular projects, subscribers, or policies).
- A form might have some fields locked (so they can not be edited) or hidden.
- A form might have a different field order.
- A form might have a limited visibility policy, so only some users can access it.
This diff adds a new storage object (`EditEngineConfiguration`) to keep track of all those customizations and represent "a form which has been configured to look and work a certain way".
This doesn't let these configurations do anything useful/interesting, and you can't access them directly yet, it's just all the boring plumbing to enable more interesting behavior in the future.
Test Plan:
ApplicationEditor forms now let you manage available forms and edit the current form:
{F959025}
There's a new (bare bones) list of all available engines:
{F959030}
And if you jump into an engine, you can see all the forms for it:
{F959038}
The actual form configurations have standard detail/edit pages. The edit pages are themselves driven by ApplicationEditor, of course, so you can edit the form for editing forms.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T9132
Differential Revision: https://secure.phabricator.com/D14453
2015-11-04 21:52:52 +01:00
|
|
|
public function setIsLocked($is_locked) {
|
|
|
|
$this->isLocked = $is_locked;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getIsLocked() {
|
|
|
|
return $this->isLocked;
|
|
|
|
}
|
|
|
|
|
2015-11-17 18:33:06 +01:00
|
|
|
public function setIsPreview($preview) {
|
|
|
|
$this->isPreview = $preview;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getIsPreview() {
|
|
|
|
return $this->isPreview;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setIsReorderable($is_reorderable) {
|
|
|
|
$this->isReorderable = $is_reorderable;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getIsReorderable() {
|
|
|
|
return $this->isReorderable;
|
|
|
|
}
|
|
|
|
|
2015-12-15 15:23:13 +01:00
|
|
|
public function setIsConduitOnly($is_conduit_only) {
|
|
|
|
$this->isConduitOnly = $is_conduit_only;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getIsConduitOnly() {
|
|
|
|
return $this->isConduitOnly;
|
|
|
|
}
|
|
|
|
|
2015-11-18 19:38:12 +01:00
|
|
|
public function setIsEditDefaults($is_edit_defaults) {
|
|
|
|
$this->isEditDefaults = $is_edit_defaults;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getIsEditDefaults() {
|
|
|
|
return $this->isEditDefaults;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setIsDefaultable($is_defaultable) {
|
|
|
|
$this->isDefaultable = $is_defaultable;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getIsDefaultable() {
|
|
|
|
return $this->isDefaultable;
|
|
|
|
}
|
|
|
|
|
2015-11-18 19:50:09 +01:00
|
|
|
public function setIsLockable($is_lockable) {
|
|
|
|
$this->isLockable = $is_lockable;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getIsLockable() {
|
|
|
|
return $this->isLockable;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setIsHidden($is_hidden) {
|
|
|
|
$this->isHidden = $is_hidden;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getIsHidden() {
|
|
|
|
return $this->isHidden;
|
|
|
|
}
|
|
|
|
|
2015-12-07 20:37:51 +01:00
|
|
|
public function setIsCopyable($is_copyable) {
|
|
|
|
$this->isCopyable = $is_copyable;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getIsCopyable() {
|
|
|
|
return $this->isCopyable;
|
|
|
|
}
|
|
|
|
|
2015-11-29 19:05:41 +01:00
|
|
|
public function setIsSubmittedForm($is_submitted) {
|
|
|
|
$this->isSubmittedForm = $is_submitted;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getIsSubmittedForm() {
|
|
|
|
return $this->isSubmittedForm;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setIsRequired($is_required) {
|
|
|
|
$this->isRequired = $is_required;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getIsRequired() {
|
|
|
|
return $this->isRequired;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setControlError($control_error) {
|
|
|
|
$this->controlError = $control_error;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getControlError() {
|
|
|
|
return $this->controlError;
|
|
|
|
}
|
|
|
|
|
2015-12-04 16:56:03 +01:00
|
|
|
public function setCommentActionLabel($label) {
|
|
|
|
$this->commentActionLabel = $label;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getCommentActionLabel() {
|
|
|
|
return $this->commentActionLabel;
|
|
|
|
}
|
|
|
|
|
2015-12-15 15:57:32 +01:00
|
|
|
public function setCommentActionValue($comment_action_value) {
|
|
|
|
$this->hasCommentActionValue = true;
|
|
|
|
$this->commentActionValue = $comment_action_value;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getCommentActionValue() {
|
|
|
|
return $this->commentActionValue;
|
|
|
|
}
|
|
|
|
|
2015-11-29 19:47:16 +01:00
|
|
|
protected function newControl() {
|
|
|
|
throw new PhutilMethodNotImplementedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function buildControl() {
|
2015-12-15 15:23:13 +01:00
|
|
|
if ($this->getIsConduitOnly()) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
$control = $this->newControl();
|
|
|
|
if ($control === null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$control
|
|
|
|
->setValue($this->getValueForControl())
|
|
|
|
->setName($this->getKey());
|
|
|
|
|
|
|
|
if (!$control->getLabel()) {
|
|
|
|
$control->setLabel($this->getLabel());
|
|
|
|
}
|
|
|
|
|
2015-11-29 19:47:16 +01:00
|
|
|
if ($this->getIsSubmittedForm()) {
|
|
|
|
$error = $this->getControlError();
|
|
|
|
if ($error !== null) {
|
|
|
|
$control->setError($error);
|
|
|
|
}
|
|
|
|
} else if ($this->getIsRequired()) {
|
|
|
|
$control->setError(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $control;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function renderControl() {
|
|
|
|
$control = $this->buildControl();
|
|
|
|
if ($control === null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-11-18 19:38:12 +01:00
|
|
|
if ($this->getIsPreview()) {
|
|
|
|
$disabled = true;
|
2015-11-18 19:50:09 +01:00
|
|
|
$hidden = false;
|
2015-11-18 19:38:12 +01:00
|
|
|
} else if ($this->getIsEditDefaults()) {
|
|
|
|
$disabled = false;
|
2015-11-18 19:50:09 +01:00
|
|
|
$hidden = false;
|
2015-11-18 19:38:12 +01:00
|
|
|
} else {
|
|
|
|
$disabled = $this->getIsLocked();
|
2015-11-18 19:50:09 +01:00
|
|
|
$hidden = $this->getIsHidden();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($hidden) {
|
|
|
|
return null;
|
Allow ApplicationEditor forms to be reconfigured
Summary:
Ref T9132. This diff doesn't do anything interesting, it just lays the groundwork for more interesting future diffs.
Broadly, the idea here is to let you create multiple views of each edit form. For example, we might create several different "Create Task" forms, like:
- "New Bug Report"
- "New Feature Request"
These would be views of the "Create Task" form, but with various adjustments:
- A form might have additional instructions ("how to file a good bug report").
- A form might have prefilled values for some fields (like particular projects, subscribers, or policies).
- A form might have some fields locked (so they can not be edited) or hidden.
- A form might have a different field order.
- A form might have a limited visibility policy, so only some users can access it.
This diff adds a new storage object (`EditEngineConfiguration`) to keep track of all those customizations and represent "a form which has been configured to look and work a certain way".
This doesn't let these configurations do anything useful/interesting, and you can't access them directly yet, it's just all the boring plumbing to enable more interesting behavior in the future.
Test Plan:
ApplicationEditor forms now let you manage available forms and edit the current form:
{F959025}
There's a new (bare bones) list of all available engines:
{F959030}
And if you jump into an engine, you can see all the forms for it:
{F959038}
The actual form configurations have standard detail/edit pages. The edit pages are themselves driven by ApplicationEditor, of course, so you can edit the form for editing forms.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T9132
Differential Revision: https://secure.phabricator.com/D14453
2015-11-04 21:52:52 +01:00
|
|
|
}
|
|
|
|
|
2015-11-18 19:38:12 +01:00
|
|
|
$control->setDisabled($disabled);
|
|
|
|
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
return $control;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function appendToForm(AphrontFormView $form) {
|
|
|
|
$control = $this->renderControl();
|
|
|
|
if ($control !== null) {
|
2015-11-18 19:50:09 +01:00
|
|
|
|
|
|
|
if ($this->getIsPreview()) {
|
|
|
|
if ($this->getIsHidden()) {
|
|
|
|
$control
|
|
|
|
->addClass('aphront-form-preview-hidden')
|
|
|
|
->setError(pht('Hidden'));
|
|
|
|
} else if ($this->getIsLocked()) {
|
|
|
|
$control
|
|
|
|
->setError(pht('Locked'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
$form->appendControl($control);
|
|
|
|
}
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getValueForControl() {
|
|
|
|
return $this->getValue();
|
|
|
|
}
|
|
|
|
|
2015-11-18 19:38:12 +01:00
|
|
|
public function getValueForDefaults() {
|
|
|
|
$value = $this->getValue();
|
|
|
|
|
|
|
|
// By default, just treat the empty string like `null` since they're
|
|
|
|
// equivalent for almost all fields and this reduces the number of
|
|
|
|
// meaningless transactions we generate when adjusting defaults.
|
|
|
|
if ($value === '') {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
protected function getValue() {
|
|
|
|
return $this->value;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setValue($value) {
|
|
|
|
$this->hasValue = true;
|
2015-11-29 22:24:47 +01:00
|
|
|
$this->initialValue = $value;
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
$this->value = $value;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setMetadataValue($key, $value) {
|
|
|
|
$this->metadata[$key] = $value;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2015-11-29 22:24:47 +01:00
|
|
|
public function getMetadata() {
|
|
|
|
return $this->metadata;
|
|
|
|
}
|
|
|
|
|
2015-11-30 16:58:35 +01:00
|
|
|
public function getValueForTransaction() {
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
return $this->getValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getTransactionType() {
|
|
|
|
return $this->transactionType;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setTransactionType($type) {
|
|
|
|
$this->transactionType = $type;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function readValueFromRequest(AphrontRequest $request) {
|
2015-12-02 14:55:31 +01:00
|
|
|
$check = $this->getAllReadValueFromRequestKeys();
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
foreach ($check as $key) {
|
|
|
|
if (!$this->getValueExistsInRequest($request, $key)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->value = $this->getValueFromRequest($request, $key);
|
2015-12-02 14:55:31 +01:00
|
|
|
break;
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
}
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2015-12-15 15:57:32 +01:00
|
|
|
public function readValueFromComment($value) {
|
|
|
|
$this->value = $this->getValueFromComment($value);
|
2015-12-04 18:30:53 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getValueFromComment($value) {
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
2015-12-02 14:55:31 +01:00
|
|
|
public function getAllReadValueFromRequestKeys() {
|
|
|
|
$keys = array();
|
|
|
|
|
|
|
|
$keys[] = $this->getKey();
|
|
|
|
foreach ($this->getAliases() as $alias) {
|
|
|
|
$keys[] = $alias;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $keys;
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
}
|
|
|
|
|
Allow ApplicationEditor forms to be reconfigured
Summary:
Ref T9132. This diff doesn't do anything interesting, it just lays the groundwork for more interesting future diffs.
Broadly, the idea here is to let you create multiple views of each edit form. For example, we might create several different "Create Task" forms, like:
- "New Bug Report"
- "New Feature Request"
These would be views of the "Create Task" form, but with various adjustments:
- A form might have additional instructions ("how to file a good bug report").
- A form might have prefilled values for some fields (like particular projects, subscribers, or policies).
- A form might have some fields locked (so they can not be edited) or hidden.
- A form might have a different field order.
- A form might have a limited visibility policy, so only some users can access it.
This diff adds a new storage object (`EditEngineConfiguration`) to keep track of all those customizations and represent "a form which has been configured to look and work a certain way".
This doesn't let these configurations do anything useful/interesting, and you can't access them directly yet, it's just all the boring plumbing to enable more interesting behavior in the future.
Test Plan:
ApplicationEditor forms now let you manage available forms and edit the current form:
{F959025}
There's a new (bare bones) list of all available engines:
{F959030}
And if you jump into an engine, you can see all the forms for it:
{F959038}
The actual form configurations have standard detail/edit pages. The edit pages are themselves driven by ApplicationEditor, of course, so you can edit the form for editing forms.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T9132
Differential Revision: https://secure.phabricator.com/D14453
2015-11-04 21:52:52 +01:00
|
|
|
public function readDefaultValueFromConfiguration($value) {
|
|
|
|
$this->value = $this->getDefaultValueFromConfiguration($value);
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getDefaultValueFromConfiguration($value) {
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
protected function getValueFromObject($object) {
|
|
|
|
if ($this->hasValue) {
|
|
|
|
return $this->value;
|
|
|
|
} else {
|
|
|
|
return $this->getDefaultValue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getValueExistsInRequest(AphrontRequest $request, $key) {
|
2015-12-02 14:55:31 +01:00
|
|
|
return $this->getHTTPParameterValueExists($request, $key);
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function getValueFromRequest(AphrontRequest $request, $key) {
|
2015-12-02 14:55:31 +01:00
|
|
|
return $this->getHTTPParameterValue($request, $key);
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
}
|
|
|
|
|
2015-12-07 20:37:51 +01:00
|
|
|
public function readValueFromField(PhabricatorEditField $other) {
|
|
|
|
$this->value = $this->getValueFromField($other);
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getValueFromField(PhabricatorEditField $other) {
|
|
|
|
return $other->getValue();
|
|
|
|
}
|
|
|
|
|
2015-11-29 22:24:47 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Read and return the value the object had when the user first loaded the
|
|
|
|
* form.
|
|
|
|
*
|
|
|
|
* This is the initial value from the user's point of view when they started
|
|
|
|
* the edit process, and used primarily to prevent race conditions for fields
|
|
|
|
* like "Projects" and "Subscribers" that use tokenizers and support edge
|
|
|
|
* transactions.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
* @return wild Value read from request.
|
|
|
|
*/
|
|
|
|
protected function getInitialValueFromSubmit(AphrontRequest $request, $key) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getInitialValue() {
|
|
|
|
return $this->initialValue;
|
|
|
|
}
|
|
|
|
|
2015-12-15 15:57:32 +01:00
|
|
|
public function setInitialValue($initial_value) {
|
|
|
|
$this->initialValue = $initial_value;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
public function readValueFromSubmit(AphrontRequest $request) {
|
|
|
|
$key = $this->getKey();
|
|
|
|
if ($this->getValueExistsInSubmit($request, $key)) {
|
|
|
|
$value = $this->getValueFromSubmit($request, $key);
|
|
|
|
} else {
|
|
|
|
$value = $this->getDefaultValue();
|
|
|
|
}
|
|
|
|
$this->value = $value;
|
2015-11-29 22:24:47 +01:00
|
|
|
|
|
|
|
$initial_value = $this->getInitialValueFromSubmit($request, $key);
|
|
|
|
$this->initialValue = $initial_value;
|
|
|
|
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getValueExistsInSubmit(AphrontRequest $request, $key) {
|
2015-12-02 14:55:31 +01:00
|
|
|
return $this->getHTTPParameterValueExists($request, $key);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getValueFromSubmit(AphrontRequest $request, $key) {
|
|
|
|
return $this->getHTTPParameterValue($request, $key);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getHTTPParameterValueExists(
|
|
|
|
AphrontRequest $request,
|
|
|
|
$key) {
|
2015-11-25 17:21:24 +01:00
|
|
|
$type = $this->getHTTPParameterType();
|
|
|
|
|
|
|
|
if ($type) {
|
|
|
|
return $type->getExists($request, $key);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
}
|
|
|
|
|
2015-12-02 14:55:31 +01:00
|
|
|
protected function getHTTPParameterValue($request, $key) {
|
|
|
|
$type = $this->getHTTPParameterType();
|
|
|
|
|
|
|
|
if ($type) {
|
|
|
|
return $type->getValue($request, $key);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function getDefaultValue() {
|
2015-11-25 17:21:24 +01:00
|
|
|
$type = $this->getHTTPParameterType();
|
|
|
|
|
|
|
|
if ($type) {
|
|
|
|
return $type->getDefaultValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
}
|
|
|
|
|
2015-11-04 14:05:10 +01:00
|
|
|
final public function getHTTPParameterType() {
|
2015-12-15 15:23:13 +01:00
|
|
|
if ($this->getIsConduitOnly()) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-11-04 14:05:10 +01:00
|
|
|
$type = $this->newHTTPParameterType();
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
|
2015-11-04 14:05:10 +01:00
|
|
|
if ($type) {
|
|
|
|
$type->setViewer($this->getViewer());
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
}
|
|
|
|
|
2015-11-04 14:05:10 +01:00
|
|
|
return $type;
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
}
|
|
|
|
|
2015-11-04 14:05:10 +01:00
|
|
|
protected function newHTTPParameterType() {
|
|
|
|
return new AphrontStringHTTPParameterType();
|
2015-11-03 14:38:06 +01:00
|
|
|
}
|
|
|
|
|
2015-11-03 17:45:00 +01:00
|
|
|
public function setEditTypeKey($edit_type_key) {
|
|
|
|
$this->editTypeKey = $edit_type_key;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getEditTypeKey() {
|
|
|
|
if ($this->editTypeKey === null) {
|
|
|
|
return $this->getKey();
|
|
|
|
}
|
|
|
|
return $this->editTypeKey;
|
|
|
|
}
|
|
|
|
|
2015-11-18 23:11:21 +01:00
|
|
|
protected function newEditType() {
|
2015-12-15 15:23:13 +01:00
|
|
|
// TODO: This could be a little cleaner.
|
|
|
|
$http_type = $this->getHTTPParameterType();
|
|
|
|
if ($http_type) {
|
|
|
|
$value_type = $http_type->getTypeName();
|
|
|
|
} else {
|
|
|
|
$value_type = 'wild';
|
|
|
|
}
|
|
|
|
|
2015-11-18 23:11:21 +01:00
|
|
|
return id(new PhabricatorSimpleEditType())
|
2015-12-15 15:23:13 +01:00
|
|
|
->setValueType($value_type);
|
2015-11-18 23:11:21 +01:00
|
|
|
}
|
|
|
|
|
2015-11-30 16:58:35 +01:00
|
|
|
protected function getEditType() {
|
2015-11-03 17:45:00 +01:00
|
|
|
$transaction_type = $this->getTransactionType();
|
2015-11-29 22:24:47 +01:00
|
|
|
|
2015-11-17 18:33:06 +01:00
|
|
|
if ($transaction_type === null) {
|
2015-11-29 22:24:47 +01:00
|
|
|
return null;
|
2015-11-17 18:33:06 +01:00
|
|
|
}
|
|
|
|
|
2015-11-03 17:45:00 +01:00
|
|
|
$type_key = $this->getEditTypeKey();
|
|
|
|
|
2015-11-29 22:24:47 +01:00
|
|
|
return $this->newEditType()
|
|
|
|
->setEditType($type_key)
|
|
|
|
->setTransactionType($transaction_type)
|
|
|
|
->setDescription($this->getDescription())
|
|
|
|
->setMetadata($this->getMetadata());
|
|
|
|
}
|
|
|
|
|
2015-11-30 16:58:35 +01:00
|
|
|
public function getConduitEditTypes() {
|
|
|
|
$edit_type = $this->getEditType();
|
|
|
|
|
|
|
|
if ($edit_type === null) {
|
Implement an "Attachments" behavior for Conduit Search APIs
Summary:
Ref T9964. We have various kinds of secondary data on objects (like subscribers, projects, paste content, Owners paths, file attachments, etc) which is somewhat slow, or somewhat large, or both.
Some approaches to handling this in the API include:
- Always return all of it (very easy, but slow).
- Require users to make separate API calls to get each piece of data (very simple, but inefficient and really cumbersome to use).
- Implement a hierarchical query language like GraphQL (powerful, but very complex).
- Kind of mix-and-match a half-power query language and some extra calls? (fairly simple, not too terrible?)
We currently mix-and-match internally, with `->needStuff(true)`. This is not a general-purpose, full-power graph query language like GraphQL, and it occasionally does limit us.
For example, there is no way to do this sort of thing:
$conpherence_thread_query = id(new ConpherenceThreadQuery())
->setViewer($viewer)
// ...
->setNeedMessages(true)
->setWhenYouLoadTheMessagesTheyNeedProfilePictures(true);
However, we almost never actually need to do this and when we do want to do it we usually don't //really// want to do it, so I don't think this is a major limit to the practical power of the system for the kinds of things we really want to do with it.
Put another way, we have a lot of 1-level hierarchical queries (get pictures or repositories or projects or files or content for these objects) but few-to-no 2+ level queries (get files for these objects, then get all the projects for those files).
So even though 1-level hierarchies are not a beautiful, general-purpose, fully-abstract system, they've worked well so far in practice and I'm comfortable moving forward with them in the API.
If we do need N-level queries in the future, there is no technical reason we can't put GraphQL (or something similar) on top of this eventually, and this would represent a solid step toward that. However, I suspect we'll never need them.
Upshot: I'm pretty happy with "->needX()" for all practical purposes, so this is just adding a way to say "->needX()" to the API.
Specifically, you say:
```
{
"attachments": {
"subscribers": true,
}
}
```
...and get back subscriber data. In the future (or for certain attachments), `true` might become a dictionary of extra parameters, if necessary, and could do so without breaking the API.
Test Plan:
- Ran queries to get attachments.
{F1025449}
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T9964
Differential Revision: https://secure.phabricator.com/D14772
2015-12-14 13:58:34 +01:00
|
|
|
return array();
|
2015-11-30 16:58:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return array($edit_type);
|
|
|
|
}
|
|
|
|
|
2015-12-15 15:57:32 +01:00
|
|
|
public function getCommentAction() {
|
|
|
|
$label = $this->getCommentActionLabel();
|
|
|
|
if ($label === null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$action = $this->newCommentAction();
|
|
|
|
if ($action === null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->hasCommentActionValue) {
|
|
|
|
$value = $this->getCommentActionValue();
|
|
|
|
} else {
|
|
|
|
$value = $this->getValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
$action
|
|
|
|
->setKey($this->getKey())
|
|
|
|
->setLabel($label)
|
|
|
|
->setValue($this->getValueForCommentAction($value));
|
|
|
|
|
|
|
|
return $action;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function newCommentAction() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getValueForCommentAction($value) {
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function shouldGenerateTransactionsFromSubmit() {
|
2015-12-15 15:23:13 +01:00
|
|
|
if ($this->getIsConduitOnly()) {
|
2015-12-15 15:57:32 +01:00
|
|
|
return false;
|
2015-12-15 15:23:13 +01:00
|
|
|
}
|
|
|
|
|
2015-11-30 16:58:35 +01:00
|
|
|
$edit_type = $this->getEditType();
|
2015-12-15 15:57:32 +01:00
|
|
|
if (!$edit_type) {
|
|
|
|
return false;
|
|
|
|
}
|
2015-11-29 22:24:47 +01:00
|
|
|
|
2015-12-15 15:57:32 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function shouldReadValueFromRequest() {
|
|
|
|
if ($this->getIsConduitOnly()) {
|
|
|
|
return false;
|
2015-11-03 17:45:00 +01:00
|
|
|
}
|
|
|
|
|
2015-12-15 15:57:32 +01:00
|
|
|
if ($this->getIsLocked()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->getIsHidden()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2015-11-03 17:45:00 +01:00
|
|
|
}
|
|
|
|
|
2015-12-15 15:57:32 +01:00
|
|
|
public function shouldReadValueFromSubmit() {
|
|
|
|
if ($this->getIsConduitOnly()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->getIsLocked()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->getIsHidden()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function shouldGenerateTransactionsFromComment() {
|
|
|
|
if ($this->getIsConduitOnly()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->getIsLocked()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->getIsHidden()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function generateTransactions(
|
|
|
|
PhabricatorApplicationTransaction $template,
|
|
|
|
array $spec) {
|
|
|
|
|
|
|
|
$edit_type = $this->getEditType();
|
|
|
|
if (!$edit_type) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'EditField (with key "%s", of class "%s") is generating '.
|
|
|
|
'transactions, but has no EditType.',
|
|
|
|
$this->getKey(),
|
|
|
|
get_class($this)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $edit_type->generateTransactions($template, $spec);
|
2015-12-02 23:38:11 +01:00
|
|
|
}
|
|
|
|
|
Implement a basic version of ApplicationEditor in Paste
Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.
This mostly looks and works like ApplicationSearch, and is heavily modeled on it.
Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.
This has no functional changes, except:
- I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
- Subscribers are now editable.
- Form field order is a little goofy (this will be fixed in a future diff).
- Subscribers and projects are now race-resistant.
The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.
Test Plan:
- Created pastes.
- Created pastes via API.
- Edited pastes.
- Edited every field.
- Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4768, T9132
Differential Revision: https://secure.phabricator.com/D14390
2015-11-03 03:58:32 +01:00
|
|
|
}
|