3 final class PhabricatorConfigEditController
4 extends PhabricatorConfigSettingsController
{
6 public function handleRequest(AphrontRequest
$request) {
7 $viewer = $request->getViewer();
8 $key = $request->getURIData('key');
10 $options = PhabricatorApplicationConfigOptions
::loadAllOptions();
11 if (empty($options[$key])) {
12 $ancient = PhabricatorExtraConfigSetupCheck
::getAncientConfig();
13 if (isset($ancient[$key])) {
15 "This configuration has been removed. You can safely delete ".
20 'This configuration option is unknown. It may be misspelled, '.
21 'or have existed in a previous version of the software.');
24 // This may be a dead config entry, which existed in the past but no
25 // longer exists. Allow it to be edited so it can be reviewed and
27 $option = id(new PhabricatorConfigOption())
31 ->setDescription($desc);
34 $option = $options[$key];
35 $group = $option->getGroup();
38 $issue = $request->getStr('issue');
40 // If the user came here from an open setup issue, send them back.
41 $done_uri = $this->getApplicationURI('issue/'.$issue.'/');
43 $done_uri = $this->getApplicationURI('settings/');
46 // Check if the config key is already stored in the database.
47 // Grab the value if it is.
48 $config_entry = id(new PhabricatorConfigEntry())
50 'configKey = %s AND namespace = %s',
54 $config_entry = id(new PhabricatorConfigEntry())
56 ->setNamespace('default')
58 $config_entry->setPHID($config_entry->generatePHID());
63 if ($request->isFormPost() && !$option->getLocked()) {
65 $result = $this->readRequest(
69 list($e_value, $value_errors, $display_value, $xaction) = $result;
70 $errors = array_merge($errors, $value_errors);
74 $editor = id(new PhabricatorConfigEditor())
76 ->setContinueOnNoEffect(true)
77 ->setContentSourceFromRequest($request);
80 $editor->applyTransactions($config_entry, array($xaction));
81 return id(new AphrontRedirectResponse())->setURI($done_uri);
82 } catch (PhabricatorConfigValidationException
$ex) {
83 $e_value = pht('Invalid');
84 $errors[] = $ex->getMessage();
88 if ($config_entry->getIsDeleted()) {
89 $display_value = null;
91 $display_value = $this->getDisplayValue(
94 $config_entry->getValue());
98 $form = id(new AphrontFormView())
99 ->setEncType('multipart/form-data');
103 $error_view = id(new PHUIInfoView())
104 ->setSeverity(PHUIInfoView
::SEVERITY_ERROR
)
105 ->setErrors($errors);
108 $doc_href = PhabricatorEnv
::getDoclink(
109 'Configuration Guide: Locked and Hidden Configuration');
111 $doc_link = phutil_tag(
115 'target' => '_blank',
117 pht('Learn more about locked and hidden options.'));
119 $status_items = array();
121 if ($option->getHidden()) {
122 $tag = id(new PHUITagView())
123 ->setName(pht('Hidden'))
124 ->setColor(PHUITagView
::COLOR_GREY
)
125 ->setBorder(PHUITagView
::BORDER_NONE
)
126 ->setType(PHUITagView
::TYPE_SHADE
);
129 'This configuration is hidden and can not be edited or viewed from '.
130 'the web interface.');
131 $status_items[] = id(new PHUIInfoView())
132 ->appendChild(array($message, ' ', $doc_link));
133 } else if ($option->getLocked()) {
134 $tag = id(new PHUITagView())
135 ->setName(pht('Locked'))
136 ->setColor(PHUITagView
::COLOR_RED
)
137 ->setBorder(PHUITagView
::BORDER_NONE
)
138 ->setType(PHUITagView
::TYPE_SHADE
);
140 $message = $option->getLockedMessage();
141 $status_items[] = id(new PHUIInfoView())
142 ->appendChild(array($message, ' ', $doc_link));
145 if ($option->getHidden() ||
$option->getLocked()) {
148 $controls = $this->renderControls(
156 ->addHiddenInput('issue', $request->getStr('issue'));
158 $description = $option->newDescriptionRemarkupView($viewer);
161 id(new AphrontFormMarkupControl())
162 ->setLabel(pht('Description'))
163 ->setValue($description));
167 $extra = $group->renderContextualDescription(
170 if ($extra !== null) {
172 id(new AphrontFormMarkupControl())
177 foreach ($controls as $control) {
178 $form->appendControl($control);
181 if (!$option->getLocked()) {
183 id(new AphrontFormSubmitControl())
184 ->addCancelButton($done_uri)
185 ->setValue(pht('Save Config Entry')));
188 $current_config = null;
189 if (!$option->getHidden()) {
190 $current_config = $this->renderDefaults($option, $config_entry);
191 $current_config = $this->buildConfigBoxView(
192 pht('Current Configuration'),
196 $examples = $this->renderExamples($option);
198 $examples = $this->buildConfigBoxView(
205 $box_header = array();
206 $box_header[] = $key;
208 $crumbs = $this->newCrumbs()
209 ->addTextCrumb($key, '/config/edit/'.$key);
211 $form_box = $this->buildConfigBoxView($box_header, $form, $tag);
213 $timeline = $this->buildTransactionTimeline(
215 new PhabricatorConfigTransactionQuery());
216 $timeline->setShouldTerminate(true);
218 $header = $this->buildHeaderView($title);
220 $view = id(new PHUITwoColumnView())
231 return $this->newPage()
234 ->appendChild($view);
237 private function readRequest(
238 PhabricatorConfigOption
$option,
239 AphrontRequest
$request) {
241 $type = $option->newOptionType();
243 $is_set = $type->isValuePresentInRequest($option, $request);
245 $value = $type->readValueFromRequest($option, $request);
249 $canonical_value = $type->newValueFromRequestValue(
252 $type->validateStoredValue($option, $canonical_value);
253 $xaction = $type->newTransaction($option, $canonical_value);
254 } catch (PhabricatorConfigValidationException
$ex) {
255 $errors[] = $ex->getMessage();
257 } catch (Exception
$ex) {
258 // NOTE: Some older validators throw bare exceptions. Purely in good
259 // taste, it would be nice to convert these at some point.
260 $errors[] = $ex->getMessage();
265 $errors ?
pht('Invalid') : null,
271 $delete_xaction = id(new PhabricatorConfigTransaction())
272 ->setTransactionType(PhabricatorConfigTransaction
::TYPE_EDIT
)
288 // TODO: If we missed on the new `PhabricatorConfigType` map, fall back
289 // to the old semi-modular, semi-hacky way of doing things.
291 $xaction = new PhabricatorConfigTransaction();
292 $xaction->setTransactionType(PhabricatorConfigTransaction
::TYPE_EDIT
);
297 if ($option->isCustomType()) {
298 $info = $option->getCustomObject()->readRequest($option, $request);
299 list($e_value, $errors, $set_value, $value) = $info;
303 'Unknown configuration option type "%s".',
304 $option->getType()));
308 $xaction->setNewValue(
311 'value' => $set_value,
317 return array($e_value, $errors, $value, $xaction);
320 private function getDisplayValue(
321 PhabricatorConfigOption
$option,
322 PhabricatorConfigEntry
$entry,
325 $type = $option->newOptionType();
327 return $type->newDisplayValue($option, $value);
330 if ($option->isCustomType()) {
331 return $option->getCustomObject()->getDisplayValue(
339 'Unknown configuration option type "%s".',
340 $option->getType()));
343 private function renderControls(
344 PhabricatorConfigOption
$option,
348 $type = $option->newOptionType();
350 return $type->newControls(
356 if ($option->isCustomType()) {
357 $controls = $option->getCustomObject()->renderControls(
364 'Unknown configuration option type "%s".',
365 $option->getType()));
371 private function renderExamples(PhabricatorConfigOption
$option) {
372 $examples = $option->getExamples();
378 $table[] = phutil_tag('tr', array('class' => 'column-labels'), array(
379 phutil_tag('th', array(), pht('Example')),
380 phutil_tag('th', array(), pht('Value')),
382 foreach ($examples as $example) {
383 list($value, $description) = $example;
385 if ($value === null) {
386 $value = phutil_tag('em', array(), pht('(empty)'));
388 if (is_array($value)) {
389 $value = implode("\n", $value);
393 $table[] = phutil_tag('tr', array('class' => 'column-labels'), array(
394 phutil_tag('th', array(), $description),
395 phutil_tag('td', array(), $value),
399 require_celerity_resource('config-options-css');
404 'class' => 'config-option-table',
405 'cellspacing' => '0',
406 'cellpadding' => '0',
411 private function renderDefaults(
412 PhabricatorConfigOption
$option,
413 PhabricatorConfigEntry
$entry) {
415 $stack = PhabricatorEnv
::getConfigSourceStack();
416 $stack = $stack->getStack();
419 $table[] = phutil_tag('tr', array('class' => 'column-labels'), array(
420 phutil_tag('th', array(), pht('Source')),
421 phutil_tag('th', array(), pht('Value')),
424 $is_effective_value = true;
425 foreach ($stack as $key => $source) {
426 $row_classes = array(
430 $value = $source->getKeys(
435 if (!array_key_exists($option->getKey(), $value)) {
436 $value = phutil_tag('em', array(), pht('(No Value Configured)'));
438 $value = $this->getDisplayValue(
441 $value[$option->getKey()]);
443 if ($is_effective_value) {
444 $is_effective_value = false;
445 $row_classes[] = 'config-options-effective-value';
449 $table[] = phutil_tag(
452 'class' => implode(' ', $row_classes),
455 phutil_tag('th', array(), $source->getName()),
456 phutil_tag('td', array(), $value),
460 require_celerity_resource('config-options-css');
465 'class' => 'config-option-table',
466 'cellspacing' => '0',
467 'cellpadding' => '0',