@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Allow configuration to have custom UI types

Summary:
Ref T1703. This sets the stage for (but does not yet implement) custom UI types for config. In particular, a draggable list for custom fields.

I might make all the builtin types go through this at some point too, but don't really want to bother for the moment. It would be very slightly cleaner but woudn't get us much of anything.

Test Plan:
UI now renders via custom code, although that code does nothing (produces an unadorned text field):

{F45693}

Reviewers: chad

Reviewed By: chad

CC: aran

Maniphest Tasks: T1703

Differential Revision: https://secure.phabricator.com/D6154

+222 -126
+3
src/__phutil_library_map__.php
··· 894 894 'PhabricatorConfigManagementSetWorkflow' => 'applications/config/management/PhabricatorConfigManagementSetWorkflow.php', 895 895 'PhabricatorConfigManagementWorkflow' => 'applications/config/management/PhabricatorConfigManagementWorkflow.php', 896 896 'PhabricatorConfigOption' => 'applications/config/option/PhabricatorConfigOption.php', 897 + 'PhabricatorConfigOptionType' => 'applications/config/custom/PhabricatorConfigOptionType.php', 897 898 'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php', 898 899 'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php', 899 900 'PhabricatorConfigSource' => 'infrastructure/env/PhabricatorConfigSource.php', ··· 918 919 'PhabricatorCrumbsView' => 'view/layout/PhabricatorCrumbsView.php', 919 920 'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php', 920 921 'PhabricatorCustomField' => 'infrastructure/customfield/field/PhabricatorCustomField.php', 922 + 'PhabricatorCustomFieldConfigOptionType' => 'infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php', 921 923 'PhabricatorCustomFieldDataNotAvailableException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldDataNotAvailableException.php', 922 924 'PhabricatorCustomFieldImplementationIncompleteException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldImplementationIncompleteException.php', 923 925 'PhabricatorCustomFieldIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldIndexStorage.php', ··· 2774 2776 'PhabricatorCrumbView' => 'AphrontView', 2775 2777 'PhabricatorCrumbsView' => 'AphrontView', 2776 2778 'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery', 2779 + 'PhabricatorCustomFieldConfigOptionType' => 'PhabricatorConfigOptionType', 2777 2780 'PhabricatorCustomFieldDataNotAvailableException' => 'Exception', 2778 2781 'PhabricatorCustomFieldImplementationIncompleteException' => 'Exception', 2779 2782 'PhabricatorCustomFieldIndexStorage' => 'PhabricatorLiskDAO',
+141 -125
src/applications/config/controller/PhabricatorConfigEditController.php
··· 236 236 return array($e_value, $errors, $value, $xaction); 237 237 } 238 238 239 - $type = $option->getType(); 240 - $set_value = null; 239 + if ($option->isCustomType()) { 240 + $info = $option->getCustomObject()->readRequest($option, $request); 241 + list($e_value, $errors, $set_value, $value) = $info; 242 + } else { 243 + $type = $option->getType(); 244 + $set_value = null; 241 245 242 - switch ($type) { 243 - case 'int': 244 - if (preg_match('/^-?[0-9]+$/', trim($value))) { 245 - $set_value = (int)$value; 246 - } else { 247 - $e_value = pht('Invalid'); 248 - $errors[] = pht('Value must be an integer.'); 249 - } 250 - break; 251 - case 'string': 252 - case 'enum': 253 - $set_value = (string)$value; 254 - break; 255 - case 'list<string>': 256 - $set_value = $request->getStrList('value'); 257 - break; 258 - case 'set': 259 - $set_value = array_fill_keys($request->getStrList('value'), true); 260 - break; 261 - case 'bool': 262 - switch ($value) { 263 - case 'true': 264 - $set_value = true; 265 - break; 266 - case 'false': 267 - $set_value = false; 268 - break; 269 - default: 246 + switch ($type) { 247 + case 'int': 248 + if (preg_match('/^-?[0-9]+$/', trim($value))) { 249 + $set_value = (int)$value; 250 + } else { 251 + $e_value = pht('Invalid'); 252 + $errors[] = pht('Value must be an integer.'); 253 + } 254 + break; 255 + case 'string': 256 + case 'enum': 257 + $set_value = (string)$value; 258 + break; 259 + case 'list<string>': 260 + $set_value = $request->getStrList('value'); 261 + break; 262 + case 'set': 263 + $set_value = array_fill_keys($request->getStrList('value'), true); 264 + break; 265 + case 'bool': 266 + switch ($value) { 267 + case 'true': 268 + $set_value = true; 269 + break; 270 + case 'false': 271 + $set_value = false; 272 + break; 273 + default: 274 + $e_value = pht('Invalid'); 275 + $errors[] = pht('Value must be boolean, "true" or "false".'); 276 + break; 277 + } 278 + break; 279 + case 'class': 280 + if (!class_exists($value)) { 270 281 $e_value = pht('Invalid'); 271 - $errors[] = pht('Value must be boolean, "true" or "false".'); 272 - break; 273 - } 274 - break; 275 - case 'class': 276 - if (!class_exists($value)) { 277 - $e_value = pht('Invalid'); 278 - $errors[] = pht('Class does not exist.'); 279 - } else { 280 - $base = $option->getBaseClass(); 281 - if (!is_subclass_of($value, $base)) { 282 + $errors[] = pht('Class does not exist.'); 283 + } else { 284 + $base = $option->getBaseClass(); 285 + if (!is_subclass_of($value, $base)) { 286 + $e_value = pht('Invalid'); 287 + $errors[] = pht('Class is not of valid type.'); 288 + } else { 289 + $set_value = $value; 290 + } 291 + } 292 + break; 293 + default: 294 + $json = json_decode($value, true); 295 + if ($json === null && strtolower($value) != 'null') { 282 296 $e_value = pht('Invalid'); 283 - $errors[] = pht('Class is not of valid type.'); 297 + $errors[] = pht( 298 + 'The given value must be valid JSON. This means, among '. 299 + 'other things, that you must wrap strings in double-quotes.'); 284 300 } else { 285 - $set_value = $value; 301 + $set_value = $json; 286 302 } 287 - } 288 - break; 289 - default: 290 - $json = json_decode($value, true); 291 - if ($json === null && strtolower($value) != 'null') { 292 - $e_value = pht('Invalid'); 293 - $errors[] = pht( 294 - 'The given value must be valid JSON. This means, among '. 295 - 'other things, that you must wrap strings in double-quotes.'); 296 - } else { 297 - $set_value = $json; 298 - } 299 - break; 303 + break; 304 + } 300 305 } 301 306 302 307 if (!$errors) { ··· 320 325 return null; 321 326 } 322 327 323 - $type = $option->getType(); 324 - $value = $entry->getValue(); 325 - switch ($type) { 326 - case 'int': 327 - case 'string': 328 - case 'enum': 329 - case 'class': 330 - return $value; 331 - case 'bool': 332 - return $value ? 'true' : 'false'; 333 - case 'list<string>': 334 - return implode("\n", nonempty($value, array())); 335 - case 'set': 336 - return implode("\n", nonempty(array_keys($value), array())); 337 - default: 338 - return PhabricatorConfigJSON::prettyPrintJSON($value); 328 + if ($option->isCustomType()) { 329 + return $option->getCustomObject()->getDisplayValue($option, $entry); 330 + } else { 331 + $type = $option->getType(); 332 + $value = $entry->getValue(); 333 + switch ($type) { 334 + case 'int': 335 + case 'string': 336 + case 'enum': 337 + case 'class': 338 + return $value; 339 + case 'bool': 340 + return $value ? 'true' : 'false'; 341 + case 'list<string>': 342 + return implode("\n", nonempty($value, array())); 343 + case 'set': 344 + return implode("\n", nonempty(array_keys($value), array())); 345 + default: 346 + return PhabricatorConfigJSON::prettyPrintJSON($value); 347 + } 339 348 } 340 349 } 341 350 ··· 344 353 $display_value, 345 354 $e_value) { 346 355 347 - $type = $option->getType(); 348 - switch ($type) { 349 - case 'int': 350 - case 'string': 351 - $control = id(new AphrontFormTextControl()); 352 - break; 353 - case 'bool': 354 - $control = id(new AphrontFormSelectControl()) 355 - ->setOptions( 356 + if ($option->isCustomType()) { 357 + $control = $option->getCustomObject()->renderControl( 358 + $option, 359 + $display_value, 360 + $e_value); 361 + } else { 362 + $type = $option->getType(); 363 + switch ($type) { 364 + case 'int': 365 + case 'string': 366 + $control = id(new AphrontFormTextControl()); 367 + break; 368 + case 'bool': 369 + $control = id(new AphrontFormSelectControl()) 370 + ->setOptions( 371 + array( 372 + '' => pht('(Use Default)'), 373 + 'true' => idx($option->getBoolOptions(), 0), 374 + 'false' => idx($option->getBoolOptions(), 1), 375 + )); 376 + break; 377 + case 'enum': 378 + $options = array_mergev( 356 379 array( 357 - '' => pht('(Use Default)'), 358 - 'true' => idx($option->getBoolOptions(), 0), 359 - 'false' => idx($option->getBoolOptions(), 1), 380 + array('' => pht('(Use Default)')), 381 + $option->getEnumOptions(), 360 382 )); 361 - break; 362 - case 'enum': 363 - $options = array_mergev( 364 - array( 365 - array('' => pht('(Use Default)')), 366 - $option->getEnumOptions(), 367 - )); 368 - $control = id(new AphrontFormSelectControl()) 369 - ->setOptions($options); 370 - break; 371 - case 'class': 372 - $symbols = id(new PhutilSymbolLoader()) 373 - ->setType('class') 374 - ->setAncestorClass($option->getBaseClass()) 375 - ->setConcreteOnly(true) 376 - ->selectSymbolsWithoutLoading(); 377 - $names = ipull($symbols, 'name', 'name'); 378 - asort($names); 379 - $names = array( 380 - '' => pht('(Use Default)'), 381 - ) + $names; 383 + $control = id(new AphrontFormSelectControl()) 384 + ->setOptions($options); 385 + break; 386 + case 'class': 387 + $symbols = id(new PhutilSymbolLoader()) 388 + ->setType('class') 389 + ->setAncestorClass($option->getBaseClass()) 390 + ->setConcreteOnly(true) 391 + ->selectSymbolsWithoutLoading(); 392 + $names = ipull($symbols, 'name', 'name'); 393 + asort($names); 394 + $names = array( 395 + '' => pht('(Use Default)'), 396 + ) + $names; 382 397 383 - $control = id(new AphrontFormSelectControl()) 384 - ->setOptions($names); 385 - break; 386 - case 'list<string>': 387 - case 'set': 388 - $control = id(new AphrontFormTextAreaControl()) 389 - ->setCaption(pht('Separate values with newlines or commas.')); 390 - break; 391 - default: 392 - $control = id(new AphrontFormTextAreaControl()) 393 - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) 394 - ->setCustomClass('PhabricatorMonospaced') 395 - ->setCaption(pht('Enter value in JSON.')); 396 - break; 398 + $control = id(new AphrontFormSelectControl()) 399 + ->setOptions($names); 400 + break; 401 + case 'list<string>': 402 + case 'set': 403 + $control = id(new AphrontFormTextAreaControl()) 404 + ->setCaption(pht('Separate values with newlines or commas.')); 405 + break; 406 + default: 407 + $control = id(new AphrontFormTextAreaControl()) 408 + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) 409 + ->setCustomClass('PhabricatorMonospaced') 410 + ->setCaption(pht('Enter value in JSON.')); 411 + break; 412 + } 413 + 414 + $control 415 + ->setLabel(pht('Value')) 416 + ->setError($e_value) 417 + ->setValue($display_value) 418 + ->setName('value'); 397 419 } 398 - 399 - $control 400 - ->setLabel(pht('Value')) 401 - ->setError($e_value) 402 - ->setValue($display_value) 403 - ->setName('value'); 404 420 405 421 if ($option->getLocked()) { 406 422 $control->setDisabled(true);
+39
src/applications/config/custom/PhabricatorConfigOptionType.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorConfigOptionType { 4 + 5 + public function validateOption(PhabricatorConfigOption $option, $value) { 6 + return; 7 + } 8 + 9 + public function readRequest( 10 + PhabricatorConfigOption $option, 11 + AphrontRequest $request) { 12 + 13 + $e_value = null; 14 + $errors = array(); 15 + $storage_value = $request->getStr('value'); 16 + $display_value = $request->getStr('value'); 17 + 18 + return array($e_value, $errors, $storage_value, $display_value); 19 + } 20 + 21 + public function getDisplayValue( 22 + PhabricatorConfigOption $option, 23 + PhabricatorConfigEntry $entry) { 24 + return $entry->getValue(); 25 + } 26 + 27 + public function renderControl( 28 + PhabricatorConfigOption $option, 29 + $display_value, 30 + $e_value) { 31 + 32 + return id(new AphrontFormTextControl()) 33 + ->setName('value') 34 + ->setLabel(pht('Value')) 35 + ->setValue($display_value) 36 + ->setError($e_value); 37 + } 38 + 39 + }
+4
src/applications/config/option/PhabricatorApplicationConfigOptions.php
··· 15 15 return; 16 16 } 17 17 18 + if ($option->isCustomType()) { 19 + return $option->getCustomObject()->validateOption($option, $value); 20 + } 21 + 18 22 switch ($option->getType()) { 19 23 case 'bool': 20 24 if ($value !== true &&
+25
src/applications/config/option/PhabricatorConfigOption.php
··· 17 17 private $hidden; 18 18 private $masked; 19 19 private $baseClass; 20 + private $customData; 21 + private $customObject; 20 22 21 23 public function setBaseClass($base_class) { 22 24 $this->baseClass = $base_class; ··· 176 178 177 179 public function getType() { 178 180 return $this->type; 181 + } 182 + 183 + public function isCustomType() { 184 + return !strncmp($this->getType(), 'custom:', 7); 185 + } 186 + 187 + public function getCustomObject() { 188 + if (!$this->customObject) { 189 + if (!$this->isCustomType()) { 190 + throw new Exception("This option does not have a custom type!"); 191 + } 192 + $this->customObject = newv(substr($this->getType(), 7), array()); 193 + } 194 + return $this->customObject; 195 + } 196 + 197 + public function getCustomData() { 198 + return $this->customData; 199 + } 200 + 201 + public function setCustomData($data) { 202 + $this->customData = $data; 203 + return $this; 179 204 } 180 205 181 206 /* -( PhabricatorMarkupInterface )----------------------------------------- */
+4 -1
src/applications/people/config/PhabricatorUserConfigOptions.php
··· 25 25 ); 26 26 } 27 27 28 + $custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType'; 29 + 28 30 return array( 29 - $this->newOption('user.fields', 'wild', $default) 31 + $this->newOption('user.fields', $custom_field_type, $default) 32 + ->setCustomData(id(new PhabricatorUser())->getCustomFieldBaseClass()) 30 33 ->setDescription(pht("Select and reorder user profile fields.")), 31 34 ); 32 35 }
+6
src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php
··· 1 + <?php 2 + 3 + final class PhabricatorCustomFieldConfigOptionType 4 + extends PhabricatorConfigOptionType { 5 + 6 + }