[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 final class PhabricatorConfigEditController 4 extends PhabricatorConfigController { 5 6 private $key; 7 8 public function willProcessRequest(array $data) { 9 $this->key = $data['key']; 10 } 11 12 public function processRequest() { 13 $request = $this->getRequest(); 14 $user = $request->getUser(); 15 16 17 $options = PhabricatorApplicationConfigOptions::loadAllOptions(); 18 if (empty($options[$this->key])) { 19 $ancient = PhabricatorSetupCheckExtraConfig::getAncientConfig(); 20 if (isset($ancient[$this->key])) { 21 $desc = pht( 22 "This configuration has been removed. You can safely delete ". 23 "it.\n\n%s", 24 $ancient[$this->key]); 25 } else { 26 $desc = pht( 27 'This configuration option is unknown. It may be misspelled, '. 28 'or have existed in a previous version of Phabricator.'); 29 } 30 31 // This may be a dead config entry, which existed in the past but no 32 // longer exists. Allow it to be edited so it can be reviewed and 33 // deleted. 34 $option = id(new PhabricatorConfigOption()) 35 ->setKey($this->key) 36 ->setType('wild') 37 ->setDefault(null) 38 ->setDescription($desc); 39 $group = null; 40 $group_uri = $this->getApplicationURI(); 41 } else { 42 $option = $options[$this->key]; 43 $group = $option->getGroup(); 44 $group_uri = $this->getApplicationURI('group/'.$group->getKey().'/'); 45 } 46 47 $issue = $request->getStr('issue'); 48 if ($issue) { 49 // If the user came here from an open setup issue, send them back. 50 $done_uri = $this->getApplicationURI('issue/'.$issue.'/'); 51 } else { 52 $done_uri = $group_uri; 53 } 54 55 // Check if the config key is already stored in the database. 56 // Grab the value if it is. 57 $config_entry = id(new PhabricatorConfigEntry()) 58 ->loadOneWhere( 59 'configKey = %s AND namespace = %s', 60 $this->key, 61 'default'); 62 if (!$config_entry) { 63 $config_entry = id(new PhabricatorConfigEntry()) 64 ->setConfigKey($this->key) 65 ->setNamespace('default') 66 ->setIsDeleted(true); 67 $config_entry->setPHID($config_entry->generatePHID()); 68 } 69 70 $e_value = null; 71 $errors = array(); 72 if ($request->isFormPost() && !$option->getLocked()) { 73 74 $result = $this->readRequest( 75 $option, 76 $request); 77 78 list($e_value, $value_errors, $display_value, $xaction) = $result; 79 $errors = array_merge($errors, $value_errors); 80 81 if (!$errors) { 82 83 $editor = id(new PhabricatorConfigEditor()) 84 ->setActor($user) 85 ->setContinueOnNoEffect(true) 86 ->setContentSourceFromRequest($request); 87 88 try { 89 $editor->applyTransactions($config_entry, array($xaction)); 90 return id(new AphrontRedirectResponse())->setURI($done_uri); 91 } catch (PhabricatorConfigValidationException $ex) { 92 $e_value = pht('Invalid'); 93 $errors[] = $ex->getMessage(); 94 } 95 } 96 } else { 97 if ($config_entry->getIsDeleted()) { 98 $display_value = null; 99 } else { 100 $display_value = $this->getDisplayValue( 101 $option, 102 $config_entry, 103 $config_entry->getValue()); 104 } 105 } 106 107 $form = new AphrontFormView(); 108 109 $error_view = null; 110 if ($errors) { 111 $error_view = id(new AphrontErrorView()) 112 ->setErrors($errors); 113 } else if ($option->getHidden()) { 114 $msg = pht( 115 'This configuration is hidden and can not be edited or viewed from '. 116 'the web interface.'); 117 118 $error_view = id(new AphrontErrorView()) 119 ->setTitle(pht('Configuration Hidden')) 120 ->setSeverity(AphrontErrorView::SEVERITY_WARNING) 121 ->appendChild(phutil_tag('p', array(), $msg)); 122 } else if ($option->getLocked()) { 123 124 $msg = $option->getLockedMessage(); 125 $error_view = id(new AphrontErrorView()) 126 ->setTitle(pht('Configuration Locked')) 127 ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) 128 ->appendChild(phutil_tag('p', array(), $msg)); 129 } 130 131 if ($option->getHidden()) { 132 $control = null; 133 } else { 134 $control = $this->renderControl( 135 $option, 136 $display_value, 137 $e_value); 138 } 139 140 $engine = new PhabricatorMarkupEngine(); 141 $engine->setViewer($user); 142 $engine->addObject($option, 'description'); 143 $engine->process(); 144 $description = phutil_tag( 145 'div', 146 array( 147 'class' => 'phabricator-remarkup', 148 ), 149 $engine->getOutput($option, 'description')); 150 151 $form 152 ->setUser($user) 153 ->addHiddenInput('issue', $request->getStr('issue')) 154 ->appendChild( 155 id(new AphrontFormMarkupControl()) 156 ->setLabel(pht('Description')) 157 ->setValue($description)); 158 159 if ($group) { 160 $extra = $group->renderContextualDescription( 161 $option, 162 $request); 163 if ($extra !== null) { 164 $form->appendChild( 165 id(new AphrontFormMarkupControl()) 166 ->setValue($extra)); 167 } 168 } 169 170 $form 171 ->appendChild($control); 172 173 $submit_control = id(new AphrontFormSubmitControl()) 174 ->addCancelButton($done_uri); 175 176 if (!$option->getLocked()) { 177 $submit_control->setValue(pht('Save Config Entry')); 178 } 179 180 $form->appendChild($submit_control); 181 182 $examples = $this->renderExamples($option); 183 if ($examples) { 184 $form->appendChild( 185 id(new AphrontFormMarkupControl()) 186 ->setLabel(pht('Examples')) 187 ->setValue($examples)); 188 } 189 190 if (!$option->getHidden()) { 191 $form->appendChild( 192 id(new AphrontFormMarkupControl()) 193 ->setLabel(pht('Default')) 194 ->setValue($this->renderDefaults($option, $config_entry))); 195 } 196 197 $title = pht('Edit %s', $this->key); 198 $short = pht('Edit'); 199 200 $form_box = id(new PHUIObjectBoxView()) 201 ->setHeaderText($title) 202 ->setForm($form); 203 204 if ($error_view) { 205 $form_box->setErrorView($error_view); 206 } 207 208 $crumbs = $this->buildApplicationCrumbs(); 209 $crumbs->addTextCrumb(pht('Config'), $this->getApplicationURI()); 210 211 if ($group) { 212 $crumbs->addTextCrumb($group->getName(), $group_uri); 213 } 214 215 $crumbs->addTextCrumb($this->key, '/config/edit/'.$this->key); 216 217 $xactions = id(new PhabricatorConfigTransactionQuery()) 218 ->withObjectPHIDs(array($config_entry->getPHID())) 219 ->setViewer($user) 220 ->execute(); 221 222 $xaction_view = id(new PhabricatorApplicationTransactionView()) 223 ->setUser($user) 224 ->setObjectPHID($config_entry->getPHID()) 225 ->setTransactions($xactions); 226 227 return $this->buildApplicationPage( 228 array( 229 $crumbs, 230 $form_box, 231 $xaction_view, 232 ), 233 array( 234 'title' => $title, 235 )); 236 } 237 238 private function readRequest( 239 PhabricatorConfigOption $option, 240 AphrontRequest $request) { 241 242 $xaction = new PhabricatorConfigTransaction(); 243 $xaction->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT); 244 245 $e_value = null; 246 $errors = array(); 247 248 $value = $request->getStr('value'); 249 if (!strlen($value)) { 250 $value = null; 251 252 $xaction->setNewValue( 253 array( 254 'deleted' => true, 255 'value' => null, 256 )); 257 258 return array($e_value, $errors, $value, $xaction); 259 } 260 261 if ($option->isCustomType()) { 262 $info = $option->getCustomObject()->readRequest($option, $request); 263 list($e_value, $errors, $set_value, $value) = $info; 264 } else { 265 $type = $option->getType(); 266 $set_value = null; 267 268 switch ($type) { 269 case 'int': 270 if (preg_match('/^-?[0-9]+$/', trim($value))) { 271 $set_value = (int)$value; 272 } else { 273 $e_value = pht('Invalid'); 274 $errors[] = pht('Value must be an integer.'); 275 } 276 break; 277 case 'string': 278 case 'enum': 279 $set_value = (string)$value; 280 break; 281 case 'list<string>': 282 case 'list<regex>': 283 $set_value = phutil_split_lines( 284 $request->getStr('value'), 285 $retain_endings = false); 286 287 foreach ($set_value as $key => $v) { 288 if (!strlen($v)) { 289 unset($set_value[$key]); 290 } 291 } 292 $set_value = array_values($set_value); 293 294 break; 295 case 'set': 296 $set_value = array_fill_keys($request->getStrList('value'), true); 297 break; 298 case 'bool': 299 switch ($value) { 300 case 'true': 301 $set_value = true; 302 break; 303 case 'false': 304 $set_value = false; 305 break; 306 default: 307 $e_value = pht('Invalid'); 308 $errors[] = pht('Value must be boolean, "true" or "false".'); 309 break; 310 } 311 break; 312 case 'class': 313 if (!class_exists($value)) { 314 $e_value = pht('Invalid'); 315 $errors[] = pht('Class does not exist.'); 316 } else { 317 $base = $option->getBaseClass(); 318 if (!is_subclass_of($value, $base)) { 319 $e_value = pht('Invalid'); 320 $errors[] = pht('Class is not of valid type.'); 321 } else { 322 $set_value = $value; 323 } 324 } 325 break; 326 default: 327 $json = json_decode($value, true); 328 if ($json === null && strtolower($value) != 'null') { 329 $e_value = pht('Invalid'); 330 $errors[] = pht( 331 'The given value must be valid JSON. This means, among '. 332 'other things, that you must wrap strings in double-quotes.'); 333 } else { 334 $set_value = $json; 335 } 336 break; 337 } 338 } 339 340 if (!$errors) { 341 $xaction->setNewValue( 342 array( 343 'deleted' => false, 344 'value' => $set_value, 345 )); 346 } else { 347 $xaction = null; 348 } 349 350 return array($e_value, $errors, $value, $xaction); 351 } 352 353 private function getDisplayValue( 354 PhabricatorConfigOption $option, 355 PhabricatorConfigEntry $entry, 356 $value) { 357 358 if ($option->isCustomType()) { 359 return $option->getCustomObject()->getDisplayValue( 360 $option, 361 $entry, 362 $value); 363 } else { 364 $type = $option->getType(); 365 switch ($type) { 366 case 'int': 367 case 'string': 368 case 'enum': 369 case 'class': 370 return $value; 371 case 'bool': 372 return $value ? 'true' : 'false'; 373 case 'list<string>': 374 case 'list<regex>': 375 return implode("\n", nonempty($value, array())); 376 case 'set': 377 return implode("\n", nonempty(array_keys($value), array())); 378 default: 379 return PhabricatorConfigJSON::prettyPrintJSON($value); 380 } 381 } 382 } 383 384 private function renderControl( 385 PhabricatorConfigOption $option, 386 $display_value, 387 $e_value) { 388 389 if ($option->isCustomType()) { 390 $control = $option->getCustomObject()->renderControl( 391 $option, 392 $display_value, 393 $e_value); 394 } else { 395 $type = $option->getType(); 396 switch ($type) { 397 case 'int': 398 case 'string': 399 $control = id(new AphrontFormTextControl()); 400 break; 401 case 'bool': 402 $control = id(new AphrontFormSelectControl()) 403 ->setOptions( 404 array( 405 '' => pht('(Use Default)'), 406 'true' => idx($option->getBoolOptions(), 0), 407 'false' => idx($option->getBoolOptions(), 1), 408 )); 409 break; 410 case 'enum': 411 $options = array_mergev( 412 array( 413 array('' => pht('(Use Default)')), 414 $option->getEnumOptions(), 415 )); 416 $control = id(new AphrontFormSelectControl()) 417 ->setOptions($options); 418 break; 419 case 'class': 420 $symbols = id(new PhutilSymbolLoader()) 421 ->setType('class') 422 ->setAncestorClass($option->getBaseClass()) 423 ->setConcreteOnly(true) 424 ->selectSymbolsWithoutLoading(); 425 $names = ipull($symbols, 'name', 'name'); 426 asort($names); 427 $names = array( 428 '' => pht('(Use Default)'), 429 ) + $names; 430 431 $control = id(new AphrontFormSelectControl()) 432 ->setOptions($names); 433 break; 434 case 'list<string>': 435 case 'list<regex>': 436 $control = id(new AphrontFormTextAreaControl()) 437 ->setCaption(pht('Separate values with newlines.')); 438 break; 439 case 'set': 440 $control = id(new AphrontFormTextAreaControl()) 441 ->setCaption(pht('Separate values with newlines or commas.')); 442 break; 443 default: 444 $control = id(new AphrontFormTextAreaControl()) 445 ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) 446 ->setCustomClass('PhabricatorMonospaced') 447 ->setCaption(pht('Enter value in JSON.')); 448 break; 449 } 450 451 $control 452 ->setLabel(pht('Value')) 453 ->setError($e_value) 454 ->setValue($display_value) 455 ->setName('value'); 456 } 457 458 if ($option->getLocked()) { 459 $control->setDisabled(true); 460 } 461 462 return $control; 463 } 464 465 private function renderExamples(PhabricatorConfigOption $option) { 466 $examples = $option->getExamples(); 467 if (!$examples) { 468 return null; 469 } 470 471 $table = array(); 472 $table[] = phutil_tag('tr', array('class' => 'column-labels'), array( 473 phutil_tag('th', array(), pht('Example')), 474 phutil_tag('th', array(), pht('Value')), 475 )); 476 foreach ($examples as $example) { 477 list($value, $description) = $example; 478 479 if ($value === null) { 480 $value = phutil_tag('em', array(), pht('(empty)')); 481 } else { 482 if (is_array($value)) { 483 $value = implode("\n", $value); 484 } 485 } 486 487 $table[] = phutil_tag('tr', array(), array( 488 phutil_tag('th', array(), $description), 489 phutil_tag('td', array(), $value), 490 )); 491 } 492 493 require_celerity_resource('config-options-css'); 494 495 return phutil_tag( 496 'table', 497 array( 498 'class' => 'config-option-table', 499 ), 500 $table); 501 } 502 503 private function renderDefaults( 504 PhabricatorConfigOption $option, 505 PhabricatorConfigEntry $entry) { 506 507 $stack = PhabricatorEnv::getConfigSourceStack(); 508 $stack = $stack->getStack(); 509 510 $table = array(); 511 $table[] = phutil_tag('tr', array('class' => 'column-labels'), array( 512 phutil_tag('th', array(), pht('Source')), 513 phutil_tag('th', array(), pht('Value')), 514 )); 515 foreach ($stack as $key => $source) { 516 $value = $source->getKeys( 517 array( 518 $option->getKey(), 519 )); 520 521 if (!array_key_exists($option->getKey(), $value)) { 522 $value = phutil_tag('em', array(), pht('(empty)')); 523 } else { 524 $value = $this->getDisplayValue( 525 $option, 526 $entry, 527 $value[$option->getKey()]); 528 } 529 530 $table[] = phutil_tag('tr', array('class' => 'column-labels'), array( 531 phutil_tag('th', array(), $source->getName()), 532 phutil_tag('td', array(), $value), 533 )); 534 } 535 536 require_celerity_resource('config-options-css'); 537 538 return phutil_tag( 539 'table', 540 array( 541 'class' => 'config-option-table', 542 ), 543 $table); 544 } 545 546 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |